2

我有一个启用了 http-keepalive 的多线程 gSOAP 服务。当仍有客户端连接时,如何优雅地关闭服务?

gSoap中提出了一个类似的问题:如何优雅地关闭 Web 服务应用程序?,但答案不包括 http-keepalive 方面:soap-serve 函数将不会返回,直到客户端没有关闭 http-keepalive-session。因此,接受的答案中的第 2 步将阻塞,直到客户端决定关闭连接(或接收超时到期,但短暂的超时会破坏此处所需的 http-keepalive 行为)。

gSOAP 文档中的示例遇到了同样的问题。

到目前为止,我尝试的是为挂在主线程的soap_serve调用中的所有soap结构调用soap_done(),以中断等待http-keepalive的连接,这在大多数情况下都有效,但在极少数情况下会崩溃(a可能是比赛条件),所以这对我来说不是解决方案。

4

2 回答 2

1

我刚刚遇到了同样的问题,我想我已经为你找到了解决方案。

正如您刚才所说,问题在于 gSoap 挂在 soap_serve 上。发生这种情况是因为 gSOAP 为您生成了一个内部循环,该循环等待所有保持活动请求的到达或服务器端出现超时。

我所做的是在自动生成的服务存根中获取soap_serve 函数。我将列出原始的 soap_serve 函数,以便您可以在服务存根文件中找到它:

  SOAP_FMAC5 int SOAP_FMAC6 soap_serve(struct soap *soap)
    {
    #ifndef WITH_FASTCGI
        unsigned int k = soap->max_keep_alive;
    #endif

        do
        {
    #ifdef WITH_FASTCGI
            if (FCGI_Accept() < 0)
            {
                soap->error = SOAP_EOF;
                return soap_send_fault(soap);
            }
    #endif

            soap_begin(soap);

    #ifndef WITH_FASTCGI
            if (soap->max_keep_alive > 0 && !--k)
                soap->keep_alive = 0;
    #endif

            if (soap_begin_recv(soap))
            {   if (soap->error < SOAP_STOP)
                {
    #ifdef WITH_FASTCGI
                    soap_send_fault(soap);
    #else 
                    return soap_send_fault(soap);
    #endif
                }
                soap_closesock(soap);

                continue;
            }

            if (soap_envelope_begin_in(soap)
             || soap_recv_header(soap)
             || soap_body_begin_in(soap)
             || soap_serve_request(soap)
             || (soap->fserveloop && soap->fserveloop(soap)))
            {
    #ifdef WITH_FASTCGI
                soap_send_fault(soap);
    #else
                return soap_send_fault(soap);
    #endif
            }

    #ifdef WITH_FASTCGI
            soap_destroy(soap);
            soap_end(soap);
        } while (1);
    #else
        } while (soap->keep_alive);
    #endif
        return SOAP_OK;
    }

您应该提取此函数的主体,并用以下内容替换您的线程(执行请求并因保持活动而暂停的线程)内的旧 soap_serve(mySoap) 调用:

do
    {
        if ( Server::mustShutdown() ) {
            break;
        }

        soap_begin(mySoap);

        // If we reached the max_keep_alive we'll exit
        if (mySoap->max_keep_alive > 0 && !--k)
            mySoap->keep_alive = 0;


        if (soap_begin_recv(mySoap))
        {   if (mySoap->error < SOAP_STOP)
            {
                soap_send_fault(mySoap);
                break;
            }
            soap_closesock(mySoap);

            continue;
        }

        if (soap_envelope_begin_in(mySoap)
         || soap_recv_header(mySoap)
         || soap_body_begin_in(mySoap)
         || soap_serve_request(mySoap)
         || (mySoap->fserveloop && mParm_Soap->fserveloop(mySoap)))
        {
            soap_send_fault(mySoap);
            break;
        }


    } while (mySoap->keep_alive);

请注意以下事项:

  1. Server::mustShutdown() 充当一个标志,将设置为 true(外部)以结束所有线程。当您想阻止服务器处理新请求时,此函数将返回 true 并且循环将结束。
  2. 我已经删除了 ifdef,WITH_FASTCGI 它现在对我们来说并不有趣。
  3. 当您像这样关闭连接时,任何连接到服务器的客户端都会引发异常。例如,用 C# 编写的客户端会抛出“底层连接被服务器关闭以保持活动状态”,这对我们来说非常有意义。

但是我们还没有完成,感谢AudioComplex 指出的,系统仍然在等待soap_begin_recv 上的reqeuests。但我也有解决方案;)

连接处理池上的每个线程都创建主soap上下文的副本(通过soap_copy),这些线程是
我将这些上下文中的每一个存储为驻留在主连接处理线程上的数组上的元素的线程. 当终止主连接处理线程(为请求提供服务的线程)时,它将遍历所有soap上下文并使用以下方法“手动”完成连接:

for (int i = 0; i < soaps.size(); ++i) {
  soaps[i]->fclose(soaps[i]);
}

这将强制soap_serve 循环完成。它实际上会在 stdsoap2.cpp_ 的第 921 行附近停止内部循环

r = select((int)soap->socket + 1, &fd, NULL, &fd, &timeout);

这不是最干净的解决方案(还没有找到更干净的解决方案),但它肯定会停止服务。

于 2011-11-09T11:05:17.870 回答
0

你不需要改变soap_serve中的循环,只需在你的服务实现中返回一些错误代码:

return Server::mustShutdown() ? SOAP_SVR_FAULT : SOAP_OK;
于 2021-07-28T05:37:16.710 回答