【问题标题】:Client application crash causes Server to crash? (C++)客户端应用程序崩溃导致服务器崩溃? (C++)
【发布时间】:2011-01-07 09:03:12
【问题描述】:

我不确定这是否是我遇到的已知问题,但我找不到可以为我提供任何有用结果的好的搜索字符串。 无论如何,这是基本的纲要:

我们有一个相对简单的应用程序,它从源(数据库或文件)获取数据,并在新数据进入时通过 TCP 将数据流式传输到连接的客户端。它的客户端数量相对较少;我会说每台服务器最多有 10 个客户端,所以我们有以下粗略的设计:

客户端:连接到服务器,设置为读取(超时设置为高于服务器心跳消息频率)。它在读取时阻塞。

服务器:一个监听线程,它接受连接,然后产生一个写入线程来从数据源读取数据并写入客户端。编写器线程也被分离(使用 boost::thread 所以只需调用 .detach() 函数)。它会无限期地阻止写入,但会在写入之前检查 errno 是否有错误。我们使用单个 perl 脚本启动服务器,并为每个服务器进程调用“fork”。

问题: 在看似随机的时间,客户端将关闭并显示“连接终止(成功)”,表明远程服务器故意关闭了套接字。但是,当这种情况发生时,服务器应用程序也会关闭,没有任何错误或任何东西。它只是崩溃。

现在,为了进一步解决问题,我们有多个服务器应用程序实例由运行不同文件和不同端口的启动脚本启动。当其中一台服务器像这样崩溃时,所有服务器都崩溃了。

服务器和客户端都使用内部创建的相同“连接”库。它主要是 C 套接字调用的 C++ 包装器。

这里是连接库中写入和读取功能的一些粗略代码:

int connectionTimeout_read = 60 * 60 * 1000; 
int Socket::readUntil(char* buf, int amount) const
    {
        int readyFds = epoll_wait(epfd,epEvents,1,connectionTimeout_read);
        if(readyFds < 0)
        {
            status = convertFlagToStatus(errno);
            return 0;
        }
        if(readyFds == 0)
        {
            status = CONNECTION_TIMEOUT;
            return 0;
        }
        int fd = epEvents[0].data.fd;
        if( fd != socket)
        {
            status = CONNECTION_INCORRECT_SOCKET;
            return 0;
        }
        int rec = recv(fd,buf,amount,MSG_WAITALL);

        if(rec == 0)
            status = CONNECTION_CLOSED;
        else if(rec < 0)
            status = convertFlagToStatus(errno);
        else
            status = CONNECTION_NORMAL;
        lastReadBytes = rec;
        return rec;

    }



int Socket::write(const void* buf, int size) const
    {

        int readyFds = epoll_wait(epfd,epEvents,1,-1);
        if(readyFds < 0)
        {
            status = convertFlagToStatus(errno);
            return 0;
        }
        if(readyFds == 0)
        {
            status = CONNECTION_TERMINATED;
            return 0;
        }
        int fd = epEvents[0].data.fd;
        if(fd != socket)
        {
            status = CONNECTION_INCORRECT_SOCKET;
            return 0;
        }
        if(epEvents[0].events != EPOLLOUT)
        {
            status = CONNECTION_CLOSED;
            return 0;
        }
        int bytesWrote = ::send(socket, buf, size,0);
        if(bytesWrote < 0)
            status = convertFlagToStatus(errno);
        lastWriteBytes = bytesWrote;
        return bytesWrote;

    }

任何帮助解决这个神秘的错误都会很棒!至少,我希望它不会让服务器崩溃,即使客户端崩溃(这对我来说真的很奇怪,因为没有双向通信)。

另外,作为参考,这里是服务器监听代码:

while(server.getStatus() == connection::CONNECTION_NORMAL)
        {
            connection::Socket s = server.listen();
                if(s.getStatus() != connection::CONNECTION_NORMAL)
                {
                    fprintf(stdout,"failed to accept a socket. error: %s\n",connection::getStatusString(s.getStatus()));
                }

                DATASOURCE* dataSource;
                dataSource = open_datasource(XXXX);  /* edited */               if(dataSource == NULL)
                {
                    fprintf(stdout,"FATAL ERROR. DATASOURCE NOT FOUND\n");
                    return;
                }
                    boost::thread fileSender(Sender(s,dataSource));
                    fileSender.detach();


        }

...这里还有生成的子发送线程:

