【问题标题】:Non-Blocking socket and poll() quirks in proxy - newbie代理中的非阻塞套接字和 poll() 怪癖 - 新手
【发布时间】:2011-08-30 21:51:27
【问题描述】:

我是一个涉足 C 的新手,我的小项目是编写一个简单的 SOCKS4 代理。多亏了这里的帮助,我才得以在日常工作中使用非阻塞套接字和 poll()。但是在这一点上我似乎有两个问题:

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

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

附上相关代码(小心编程新手):

 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);
     }
} 

【问题讨论】:

  • 虽然这可能不是真正的问题,但您最终需要处理它:使用非阻塞套接字 send() 可能不会发送您请求它发送的所有字节。另外,您是否已将浏览器配置为将您的程序用作 SOCKS 代理?如果是这样,您需要实现 SOCKS 协议才能使浏览器正常工作。
  • 2.笔记。注意|和 & 运算符运算符优先级 fds[1].revents & POLLIN | POLLPRI (fds[1].revents & POLLIN) | POLLPRI 相同,您需要 fds[1].revents & (POLLIN | POLLPRI) (或者按照 caf 的建议删除 POLLPRI
  • 显然是firefox的一个bug,chrome和其他socks应用都能正常工作。关于优先级的好点不知道,谢谢。

标签: c sockets nonblocking


【解决方案1】:

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

您不需要将POLLPRI 用于 SOCKS / HTTP 连接 - 它表示 TCP“紧急数据”,这些协议不使用(或者实际上,根本不使用)。


除了你的直接问题,你还需要做更多的事情来实现一个可靠的代理:

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

【讨论】:

  • 感谢您的信息,我添加了 if (recvMsgSize == 0) break;和 sndMsgSize 一样,现在就像是连接关闭的魅力。
  • 非常感谢您的意见,我显然知道我的“代理”并不完全是生产价值,它只是一个让我在学习 C 方面搞砸的项目。我将尝试执行你所说的,但我想我必须先做一些阅读。从好的方面来说,浏览器无法使用 socks 代理似乎是一个 firefox 错误,因为 chrome 和任何其他支持 socks 的应用程序都可以工作。
  • @user912877:我认为 Firefox 很有可能因为第 5 点而无法使用它。
  • firefox 在我根本没有关闭套接字的代理版本中不起作用。我检查了wireshark中的数据,它确实得到了1:1的副本。当然,您是对的,我目前执行 close() 的方式非常糟糕。我认为我现在这样做的方式最终可能会让服务器发送一些东西,关闭连接,我的代理也会在传递数据之前终止客户端连接。 (超出我应该做的事情:-))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多