代理中的非阻塞套接字和poll()怪癖 - 新手

时间:2011-08-30 21:51:28

标签: c sockets nonblocking

我是一个涉及C的新手,我的小项目是编写一个简单的SOCKS4代理。感谢这里的帮助,我到目前为止使用非阻塞套接字和poll()作为我的例程。但是在这一点上我似乎有两个问题:

  1. 如果传入的Socket rcvSocket被关闭,则传出的Socket dstSocket不会关闭,反之亦然。我不在循环中检查这个,但我不知道如何。我试过POLLHUP作为revents,但这似乎没有办法。正常检查似乎是recv()是否返回0,但是对于非阻塞套接字也是有效的吗?如果是这样,那怎么能用revents,我似乎无法弄清楚我会把它放在哪里,因为如果POLLIN | POLLPRI设置在我看来recv()永远不应该返回0?另外我不明白POLLIN和POLLPRI之间究竟有什么区别,在我看来只是检查“数据是否可供阅读”?

  2. 代理似乎适用于我使用netcat测试的连接。但是,如果我使用浏览器,它说(当我定位网站时)我是否要保存“二进制数据”。我检查了wireshark中的数据,并且从服务器收到的内容正好逐字节地正确转发到客户端。如果有人可能知道为什么这个程序会发生这种情况会很好:)

  3. 附上相关代码(谨防编程新手):

     fds[1].fd = dstSocket;
     fds[0].fd = rcvSocket;
     fds[1].events = POLLIN | POLLPRI | POLLHUP;
     fds[0].events = POLLIN | POLLPRI | POLLHUP;
    
     timer = poll(fds, 2, timeout_msecs); /* i dont use this yet */
    
     fcntl(rcvSocket, F_SETFL, O_NONBLOCK);
     fcntl(dstSocket, F_SETFL, O_NONBLOCK);
    
     while (1 == 1)
     {
         if (fds[0].revents & POLLIN | POLLPRI)
         {
            recvMsgSize = recv(rcvSocket, rcvBuffer, RCVBUFSIZE, 0);
            if (recvMsgSize > 0) {send(dstSocket, rcvBuffer, recvMsgSize, 0);}
         }
         if (fds[1].revents & POLLIN | POLLPRI)
         { 
            sndMsgSize = recv(dstSocket, sndBuffer, RCVBUFSIZE, 0);
            if (sndMsgSize > 0) { send(rcvSocket, sndBuffer, sndMsgSize, 0);}
         }         
    
         if ((fds[0].revents & POLLHUP) || (fds[1].revents & POLLHUP))
         {
            close(rcvSocket);
            close(dstSocket);
         }
    } 
    

1 个答案:

答案 0 :(得分:1)

recv()在干净的远程关闭时返回0 - 即使对于非阻塞套接字也是如此。在这种情况下,将返回POLLIN - 远程端已关闭套接字的通知被视为“可读”事件。

您不应该使用POLLPRI进行SOCKS / HTTP连接 - 它表示TCP“紧急数据”,这些协议不使用这些协议(实际上,根本没有使用)。


除了您的直接问题,您还需要做更多工作来实施可靠的代理:

  1. 您需要在每个循环上调用poll(),而不仅仅是一次。你写它的方式是繁忙循环,这通常不被认为是可以接受的做法。
  2. 您应该使用SIGPIPEsignal(SIGPIPE, SIG_IGN);的处置设置为忽略。这使您可以优雅地处理写入失败。
  3. 您应该检查send()的结果。请注意,它可以写入少于您请求的数量 - 在这种情况下,您必须保持未发送的数据被缓冲,返回poll()并尝试在POLLOUT被引发时再次发送剩余数据插座。您只想在 未发送的数据时请求POLLOUT,因此您需要确保在每次.events来电之前正确设置poll()
  4. 如果errnorecv()返回的值小于0,则应检查send() EINTR并忽略EWOULDBLOCK;任何其他错误都应被视为连接失败。
  5. 当一个套接字关闭时,您不应该立即关闭两个方向 - 您必须支持非对称关闭。这意味着当您看到远程端已关闭fds[0]时,您应该调用shutdown(fds[1], SHUT_WR);,反之亦然;只有当两个都已关闭(或发生连接失败)时,才应在两个文件描述符上调用close()并完成。