Since netcat
is honored as “TCP/IP swiss army knife”, I read its source code in OpenBSD
to summarize some socket programming tips:
(1) Client connects in non-blocking mode:
......
s = socket(res->ai_family, res->ai_socktype |
SOCK_NONBLOCK, res->ai_protocol);
......
if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) {
pfd.fd = s;
pfd.events = POLLOUT;
ret = poll(&pfd, 1, timeout));
}
......
Creating socket and set SOCK_NONBLOCK
mode for it. Then calling connect()
function, if ret
is 0
, it means connection is established successfully; if errno
is EINPROGRESS
, we can use timeout
to control how long to wait; otherwise the connection can’t be built.
(2) The usage of poll()
:
......
/* stdin */
pfd[POLL_STDIN].fd = stdin_fd;
pfd[POLL_STDIN].events = POLLIN;
/* network out */
pfd[POLL_NETOUT].fd = net_fd;
pfd[POLL_NETOUT].events = 0;
/* network in */
pfd[POLL_NETIN].fd = net_fd;
pfd[POLL_NETIN].events = POLLIN;
/* stdout */
pfd[POLL_STDOUT].fd = stdout_fd;
pfd[POLL_STDOUT].events = 0;
......
/* poll */
num_fds = poll(pfd, 4, timeout);
/* treat poll errors */
if (num_fds == -1)
err(1, "polling error");
/* timeout happened */
if (num_fds == 0)
return;
/* treat socket error conditions */
for (n = 0; n < 4; n++) {
if (pfd[n].revents & (POLLERR|POLLNVAL)) {
pfd[n].fd = -1;
}
}
/* reading is possible after HUP */
if (pfd[POLL_STDIN].events & POLLIN &&
pfd[POLL_STDIN].revents & POLLHUP &&
!(pfd[POLL_STDIN].revents & POLLIN))
pfd[POLL_STDIN].fd = -1;
Usually, we just need to care about file descriptors for reading:
pfd[POLL_STDIN].fd = stdin_fd;
pfd[POLL_STDIN].events = POLLIN;
no need to monitor file descriptors for writing:
/* network out */
pfd[POLL_NETOUT].fd = net_fd;
pfd[POLL_NETOUT].events = 0;
According to poll() manual from OpenBSD
, if no need for “high-priority” (maybe out-of-band) data, POLLIN
is enough, otherwise the monitor events should be POLLIN|POLLPRI
. And this is similar for POLLOUT
and POLLWRBAND
.
There are 3
values(POLLERR
, POLLNVAL
and POLLHUP
) which are only used in struct pollfd
‘s revents
. If POLLERR
or POLLNVAL
is detected, it’s not necessary to poll this file descriptor furthermore:
if (pfd[n].revents & (POLLERR|POLLNVAL)) {
pfd[n].fd = -1;
}
We should pay more attention to POLLHUP
:
(a)
POLLHUP
The device or socket has been disconnected. This event and POLLOUT are mutually-exclusive; a descriptor can never be writable if a hangup has occurred. However, this event and POLLIN, POLLRDNORM, POLLRDBAND, or POLLPRI are not mutually-exclusive. This flag is only valid in the revents bitmask; it is ignored in the events member.
(b)
The second difference is that on EOF there is no guarantee that POLLIN will be set in revents, the caller must also check for POLLHUP.
So it means if POLLHUP
and POLLIN
are both set in revents
, there must be data to read (maybe EOF
?), otherwise if only POLLHUP
is checked, there is no data to read from.