FastDFS 中的 tcprecvdata_ex 与 tcprecvdata_nb_ex
FastDFS 与 socket 相关的函数一般放在 common/sockopt.c 文件里,其中有两个函数,非别为 tcprecvdata_ex 和 tcprecvdata_nb_ex。从名字上看,很明显后者是想表达 nonblock 的意思,那么看代码证实一下。它们的代码分别如下:
1. tcprecvdata_ex
int tcprecvdata_ex(int sock, void *data, const int size, \
const int timeout, int *count)
{
int left_bytes;
int read_bytes;
int res;
int ret_code;
unsigned char* p;
#ifdef USE_SELECT
fd_set read_set;
struct timeval t;
#else
struct pollfd pollfds;
#endif
#ifdef USE_SELECT
FD_ZERO(&read_set);
FD_SET(sock, &read_set);
#else
pollfds.fd = sock;
pollfds.events = POLLIN;
#endif
read_bytes = 0;
ret_code = 0;
p = (unsigned char*)data;
left_bytes = size;
while (left_bytes > 0)
{
#ifdef USE_SELECT
if (timeout <= 0)
{
res = select(sock+1, &read_set, NULL, NULL, NULL);
}
else
{
t.tv_usec = 0;
t.tv_sec = timeout;
res = select(sock+1, &read_set, NULL, NULL, &t);
}
#else
res = poll(&pollfds, 1, 1000 * timeout);
if (pollfds.revents & POLLHUP)
{
ret_code = ENOTCONN;
break;
}
#endif
if (res < 0)
{
ret_code = errno != 0 ? errno : EINTR;
break;
}
else if (res == 0)
{
ret_code = ETIMEDOUT;
break;
}
read_bytes = recv(sock, p, left_bytes, 0);
if (read_bytes < 0)
{
ret_code = errno != 0 ? errno : EINTR;
break;
}
if (read_bytes == 0)
{
ret_code = ENOTCONN;
break;
}
left_bytes -= read_bytes;
p += read_bytes;
}
if (count != NULL)
{
*count = size - left_bytes;
}
return ret_code;
}
2. tcprecvdata_nb_ex
int tcprecvdata_nb_ex(int sock, void *data, const int size, \
const int timeout, int *count)
{
int left_bytes;
int read_bytes;
int res;
int ret_code;
unsigned char* p;
#ifdef USE_SELECT
fd_set read_set;
struct timeval t;
#else
struct pollfd pollfds;
#endif
#ifdef USE_SELECT
FD_ZERO(&read_set);
FD_SET(sock, &read_set);
#else
pollfds.fd = sock;
pollfds.events = POLLIN;
#endif
read_bytes = 0;
ret_code = 0;
p = (unsigned char*)data;
left_bytes = size;
while (left_bytes > 0)
{
read_bytes = recv(sock, p, left_bytes, 0);
if (read_bytes > 0)
{
left_bytes -= read_bytes;
p += read_bytes;
continue;
}
if (read_bytes < 0)
{
if (!(errno == EAGAIN || errno == EWOULDBLOCK))
{
ret_code = errno != 0 ? errno : EINTR;
break;
}
}
else
{
ret_code = ENOTCONN;
break;
}
#ifdef USE_SELECT
if (timeout <= 0)
{
res = select(sock+1, &read_set, NULL, NULL, NULL);
}
else
{
t.tv_usec = 0;
t.tv_sec = timeout;
res = select(sock+1, &read_set, NULL, NULL, &t);
}
#else
res = poll(&pollfds, 1, 1000 * timeout);
if (pollfds.revents & POLLHUP)
{
ret_code = ENOTCONN;
break;
}
#endif
if (res < 0)
{
ret_code = errno != 0 ? errno : EINTR;
break;
}
else if (res == 0)
{
ret_code = ETIMEDOUT;
break;
}
}
if (count != NULL)
{
*count = size - left_bytes;
}
return ret_code;
}
乍一眼看上去,这两个不是一样吗?仔细看下就会发现区别所在:nonblock 版本在循环里把 recv 操作放在了 select/poll 之前,而 block 版本在循环里把 recv 操作放在了 select/poll 之后。其他地方几乎都是一模一样的。
其实,这两个函数处理的 socket 在调用他们之前,都已经被设置为 nonblock 了,这是一个前提条件。也就是说,我们这两个函数是对一个 nonblock 的 socket 在细分为 block 和 nonblock,有点拗口,既然这个 socket 都已经是 nonblock 的了,为什么这里还会有 block 和 nonblock 的区别呢?
他们的区别在于,tcprecvdata_nb_ex 会调用 select/poll 从而阻塞的唯一场合就是在一个 recv 返回 EAGAIN 错误或 EWOULDBLOCK 时,而 tcprecvdata_nb 在每一次 recv 调用前都会调用可能引起阻塞的 select/poll。
在这两个函数中学到了一个技巧。就是如何在一个 nonblock 的 socket 连接中获取希望获取的字节数。由于 socket 是 nonblock 的,调用 recv 马上返回,如果出错且错误是 EAGAIN 或 EWOULDBLOCK,那么我们希望能再重试一下。但如果只是用一个 while 循环来主动一遍一遍的重试的话,超时该如何处理呢?其实这才是这两个函数中用到 select/poll 的真正意义所在。即,在一个非阻塞的套接字连接里,达到阻塞接收指定大小的数据的效果,但同时又有超时机制来保证并不会真正的阻塞。