在多线程 HTTP 服务器中发送后,如何干净地关闭套接字?

Posted

技术标签:

【中文标题】在多线程 HTTP 服务器中发送后,如何干净地关闭套接字?【英文标题】:How do I close a socket cleanly after sending in a multithreaded HTTP server? 【发布时间】:2014-03-02 14:11:31 【问题描述】:

我继承了一个 Windows C/C++ 代码 TCP/IP 服务器来维护,以前的程序员定义了一个额外的端口用于基本的 HTTP 通信。服务器使用 Windows 套接字库 2,通过生成新线程来发送请求来处理请求。问题似乎是对包含多个项目的 html 页面的 HTTP 请求似乎无法完全加载页面,这意味着客户端在 Web 浏览器中获得了一个无休止的旋转图标。

我发现问题在于客户端(例如 Chrome v33)在同一个端口上触发了多个 GET 请求,这意味着服务器正在启动新线程以在同一个套接字上处理这些请求。也就是说,accept() 返回的套接字(即连接套接字,而不是监听套接字)然后被传递给 CreateThread() 用户函数以处理 GET 请求。但是,第一个启动的线程在完成发送时关闭此套接字,这意味着其他线程在轮到它们发送时发现该套接字已关闭。我尝试不关闭套接字,效果更好并加载了更多页面,但仍然没有完全完成页面加载,大概是因为没有服务器线程关闭套接字以让客户端知道网页已完成。

所以我的问题是,这种情况应该如何由多线程 HTTP 服务器处理?在同一个套接字上进行多个线程通信是严格禁忌还是不建议?现代 Web 浏览器会对页面项目发出多个并发(而不是顺序)请求似乎是有道理的,但是对于服务器来说,在单独的线程中处理这些请求而不是在一个线程中顺序处理这些请求似乎也很有意义线。每个请求都必须以某种方式打开自己的套接字 - 可能是通过在新线程中调用 accept() 吗?

这通常是通过关闭套接字的超时来解决的,还是通过活动计时器或类似的?还是使用 HTTP v1.0 禁用持久连接更好?或者是否有另一种方法可以知道所有网页元素何时已发送并关闭服务器套接字?这是套接字关闭代码,以防我错过了其他一些可以神奇地处理持久连接的winsock2选项:

bool ShutdownConnection
(
CONFIG      *configSP,
SOCKET sd
)

    if ( shutdown(sd, SD_SEND) == SOCKET_ERROR)
    
        return false;
    
    char readBuf[bufSize];

    while (1)
    
        int newBytes = recv(sd, readBuf, bufSize, 0);

        if (newBytes == SOCKET_ERROR)
        
            return false;
        
        else
        
            break;
        
    
    if (closesocket(sd) == SOCKET_ERROR)
    
        return false;
    
    return true;

【问题讨论】:

您应该关闭从接受返回的套接字,您确定您没有关闭(原始)监听套接字吗?我们错过了一些背景。 这里似乎有一个混淆的主题。套接字是一个通信端点,这意味着对于客户端到服务器的每个连接,服务器必须接受一个不同的套接字。看起来您可能遇到了 HTTP/1.1 持久连接的问题,这意味着多个 请求 在同一个 连接 上发送。这是通过使用Connection HTTP 标头来控制的。 你是关闭监听的socket,还是已经接受的socket? while (1) 是干什么用的? 对不起,伙计们,应该澄清我创建了一个监听套接字和一个绑定()到的端口,但我从不关闭监听套接字。我正在谈论并关闭(或不关闭)的套接字是从 accept() 返回的那个。 【参考方案1】:

我发现问题在于客户端(例如 Chrome v33)在同一个端口上触发了多个 GET 请求,这意味着服务器正在启动新线程以在同一个套接字上处理这些请求。

不,它没有。每个accept() 返回一个新的套接字。

即accept()返回的socket(即连接socket,不是监听socket)然后传递给CreateThread()用户函数处理GET请求。

