手机版
你好,游客 登录 注册
背景:
阅读新闻

服务器后台TCP连接存活问题

[日期:2015-09-08] 来源:Linux社区  作者:jabnih [字体: ]

0. 背景

  公司的服务器后台部署在某一个地方,接入的是用户的APP,而该地方的网络信号较差,导致了服务器后台在运行一段时间后用户无法接入,那边的同事反馈使用netstat查看系统,存在较多的TCP连接。

1. 问题分析

  首先在公司内部测试服务器上部署,使用LoadRunner做压力测试,能正常运行,然后那边的同事反馈该地方信号较差。考虑到接入的问题,有可能接入进程的FD资源耗尽,导致accept失败。推论的依据是对于TCP连接来说,如果客户端那边由于一些异常情况导致断网而未能向服务器发起FIN关闭消息,服务端这边若没有设置存活检测的话,该连接会存在(存活时间暂未测)。

2. 实验测试

  这里简单地写了一个服务端的程序,主要功能是回应,即接受一个报文(格式:2Byte报文长度+报文内容),然后原封不动将报文内容发回客户端。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

int g_epfd;

int InitServer( unsigned short port )
{
    int nServerFd = socket( AF_INET, SOCK_STREAM, 0 );

    struct sockaddr_in addr;
    memset( &addr, 0, sizeof(addr) );

    addr.sin_family = AF_INET;
    addr.sin_port = htons( port );
    addr.sin_addr.s_addr = 0;

    if ( bind( nServerFd, (struct sockaddr *)&addr, sizeof(addr) ) <0 )
    {
        printf("bind error\n");
        exit(-1);
    }

    if ( listen( nServerFd, 128 ) < 0 )
    {
        printf("listen error\n");
        exit(-1);
    }

    return nServerFd;
}

int AddFd( int epfd, int nFd , int nOneShot)
{
    struct epoll_event event;
    memset( &event, 0, sizeof( event) );

    event.data.fd = nFd;
    event.events |= EPOLLIN | EPOLLRDHUP | EPOLLET;

    if ( nOneShot ) event.events |= EPOLLONESHOT;

    return epoll_ctl( epfd, EPOLL_CTL_ADD, nFd, &event );
}

int ResetOneShot( int epfd, int nFd )
{
    struct epoll_event event;
    memset( &event, 0, sizeof(event) );

    event.data.fd = nFd;
    event.events |= EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;

    return epoll_ctl( epfd, EPOLL_CTL_MOD, nFd, &event);
}

void * ReadFromClient( void * arg )
{
    int nClientFd = (int)arg;
    unsigned char buf[1024];
    const int nBufSize = sizeof( buf );
    int nRead;
    int nTotal;
    int nDataLen;

    printf("ReadFromClient Enter\n");

    if ( (nRead = read( nClientFd, buf, 2 )) != 2 )
    {
        printf("Read Data Len error\n");
        pthread_exit(NULL);
    }

    nDataLen = *(unsigned short *)buf;
    printf("nDataLen [%d]\n", nDataLen);
    nDataLen = buf[0]*256 + buf[1];
    printf("nDataLen [%d]\n", nDataLen);

    nRead = 0;
    nTotal = 0;
    while( 1 )
    {
        nRead = read( nClientFd, buf + nRead, nBufSize );
        if ( nRead < 0 )
        {
            printf("Read Data error\n");
            pthread_exit( NULL );
        }
        nTotal += nRead;
        if ( nTotal >= nDataLen )
        {
            break;
        }
    }
    printf("nTotal [%d]\n", nTotal);

    sleep(5);

    int nWrite = write( nClientFd, buf, nTotal );
    printf("nWrite[%d]\n", nWrite);

    printf("Not Write ResetOneShot [%d]\n", ResetOneShot(g_epfd, nClientFd));

    return NULL;
}

int main(int argc, char const *argv[])
{
    int i;
    int nClientFd;
    pthread_t tid;
    struct epoll_event events[1024];

    int nServerFd = InitServer( 7777 );
    if ( nServerFd < 0 )
    {
        perror( "nServerFd" );
        exit(-1);
    }

    int epfd = epoll_create( 1024 );

    g_epfd = epfd;

    int nReadyNums;

    if ( AddFd( epfd, nServerFd, 0 ) < 0 )
    {
        printf("AddFd error\n");
        exit(-1);
    }

    while( 1 )
    {
        nReadyNums = epoll_wait( epfd, events, 1024, -1 );

        if ( nReadyNums < 0 )
        {
            printf("epoll_wait error\n");
            exit(-1);
        }

        for ( i = 0; i <  nReadyNums; ++i)
        {
            if ( events[i].data.fd == nServerFd )
            {
                nClientFd = accept( nServerFd, NULL, NULL );

                AddFd( epfd, nClientFd, 1 );

            }else if ( events[i].events & EPOLLIN )
            {
                // Can be implemented by threadpool
                //Read data from client
                pthread_create( &tid, NULL, ReadFromClient, (void *)(events[i].data.fd) );

            }else if ( events[i].events & EPOLLRDHUP )
            {
                //Close By Peer
                printf("Close By Peer\n");
                close( events[i].data.fd );
            }else
            {
                printf("Some thing happened\n");
            }

        }
    }

    return 0;
}

测试内容:

注:客户端IP: 192.168.10.108  服务器IP&Port: 192.168.10.110:7777

 

a. 客户端发送一个报文至服务端,然后断网。(这里对程序做了点改动,这次实验注释了write响应,防止write影响测试,后面一个实验会使用write)。

   客户端断网后,使用netstat查看网络连接状态发送客户端与服务端还处于established状态,如图所示。

a. 实验结果

  服务端没有检测到客户端断网,依然处于连接状态。

 

b. 客户端发送一个报文至服务端,然后断网,关闭客户端,再重复一次。

  这次试验测试重新联网,程序再次建立Socket连接是否会导致之前的连接被检测到。

b. 实验结论:

  重新联网,程序再次建立Socket连接之前的连接不会被检测到。

 

c. 客户端发送一个报文至服务端,然后断网。(这次实验使用了write响应,查看write后的结果)。

  这里查看到Write居然成功了,成功了....。

c. 实验结论:

  这次使用write不会检测对端是否已经断了。

 

3. 解决方案

  临时:使用TCP的选项SO_KEEPALIVE检测客户端是否已异常掉了(setsockopt)。

  后续改进:使用心跳包来检测长连接存活问题。

注:SO_KEEPALIVE明天再补充,回家了,只有一台笔记本直接装了Ubuntu,没装虚拟机,伤不起。

 

4. 补充

  如果什么不对的或者建议直接说,多讨论讨论比较好。

本文永久更新链接地址http://www.linuxidc.com/Linux/2015-09/122776.htm

linux
相关资讯       TCP  服务器后台TCP连接 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款