程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 服務器後台TCP連接存活問題,服務器後台tcp存活

服務器後台TCP連接存活問題,服務器後台tcp存活

編輯:關於C語言

服務器後台TCP連接存活問題,服務器後台tcp存活


0. 背景

  公司的服務器後台部署在某一個地方,接入的是用戶的APP,而該地方的網絡信號較差,導致了服務器後台在運行一段時間後用戶無法接入,那邊的同事反饋使用netstat查看系統,存在較多的TCP連接。

1. 問題分析

  首先在公司內部測試服務器上部署,使用LoadRunner做壓力測試,能正常運行,然後那邊的同事反饋該地方信號較差。考慮到接入的問題,有可能接入進程的FD資源耗盡,導致accept失敗。推論的依據是對於TCP連接來說,如果客戶端那邊由於一些異常情況導致斷網而未能向服務器發起FIN關閉消息,服務端這邊若沒有設置存活檢測的話,該連接會存在(存活時間暫未測)。

2. 實驗測試

  這裡簡單地寫了一個服務端的程序,主要功能是回應,即接受一個報文(格式:2Byte報文長度+報文內容),然後原封不動將報文內容發回客戶端。

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <sys/epoll.h>
  5 #include <unistd.h>
  6 #include <pthread.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9 #include <arpa/inet.h>
 10 
 11 int g_epfd;
 12 
 13 int InitServer( unsigned short port )
 14 {
 15     int nServerFd = socket( AF_INET, SOCK_STREAM, 0 );
 16 
 17     struct sockaddr_in addr;
 18     memset( &addr, 0, sizeof(addr) );
 19 
 20     addr.sin_family = AF_INET;
 21     addr.sin_port = htons( port );
 22     addr.sin_addr.s_addr = 0;
 23 
 24     if ( bind( nServerFd, (struct sockaddr *)&addr, sizeof(addr) ) <0 )
 25     {
 26         printf("bind error\n");
 27         exit(-1);
 28     }
 29 
 30     if ( listen( nServerFd, 128 ) < 0 )
 31     {
 32         printf("listen error\n");
 33         exit(-1);
 34     }
 35 
 36     return nServerFd;
 37 }
 38 
 39 int AddFd( int epfd, int nFd , int nOneShot)
 40 {
 41     struct epoll_event event;
 42     memset( &event, 0, sizeof( event) );
 43 
 44     event.data.fd = nFd;
 45     event.events |= EPOLLIN | EPOLLRDHUP | EPOLLET;
 46 
 47     if ( nOneShot ) event.events |= EPOLLONESHOT;
 48 
 49     return epoll_ctl( epfd, EPOLL_CTL_ADD, nFd, &event );
 50 }
 51 
 52 int ResetOneShot( int epfd, int nFd )
 53 {
 54     struct epoll_event event;
 55     memset( &event, 0, sizeof(event) );
 56 
 57     event.data.fd = nFd;
 58     event.events |= EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
 59 
 60     return epoll_ctl( epfd, EPOLL_CTL_MOD, nFd, &event);
 61 }
 62 
 63 void * ReadFromClient( void * arg )
 64 {
 65     int nClientFd = (int)arg;
 66     unsigned char buf[1024];
 67     const int nBufSize = sizeof( buf );
 68     int nRead;
 69     int nTotal;
 70     int nDataLen;
 71 
 72     printf("ReadFromClient Enter\n");
 73 
 74     if ( (nRead = read( nClientFd, buf, 2 )) != 2 )
 75     {
 76         printf("Read Data Len error\n");
 77         pthread_exit(NULL);
 78     }
 79 
 80     nDataLen = *(unsigned short *)buf;
 81     printf("nDataLen [%d]\n", nDataLen);
 82     nDataLen = buf[0]*256 + buf[1];
 83     printf("nDataLen [%d]\n", nDataLen);
 84 
 85     nRead = 0;
 86     nTotal = 0;
 87     while( 1 )
 88     {
 89         nRead = read( nClientFd, buf + nRead, nBufSize );
 90         if ( nRead < 0 )
 91         {
 92             printf("Read Data error\n");
 93             pthread_exit( NULL );
 94         }
 95         nTotal += nRead;
 96         if ( nTotal >= nDataLen )
 97         {
 98             break;
 99         }
100     }
101     printf("nTotal [%d]\n", nTotal);
102 
103     sleep(5);
104 
105     int nWrite = write( nClientFd, buf, nTotal );
106     printf("nWrite[%d]\n", nWrite);
107 
108     printf("Not Write ResetOneShot [%d]\n", ResetOneShot(g_epfd, nClientFd));
109 
110     return NULL;
111 }
112 
113 int main(int argc, char const *argv[])
114 {
115     int i;
116     int nClientFd;
117     pthread_t tid;
118     struct epoll_event events[1024];
119 
120     int nServerFd = InitServer( 7777 );
121     if ( nServerFd < 0 )
122     {
123         perror( "nServerFd" );
124         exit(-1);
125     }
126 
127     int epfd = epoll_create( 1024 );
128 
129     g_epfd = epfd;
130 
131     int nReadyNums;
132 
133     if ( AddFd( epfd, nServerFd, 0 ) < 0 )
134     {
135         printf("AddFd error\n");
136         exit(-1);
137     }
138 
139     while( 1 )
140     {
141          nReadyNums = epoll_wait( epfd, events, 1024, -1 );
142 
143          if ( nReadyNums < 0 )
144          {
145              printf("epoll_wait error\n");
146              exit(-1);
147          }
148 
149          for ( i = 0; i <  nReadyNums; ++i)
150          {
151              if ( events[i].data.fd == nServerFd )
152              {
153                  nClientFd = accept( nServerFd, NULL, NULL );
154 
155                  AddFd( epfd, nClientFd, 1 );
156 
157              }else if ( events[i].events & EPOLLIN )
158              {
159                 // Can be implemented by threadpool
160                  //Read data from client
161                 pthread_create( &tid, NULL, ReadFromClient, (void *)(events[i].data.fd) );
162 
163              }else if ( events[i].events & EPOLLRDHUP )
164              {
165                  //Close By Peer
166                 printf("Close By Peer\n");
167                 close( events[i].data.fd );
168              }else
169              {
170                 printf("Some thing happened\n");
171              }
172 
173          }
174     }
175 
176     return 0;
177 }

 

 

測試內容:

注:客戶端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. 補充

  如果什麼不對的或者建議直接說,多討論討論比較好。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved