【问题标题】:Socket read() hangs for a while when there is no data to read没有数据要读取时,socket read() 会挂起一段时间
【发布时间】:2011-02-23 05:06:08
【问题描述】:

嗨,我正在编写一个简单的 http 端口转发器。我从端口 80 读取数据,并将数据传递到我的 lighttpd 服务器,端口为 8080。

只要我在端口 8080 上的套接字上 write() 数据(转发请求)就没有问题,但是当我从该套接字读取()数据(转发响应)时,最后一个 read() 挂了很多(大约 1 或 2 秒)在意识到没有更多数据并返回 0 之前。

我尝试将套接字设置为非阻塞,但这不起作用,因为有时它会返回 EWOULDBLOCKING,即使还剩下一些数据(lighttpd + cgi 可能会很慢)。 我尝试使用 select() 设置超时,但是,如上所述,当实际上有一些数据要传输时,慢速 cgi 可能会使套接字超时。


更新:已解决。毕竟是keepalive。在我的 lighttpd 配置文件中禁用它后,整个事情运行完美。

【问题讨论】:

  • 会不会是 Keep-Alive 的 HTTP 标头导致实际的 Web 服务器保持持久连接?
  • 是的,当然可以。但是为什么 thre read() 没有立即意识到没有要读取的内容,而是等待 1/2 秒?
  • 因为它在等待。实际的 Web 服务器仍然有一个打开的套接字,因此如果 Web 服务器要发送更多字节,read 可能仍然会成功。如果 Web 服务器关闭其套接字,read 可能会立即返回。
  • 好的,但是为什么这个问题只发生在我的服务器上?我的意思是,浏览器与 lighttpd 完美配合,而在我的服务器上,它等待了很多时间。
  • 因为浏览器知道如何处理 Keep-Alive?

标签: c linux unix sockets


【解决方案1】:

好吧,为了完成,根据我的评论:

很可能 HTTP 服务器本身(在您的情况下为 lighttpd)正在维护与您的代理的持久连接,因为您的代理中继了一个包含“Connection: keep-alive”的标头。当客户端想要通过同一连接发出多个请求时,此标头会有所帮助。因此,因为 lighttpd 收到了这个标头,它假定它将接收更多请求并保持套接字打开,导致 read 在您的代理中阻塞。

在您的 lighttpd 配置中禁用 keep-alive 是解决此问题的一种方法,但您也可以在将标头中的“Connection: keep-alive”中继到 Web 服务器之前将其删除。

【讨论】:

    【解决方案2】:

    使用 both 非阻塞套接字 select 是正确的方法。返回 EWLOULDBLOCK 并不意味着整个数据流都已完成接收,这意味着瞬间没有任何内容可读取。这正是您想要的,因为这意味着read 甚至不会等待半秒来显示更多数据。如果数据不能立即可用,它将返回。

    现在,很明显,这意味着您需要多次调用read 才能获得完整的数据。执行此操作的一般格式是选择循环。在伪代码中:

    do
      select ( my_sockets )
    
      if ( select error ) 
        handle_error
      else
        for each ( socket in my_sockets ) do
          if ( socket is ready ) then
            nonblocking read from socket
            if ( no data was read ) then
              close socket
              remove socket from my_sockets
            endif
          endif
        loop
      endif
    loop
    

    这个想法是select 会告诉你哪些套接字有数据可供读取现在。如果您读取其中一个套接字,则保证您要么获取数据,要么获取返回值 0,表示远程端关闭了套接字。

    如果您使用此方法,您将永远不会被困在不读取数据的read 调用中,无论多长时间。阻塞操作是select调用,如果需要写也可以选择overwriteable sockets,如果需要定期做事可以设置超时。

    【讨论】:

    • 是的,但是如果没有更多数据,这会在 select() 调用上阻塞,就像它在 read() 上阻塞一样。
    • 好吧,除了等待数据,你还想做什么?如果要写入数据,请将写入描述符也放入 select 调用中。如果您想定期做某事,请给select 一个超时时间。如果您想连续做某事,请将其他事情作为您的主循环,然后在您有时间的时候调用select 序列。如果您想并行执行某些操作,请为 I/O 创建一个新线程。
    【解决方案3】:

    不要那样做!

    Keepalive 可提高其他客户端的性能。相反,修复你的客户。在您的客户端中发送 Connection: close 标头,并确保您的请求未声明 HTTP/1.1 合规性。 (如果没有其他原因,您可能也不处理分块编码。)

    【讨论】:

      【解决方案4】:

      我想我会使用非阻塞 I/O 来完全扩展。而不是设置超时,我宁愿等待事件:

      while(select(...)) {
          switch(...) {
          case ...: // Handle accepting new connection
          case ...: // Handle reading from socket
          ...
          }
      }
      

      单线程阻塞转发器无论如何都会导致多个客户端出现问题。

      抱歉 - 我不记得确切的电话。在某些情况下也可能很奇怪(IIRC - 你需要处理写入),但有一些库可以简化任务。

      【讨论】:

      • 是的,但它会阻塞 select()!转发器不是单线程的。我为每个请求运行一个线程,但浏览器会在发出另一个请求后等待请求结束。因此,每个请求它将等待 1 秒以上,等待转发器(卡在选择中)关闭套接字。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-19
      • 2015-09-27
      • 2013-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多