服务器后台TCP连接存活问题
2015-08-17 23:37
573 查看
0. 背景
公司的服务器后台部署在某一个地方,接入的是用户的APP,而该地方的网络信号较差,导致了服务器后台在运行一段时间后用户无法接入,那边的同事反馈使用netstat查看系统,存在较多的TCP连接。
1. 问题分析
首先在公司内部测试服务器上部署,使用LoadRunner做压力测试,能正常运行,然后那边的同事反馈该地方信号较差。考虑到接入的问题,有可能接入进程的FD资源耗尽,导致accept失败。推论的依据是对于TCP连接来说,如果客户端那边由于一些异常情况导致断网而未能向服务器发起FIN关闭消息,服务端这边若没有设置存活检测的话,该连接会存在(存活时间暂未测)。
2. 实验测试
这里简单地写了一个服务端的程序,主要功能是回应,即接受一个报文(格式:2Byte报文长度+报文内容),然后原封不动将报文内容发回客户端。
测试内容:
注:客户端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. 补充
如果什么不对的或者建议直接说,多讨论讨论比较好。
公司的服务器后台部署在某一个地方,接入的是用户的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. 补充
如果什么不对的或者建议直接说,多讨论讨论比较好。
相关文章推荐
- 常用的网络开发库
- TCP相关知识
- socket 网络编程快速入门(一)教你编写基于UDP/TCP的服务(客户端)通信
- iOS开发网络篇-JSON文件的解析
- NS3网络仿真(12): ICMPv4协议
- iOS 9 适配系列教程 --https
- 39.网络编程
- centos在没有网络连接的情况下安装rpm包
- iOS应用架构谈 网络层设计方案
- 面向对象的方式进行数据交换网络之间的差异
- 网络爬虫源码
- java网络编程基础
- 监听手机网络状态变化
- fatal: could not read Username for 'https://github.com': No such file or directo
- 网络请求数据
- 解析数据与网络请求
- Linux环境下的TCP/IP通信
- PHP—— 跨域 HTTP 请求
- 谷歌语音转录背后的神经网络
- 打造安全的App!iOS安全系列之 HTTPS