recv() 不阻塞

3

我有两台机器运行一个我为测试目的编写的简单的C TCP服务器,一台是Fedora 16,另一台是Ubuntu 11.10。我的Fedora机器完美地工作,但在Ubuntu机器上,recv()不会阻塞。请记住这些机器正在运行完全相同的代码。有人见过这种情况吗?谢谢。

int TcpSocket::ReadFromClient(int socket, char* buf, int len)
{
    char *request = buf;
    int slen = len;

    int c = recv(socket, request, slen, 0);
    while((c > 0) && (request[c-1] != '\n'))
    {
        request += c;
        slen -= c;
        c = recv(socket, request, slen, 0);
    }

    if (c < 0)
    {
        return c;
    }
    else if(c == 0)
    {
        //Sending back an empty string
        buf[0] = '\0';
    }

    return len-slen;
}

听起来你的套接字处于非阻塞模式。 - Keith Randall
第一次返回1(当实际发送数据时),在循环时返回0。为什么它会在我的Fedora机器上阻塞? - tier1
2
@tkcsam,recv返回0表示另一端关闭了连接,或者可能是您传递给recv的slen参数为0。 - nos
我仍然不明白为什么 recv 第二次不会阻塞。 - tier1
1个回答

2
看起来你的代码意图是在读取到一个 '\n' 字节时停止读取。如果是这样的话,那么你需要逐字节从套接字中读取,而不是使用整个可用缓冲区大小,特别是因为你只检查了缓冲区的最后一个字节,而不是检查每个接收到的字节。
你还应该改变循环逻辑,只在一个地方调用 recv(),而不是两个地方。你当前的实现方式在缓冲区耗尽时使用 slen=0 调用 recv(),这将设置 c=0 并使缓冲区的第一个字节无效。
尝试使用以下代码替代:
int TcpSocket::ReadFromClient(int socket, char* buf, int len)
{ 
    int slen = len;
    char ch;

    while (len > 0)
    {
        int ret = recv(socket, &ch, 1, 0); 
        if (ret > 0)
        {
            *buf = ch; 
            ++buf; 
            --len; 

            if (ch == '\n')
                break;
        }
        else
        {
            if ((ret == 0) || (errno != EAGAIN))
                return ret;

            fd_set readfd;
            FD_ZERO(&readfd);
            FD_SET(socket, &readfd);

            timeval tv;
            tv.tv_sec = 5;
            tv.tv_usec = 0;

            ret = select(socket+1, &readfd, NULL, NULL, &tv);
            if (ret < 0)
                return ret;

            if (ret == 0)
            {
                // timeout elapsed while waiting for data
                // do something if desired...
            }
        } 
    } 

    return slen - len;
}

可以每次读取一个字节并在换行符处停止,也可以尽可能多地读取数据,并将接收到的数据分成行。然而,后一种方法需要将剩余的数据保存以供下一次读取使用。 - Some programmer dude
1
通常情况下,使用单独的缓冲区接收所有传入数据,然后根据需要分割该缓冲区更加高效,并且是首选方法。逐字节读取不太高效,但编码更容易。因此,这是个人偏好、效率和代码复杂度之间的权衡。 - Remy Lebeau
@RemyLebeau,你的代码看起来很稳定。然而,在第一次连接之后,它仍然在我的Ubuntu机器上以100%的速度运行。开始怀疑我的机器是否有问题。非常奇怪。 - tier1
这种循环运行时占用100%的CPU利用率是正常的,因为在这段代码中没有任何内容来让CPU切换。这就是为什么我添加了有关使用select()的注释。这将使循环进入睡眠状态,释放CPU以执行其他任务,直到数据实际到达套接字。如果您不想使用select(),那么至少可以使用sleep() - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接