1

Today I just learned the socket's select(). The information found on the Internet directly says that FD_ISSET() judges whether fd is available, and if it is available, accept() establishes a connection.

But there is no explanation why it is not possible to establish a connection with accept() first, and then use FD_ISSET() to determine whether fd is available?

My guess is that using accept() to establish a connection will consume more resources than use FD_ISSET() to determine whether fd is available.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
ThWhMn
  • 11
  • 1
  • Do you want to (1) see whether there is a connection waiting, and then (2) get it? Or do you want to (1) wait for a connection to appear and then (2) see whether there is another one? – user253751 Jul 07 '20 at 19:21

3 Answers3

2

For a listening socket, the socket should be part of the read_fds. select() will then wake up if the listening socket is readable. select() will return the subset of read_fds that are actually readable.

If the listening socket is part of that subset, then it is readable, which indicates a new connection is ready to be accepted. For TCP, this typically means the 3 way handshake has completed. So, accept() on the listening socket will return a new socket to represent the new connection.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
jxh
  • 69,070
  • 8
  • 110
  • 193
0

The Linux man pages describes the select() function https://man7.org/linux/man-pages/man2/select.2.html in this way:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select() allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.

The goal of select() is to allow you to test whether one or more sockets are ready for an action or not. The timeout parameter allows you to specify how long to wait for any of the sockets to be ready. So this is a synchronous call in that you call the function and it will either return when one or more of the sockets are ready for an action or, if a time out value was specified, when the time out has expired.

On success, select() and pselect() return the number of file descriptors contained in the three returned descriptor sets (that is, the total number of bits that are set in readfds, writefds, exceptfds). The return value may be zero if the timeout expired before any file descriptors became ready.

On error, -1 is returned, and errno is set to indicate the error; the file descriptor sets are unmodified, and timeout becomes undefined.

You specify which sockets are to be checked by the select() by specifying a map using the FD_SET() macro. See Socket programming in C, using the select() function as well as Polling using Select function in C++ on Windows and Is it necessary to reset the fd_set between select system call? for examples.

If you have more than one socket set in a mask or are using multiple masks with sockets set then when the select() function returns indicating that one or more sockets are ready, you need to use the FD_ISSET() macro to determine which of the sockets you have requested a status on are actually ready.

However if you have specified only a single socket in only one of the masks then when the select() returns indicating a sockets is ready, you only have one socket that could be ready.

The reason for using the FD_ISSET() anyway is just as a check no matter what. Think of it as good programming practice as well as improving robustness of the source code in the face of maintenance which may add other sockets in the future.

If you call accept() with a blocking listening socket and there is not a connection waiting to be accepted then the thread is going to block on the accept() until a connection request is received. https://man7.org/linux/man-pages/man2/accept.2.html

If no pending connections are present on the queue, and the socket is not marked as nonblocking, accept() blocks the caller until a connection is present. If the socket is marked nonblocking and no pending connections are present on the queue, accept() fails with the error EAGAIN or EWOULDBLOCK.

In order to be notified of incoming connections on a socket, you can use select(2), poll(2), or epoll(7). A readable event will be delivered when a new connection is attempted and you may then call accept() to get a socket for that connection. Alternatively, you can set the socket to deliver SIGIO when activity occurs on a socket; see socket(7) for details.

Since you are using select() to check if the listening socket is ready the most probably having accept() block is not the behavior you desire so using FD_ISSET() first before calling accept() seems the most prudent course of action.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
0

Today I just learned the socket's select(). The information found on the Internet directly says that FD_ISSET() judges whether fd is available, and if it is available, accept() establishes a connection.

"whether fd is available" is a rather poor way to word it, and perhaps that is contributing to your confusion. Having called select() with arguments expressing an interest in reading file descriptor fd, FD_ISSET() can tell you whether fd is ready to be read (without blocking). For the file descriptor representing a listening socket, "ready to be read" means that there is a pending connection waiting to be accepted.

But there is no explanation why it is not possible to establish a connection with accept() first, and then use FD_ISSET() to determine whether fd is available?

You seem to have missed the point of using select(), which is to avoid blocking on I/O operations such as accept().

One certainly can call accept() on a file descriptor without first using select() and FD_ISSET() to determine that there is a connection waiting to be accepted. In that event, if fd is an open file descriptor representing a listening socket then accept() will block until it can accept a connection, or until it is interrupted for some other reason. This can be a perfectly reasonable thing to do, but it is exactly what you are trying to avoid doing by applying select() to the problem.

In any event, FD_ISSET() is not useful after you have already accepted a connection (until you check again with select()), because you already accepted a connection. If FD_ISSET() reports at that point that the file descriptor is in the set then that information is stale. Otherwise, it is merely moot.

My guess is that using accept() to establish a connection will consume more resources than use FD_ISSET() to determine whether fd is available.

That is likely true, but it is irrelevant. The whole point of using select() for something like this is not to save computational cost but rather to avoid blocking. Typically, that is used to allow a single thread to manage multiple I/O channels efficiently, but it can also be used to perform other kinds of work in between handling I/O.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157