::signal(SIGPIPE,SIG_IGN);

    //const int headerNeeds = 29;
    const int BUFFERSIZE = 2000;
    char buf[BUFFERSIZE];

    bool running = true;
    while(running)
    {
             memset(buf,'\0',BUFFERSIZE*sizeof(char));
        unsigned int readBytes = 0;
        while((readBytes = read_datasource(buf,sizeof(unsigned char),BUFFERSIZE,dataSource)) == 0)
        {
            boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
        }
        socket.write(buf,readBytes);
        if(socket.getStatus() != connection::CONNECTION_NORMAL)
            running = false;

    }
    fprintf(stdout,"socket error: %s\n",connection::getStatusString(socket.getStatus()));
    socket.close();
    fprintf(stdout,"sender exiting...\n");

欢迎任何见解!提前致谢。

【问题讨论】:

  • @Nik 如果您希望更多人帮助您,您应该接受之前问题的一些答案。 0% 会推迟很多潜在的追求者。
  • 什么操作系统?有任何核心转储吗? (去钓鱼)
  • @chrisaycock 刚刚接受了之前的帖子,谢谢。
  • @bmargulies 这是 uname -a 打印的内容:Linux krma01d 2.6.18-128.el5 #1 SMP Wed Jan 21 10:44:23 EST 2009 i686 i686 i386 GNU/Linux no core dumps, i我还用 try / catch 块包装实际代码,至少看看我是否得到任何东西,但到目前为止什么都没有......
  • 在 gdb 中运行服务器并等待开发,或者确保 coresize 的 ulimit 不为零,然后查看是否获得核心转储。

标签: c++ sockets client


【解决方案1】:

你可能把所有的东西都弄反了......当服务器崩溃时,操作系统将关闭所有套接字。所以服务器崩溃首先发生并导致客户端获取断开消息(实际上是 TCP 段中的 FIN 标志),崩溃不是套接字关闭的结果。

由于您有多个服务器进程同时崩溃,我会查看它们共享的资源,以及所有服务器会尝试同时执行的任何计划任务。

编辑:您没有一个客户端连接到多个服务器,是吗?请注意,TCP 连接始终是双向的,因此如果客户端断开连接,服务器进程会收到反馈。一些互联网提供商甚至被发现在未通过某些可疑流量测试的连接上生成 RST 数据包。

编写一个信号处理程序。确保它仅使用原始 I/O 函数来记录问题(打开、写入、关闭,而不是 fwrite,而不是 printf)。

检查返回值。在套接字上检查来自write 的负返回值,但检查所有返回值。

【讨论】:

  • 感谢您的快速发帖! ATM 他们似乎共享的唯一资源是 DataSource 本身,但所发生的只是 fread 或 DB poll。对于文件,文件本身也由一个单独的进程附加,该进程不通过套接字发送数据;纯粹的数据接收然后输出到文件。将进一步研究服务器代码,看看我是否能找到任何东西。
  • 我之前也遇到过类似的问题,但后来发现问题略有不同。这就是过去发生的事情:早些时候,写函数实际上在 epoll_wait 上有一个超时用于写。由于客户端应用程序中发生了一些处理,套接字不会在超时前“准备好”,从而导致崩溃。修复是将写入的超时设置为不确定(暂时),它似乎已经解决了。
  • 参考您的编辑:不,单个客户端只能连接到单个服务器。在 tcp 级别,我知道有来回通信,但在应用程序级别没有任何客户端到服务器通信;它都是单向的。我正在使用 send() atm,它应该和 write() 一样吗?有什么区别吗?
  • @Nik:send 或 write 都可用于在 TCP 套接字上传输,主要区别在于 write 没有 flags 参数(无论如何它几乎总是为零)。您还应该检查来自send 的返回值,不仅可以检测部分发送,还可以检测错误。
  • 好吧,我就是这么想的。当前代码通过查看是否返回 -1 来检查错误代码,但我们不检查部分写入。从我在互联网上可以找到的内容来看, send() 将阻止部分写入,直到可以写入全部数量,并且仅在本地错误时返回 -1。因此,我们不处理部分写入,但这可能是我存在严重缺陷的理解。如果我对此有误,请告诉我。谢谢
【解决方案2】:

感谢所有 cmets 和建议。 在查看代码并按照 Ben 的建议添加信号处理之后,应用程序本身更加稳定。感谢您的所有意见。

然而,最初的问题是由于其中一位管理员以 root 身份运行的恶意脚本会随机杀死服务器端机器上的某些进程(我不会深入了解它试图在现实;可以肯定地说这是错误的)。 经验教训:检查环境。

谢谢大家的建议。

【讨论】:

    猜你喜欢
    • 2013-03-11
    • 1970-01-01
    • 1970-01-01
    • 2013-01-16
    • 2020-10-04
    • 2017-04-02
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    相关资源
    最近更新 更多