Socket編程實踐(6) --TCP服務端注意事項

殭屍進程處理

1)通過忽略SIGCHLD信號,避免殭屍進程

    在server端代碼中添加

    signal(SIGCHLD, SIG_IGN);

 

2)通過wait/waitpid方法,解決殭屍進程

  1. signal(SIGCHLD,onSignalCatch);  
  2.   
  3. void onSignalCatch(int signalNumber)  
  4. {  
  5.     wait(NULL);  
  6. }  

3) 如果多個客戶端同時關閉, 問題描述如下面兩幅圖所示:


  1. /** client端實現的測試代碼**/  
  2. int main()  
  3. {  
  4.     int sockfd[50];  
  5.     for (int i = 0; i < 50; ++i)  
  6.     {  
  7.         if ((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1)  
  8.             err_exit("socket error");  
  9.   
  10.         struct sockaddr_in serverAddr;  
  11.         serverAddr.sin_family = AF_INET;  
  12.         serverAddr.sin_port = htons(8001);  
  13.         serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  14.         if (connect(sockfd[i], (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)  
  15.             err_exit("connect error");  
  16.     }  
  17.     sleep(20);  
  18. }  

在客戶運行過程中按下Ctrl+C,則可以看到在server端啓動50個子進程,並且所有的客戶端全部一起斷開的情況下,產生的殭屍進程數是驚人的(此時也證明了SIGCHLD信號是不可靠的)!


解決方法-將server端信號捕捉函數改造如下:

  1. void sigHandler(int signo)  
  2. {  
  3.     while (waitpid(-1, NULL, WNOHANG) > 0)  
  4.         ;  
  5. }  

waitpid返回值解釋:

  on  success,  returns the process ID of the child whose state has changed(返回已經結束運行

的子進程的PID); if WNOHANG was specified and one or more child(ren) specified by pid exist, 

but have not yet changed state, then 0 is returned(如果此時尚有好多被pid參數標識的子進程存在

且沒有結束的跡象, 返回0).  On error, -1 is returned.

 

地址查詢API

  1. #include <sys/socket.h>  
  2. int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //獲取本地addr結構  
  3. int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //獲取對方addr結構  
  4.   
  5. int gethostname(char *name, size_t len);  
  6. int sethostname(const char *name, size_t len);  
  7.   
  8. #include <netdb.h>  
  9. extern int h_errno;  
  10. struct hostent *gethostbyname(const char *name);  
  11.   
  12. #include <sys/socket.h>       /* for AF_INET */  
  13. struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);  
  14. struct hostent *gethostent(void);  
  1. //hostent結構體  
  2. struct hostent  
  3. {  
  4.     char  *h_name;            /* official name of host */  
  5.     char **h_aliases;         /* alias list */  
  6.     int    h_addrtype;        /* host address type */  
  7.     int    h_length;          /* length of address */  
  8.     char **h_addr_list;       /* list of addresses */  
  9. }  
  10. #define h_addr h_addr_list[0] /* for backward compatibility */  
  1. /**獲取本機IP列表**/  
  2. int gethostip(char *ip)  
  3. {  
  4.     struct hostent *hp = gethostent();  
  5.     if (hp == NULL)  
  6.         return -1;  
  7.   
  8.     strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));  
  9.     return 0;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     char host[128] = {0};  
  15.     if (gethostname(host, sizeof(host)) == -1)  
  16.         err_exit("gethostname error");  
  17.   
  18.     cout << "host-name: " << host << endl;  
  19.     struct hostent *hp = gethostbyname(host);  
  20.     if (hp == NULL)  
  21.         err_exit("gethostbyname error");  
  22.   
  23.     cout << "ip list: " << endl;  
  24.     for (int i = 0; hp->h_addr_list[i] != NULL; ++i)  
  25.     {  
  26.         cout << '\t'  
  27.              << inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]) << endl;  
  28.     }  
  29.   
  30.     char ip[33] = {0};  
  31.     gethostip(ip);  
  32.     cout << "local-ip: " << ip << endl;  
  33. }  

TCP協議的11種狀態 


1.如下圖(客戶端與服務器都在本機:雙方(server的子進程,與client)鏈接已經建立(ESTABLISHED),等待通信)

 