正确,每次都是一个新的套接字。你已经自相矛盾了。

但是,第一个启动的线程在完成发送后关闭此套接字,这意味着其他线程在轮到它们发送时发现该套接字已关闭。

没有。这些线程都有自己的套接字。除非您有关闭错误套接字的编码错误,或者以某种方式将它们混淆,例如通过不正确的变量范围。

我试过不关闭套接字

关闭套接字不是“实验”,而是要求

这效果更好,加载了更多页面,但仍然没有完全完成页面加载,大概是因为没有服务器线程关闭套接字以让客户端知道网页已完成。

没有。您的复制循环可能不正确,或者您可能混淆了套接字,或者您可能没有完全独立的线程,或者您有其他一些编码错误。

所以我的问题是,这种情况应该如何由多线程 HTTP 服务器处理?

您描述的情况不可能出现在正确编写的服务器中。

多个线程在同一个套接字上通信是严格禁忌还是不建议?

这应该是不可能的。如果你有它,你的代码中有一个错误。

现代 Web 浏览器对页面项目发出多个并发(而不是顺序)请求似乎是有道理的,但是对于效率而言,服务器也在单独的线程中处理这些请求而不是处理这些请求似乎也是有意义的在一个线程中按顺序排列。

他们必须同时处理它们。

每个请求都必须以某种方式打开自己的套接字 - 可能是通过在新线程中调用 accept() 吗?

您应该已经调用accept() 来获取新的入站套接字之前您启动了一个线程来处理它。这没有任何意义。

这通常是通过关闭套接字的超时解决,还是通过活动计时器或类似方法解决?

没有。

还是使用 HTTP v1.0 禁用持久连接更好?

不必要且不合格。

或者是否有其他方法可以知道所有网页元素何时已发送并关闭服务器套接字?

你不需要知道。有多个套接字,它们应该在各自的适当时间关闭。不可能知道你在这里实际上在说什么。

这是套接字关闭代码,以防我错过了其他一些可以神奇地处理持久连接的 winsock2 选项:

您不需要读取循环或关机。关闭它。这来自 MSDN 文章(并且您没有正确复制)是关于在两端实现 同步 关闭,这在 HTTP 中不是必需的。它被广泛误解为所有 TCP 连接的要求。不是。

你在这里完全找错了树。我建议您发布一些有关评估的实际代码。

【讨论】:

【参考方案2】:

尚未完全解决此问题,但最简单的解决方法是切换回 HTTP 通信的单线程并在每条消息后关闭套接字。

我在网上没有找到太多关于此的信息,但Winsock Programmer's Guide 上的常见问题解答 3.10 建议在多个线程中的同一套接字上使用 send() 是一个坏主意,因为可能会交错发送数据。

因此,如果有人对实际实现多线程 HTTP v1.1 服务器感兴趣,我想他们需要实现一个消息队列和一个专用线程,以便在同一个套接字上执行顺序 send() 调用并使用“连接:保持-活”。我想如果你这样做,那么你可以在最后一条消息和超时时间之后关闭套接字,并在最后一个 HTTP 标头中发送“连接:关闭”,但最好让套接字随时打开以恢复.

实现具有多线程的消息队列可能比我的单线程解决方案更有效,但是对于我的低带宽需求,这种额外的代码复杂性是不值得的。

【讨论】:

如果您真的在多个线程中的同一个套接字上调用send(),那么您的代码中有一个重大错误。您当然不需要 HTTP 服务器内的消息队列。您还没有正确诊断问题,更不用说找到解决方案了。

以上是关于在多线程 HTTP 服务器中发送后,如何干净地关闭套接字?的主要内容,如果未能解决你的问题,请参考以下文章

线程 python 应用程序没有干净地关闭

在多线程中显示模态窗口,出现异常现象

Boost asio程序在多线程上崩溃

如何在多线程程序中传递或共享开放流引用?

如何在多线程环境中有效地使用RestTemplate?

在多线程程序中使用 Executer 服务