2.最先close的一端,會進入TIME_WAIT狀態; 而被動關閉的一端可以進入CLOSE_WAIT狀態 (下圖,server端首先關閉)

 

3.TIME_WAIT 時間是2MSL(報文的最長存活週期的2倍) 

  原因:(ACK y+1)如果發送失敗可以重發, 因此如果server端不設置地址重複利用的話, 服務器在短時間內就無法重啓;

    服務器端處於closed狀態,不等於客戶端也處於closed狀態。

(下圖, client先close, client出現TIME_WAIT狀態)

 

4.TCP/IP協議的第1種狀態:圖上只包含10種狀態,還有一種CLOSING狀態

產生CLOSING狀態的原因:

Server端與Client端同時關閉(同時調用close,此時兩端同時給對端發送FIN包),將產生closing狀態,最後雙方都進入TIME_WAIT狀態(如下圖)。


SIGPIPE信號

往一個已經接收FIN的套接中寫是允許的,接收到FIN僅僅代表對方不再發送數據;但是在收到RST段之後,如果還繼續寫,調用write就會產生SIGPIPE信號,對於這個信號的處理我們通常忽略即可。

    signal(SIGPIPE, SIG_IGN); 

  1. /** 測試: 在Client發送每條信息都發送兩次 
  2. 當Server端關閉之後Server端會發送一個FIN分節給Client端, 
  3. 第一次消息發送之後, Server端會發送一個RST分節給Client端,  
  4. 第二次消息發送(調用write)時, 會產生SIGPIPE信號; 
  5. 注意: Client端測試代碼使用的是下節將要介紹的Socket庫 
  6. **/  
  7. void sigHandler(int signo)  
  8. {  
  9.     if (SIGPIPE == signo)  
  10.     {  
  11.         cout << "receive SIGPIPE = " << SIGPIPE << endl;  
  12.         exit(EXIT_FAILURE);  
  13.     }  
  14. }  
  15. int main()  
  16. {  
  17.     signal(SIGPIPE, sigHandler);  
  18.     TCPClient client(8001, "127.0.0.1");  
  19.     try  
  20.     {  
  21.         std::string msg;  
  22.         while (getline(cin, msg))  
  23.         {  
  24.             client.send(msg);  
  25.             client.send(msg);   //第二次發送  
  26.             msg.clear();  
  27.             client.receive(msg);  
  28.             client.receive(msg);  
  29.             cout << msg << endl;  
  30.             msg.clear();  
  31.         }  
  32.     }  
  33.     catch (const SocketException &e)  
  34.     {  
  35.         cerr << e.what() << endl;  
  36.     }  
  37. }  

close與shutdown的區別

  1. #include <unistd.h>  
  2. int close(int fd);  
  3.   
  4. #include <sys/socket.h>  
  5. int shutdown(int sockfd, int how);  

shutdown的how參數

SHUT_RD

關閉讀端

SHUT_WR

關閉寫端

SHUT_RDWR

讀寫均關閉

1.close終止了數據傳送的兩個方向;

  而shutdown可以有選擇的終止某個方向的數據傳送或者終止數據傳送的兩個方向。

2.shutdown how=SHUT_WR(關閉寫端)可以保證對等方接收到一個EOF字符(FIN段),而不管是否有其他進程已經打開了套接字(shutdown並沒採用引用計數)。

  而close需要等待套接字引用計數減爲0時才發送FIN段。也就是說直到所有的進程都關閉了該套接字。

 

示例分析:

   客戶端向服務器按照順序發送:FIN E D C B A, 如果FIN是當client尚未接收到ABCDE之前就調用close發送的, 那麼client端將永遠接收不到ABCDE了, 而通過shutdown函數, 則可以有選擇的只關閉client的發送端而不關閉接收端, 則client端還可以接收到ABCDE的信息;


/**測試: 實現與上面類似的代碼(使用close/shutdown)兩種方式實現 **/

完整源代碼請參照:

http://download.csdn.net/detail/hanqing280441589/8486517

注意: 最好讀者需要有select的基礎, 沒有select基礎的讀者可以參考<Socket編程實踐(8)>相關部分

發佈了52 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章