歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C/C++網絡編程中的TCP保活

在默認的情況下,TCP連接是沒有保活的心跳的。這就是說,當一個TCP的socket,客戶端與服務端誰也不發送數據,會一直保持著連接。這其中如果有一方異常掉線,另一端永遠也不可能知道。這對於一些服務型的程序來說,將是災難性的後果。

  所以,必須對創建的socket,啟用保活心跳,即Keepalive選項。

啟用Keepalive

  對於WIN32或者Linux平台來說,設置socket的Keepalive都很簡單,只需使用setsockopt設置SO_KEEPALIVE即可。

  setsockopt的函數原型在Linux環境下為:

[cpp] 
  1. #include <sys/types.h>   
  2. #include <sys/socket.h>   
  3.   
  4. int setsockopt(int s, int level, int optname,  
  5.                const void *optval,  
  6.                socklen_t optlen);  
,在WIN32平台下為
[cpp] 
  1. #include <winsock2.h>   
  2.   
  3. int setsockopt(int s, int level, int optname,  
  4.                const char *optval,  
  5.                int optlen);  

  因為const void *可以接受const char *型的參數,所以為了代碼的跨平台編譯考慮,可以采用以下代碼來設置TCP的Keepalive選項。

[cpp] 
  1. alive = 1;  
  2. if (setsockopt  
  3.     (fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &alive,  
  4.      sizeof alive) != 0)  
  5.   {  
  6.     log_warn ("Set keep alive error: %s.\n", strerror (errno));  
  7.     return -1;  
  8.   }  

  這樣,對於TCP的連接,就啟用了系統默認值的保活心跳。

Linux環境下的TCP Keepalive參數設置

  為什麼說是系統默認值的呢?因為有這樣幾個值,我們並沒有手動設置,是采用的系統默認值。即,

  1. 多長時間發送一次保活心跳?
  2. 如果沒有返回,多長時間再重試發送?
  3. 重試幾次為失敗?

      如果是Linux操作系統,這三個值分別為

[plain] 
  1. # cat /proc/sys/net/ipv4/tcp_keepalive_time  
  2. 7200  
  3. # cat /proc/sys/net/ipv4/tcp_keepalive_intvl  
  4. 75  
  5. # cat /proc/sys/net/ipv4/tcp_keepalive_probes  
  6. 9  

  這就是說,在Linux系統下,如果對於TCP的socket啟用了Keepalive選項,則會在7200秒(即兩個小時)沒有數據後,發起KEEPALIVE報文。如果沒有回應,則會在75秒後再次重試。如果重試9次均失敗,則認定連接已經失效。TCP的讀取操作,將返回0。

  這對於我們大多數應用來說,前兩個時間值都有點太長了。

  我們可以通過重設上面三個值,來使得操作系統上運行的所有啟用了Keepalive選項的TCP的socket的行為更改。

  我們也可以只針對我們自己創建的socket,重設這三個值。它們分別對應TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的選項值,同樣可以使用setsockopt進行設置。

[cpp] 
  1. #include <stdlib.h>   
  2. #include <fcntl.h>   
  3. #include <errno.h>   
  4. #include <sys/socket.h>   
  5. #include <netinet/tcp.h>   
  6. #include <netinet/in.h>   
  7. #include <netdb.h>   
  8. #include <arpa/inet.h>   
  9.   
  10. int  
  11. socket_set_keepalive (int fd)  
  12. {  
  13.   int ret, error, flag, alive, idle, cnt, intv;  
  14.   
  15.   /* Set: use keepalive on fd */  
  16.   alive = 1;  
  17.   if (setsockopt  
  18.       (fd, SOL_SOCKET, SO_KEEPALIVE, &alive,  
  19.        sizeof alive) != 0)  
  20.     {  
  21.       log_warn ("Set keepalive error: %s.\n", strerror (errno));  
  22.       return -1;  
  23.     }  
  24.   
  25.   /* 10秒鐘無數據,觸發保活機制,發送保活包 */  
  26.   idle = 10;  
  27.   if (setsockopt (fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof idle) != 0)  
  28.     {  
  29.       log_warn ("Set keepalive idle error: %s.\n", strerror (errno));  
  30.       return -1;  
  31.     }  
  32.   
  33.   /* 如果沒有收到回應,則5秒鐘後重發保活包 */  
  34.   intv = 5;  
  35.   if (setsockopt (fd, SOL_TCP, TCP_KEEPINTVL, &intv, sizeof intv) != 0)  
  36.     {  
  37.       log_warn ("Set keepalive intv error: %s.\n", strerror (errno));  
  38.       return -1;  
  39.     }  
  40.   
  41.   /* 連續3次沒收到保活包,視為連接失效 */  
  42.   cnt = 3;  
  43.   if (setsockopt (fd, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof cnt) != 0)  
  44.     {  
  45.       log_warn ("Set keepalive cnt error: %s.\n", strerror (errno));  
  46.       return -1;  
  47.     }  
  48.   
  49.   return 0;  
  50. }  

WIN32環境下的TCP Keepalive參數設置

  而WIN32環境下的參數設置,就要麻煩一些,需要使用另外的一個函數WSAIoctl和一個結構struct tcp_keepalive。

  它們的原型分別為:

[cpp] 
  1. #include <winsock2.h>   
  2. #include <mstcpip.h>   
  3.   
  4. int WSAIoctl(  
  5.              SOCKET s,  
  6.              DWORD dwIoControlCode,  
  7.              LPVOID lpvInBuffer,  
  8.              DWORD cbInBuffer,  
  9.              LPVOID lpvOutBuffer,  
  10.              DWORD cbOutBuffer,  
  11.              LPDWORD lpcbBytesReturned,  
  12.              LPWSAOVERLAPPED lpOverlapped,  
  13.              LPWSAOVERLAPPED_COMPLETION lpCompletionRoutine  
  14. );  
  15.   
  16. struct tcp_keepalive {  
  17.     u_long onoff;  
  18.     u_long keepalivetime;  
  19.     u_long keepaliveinterval;  
  20. };  

  在這裡,使用WSAIoctl的時候,dwIoControlCode要使用SIO_KEEPALIVE_VALS,lpvOutBuffer用不上,cbOutBuffer必須設置為0。

  struct tcp_keepalive結構的參數意義為:

  onoff,是否開啟KEEPALIVE; keepalivetime,多長時間觸發Keepalive報文的發送; keepaliveinterval,多長時間沒有回應觸發下一次發送。

  注意:這裡兩個時間單位都是毫秒而不是秒。

[cpp] 
  1. #include <winsock2.h>   
  2. #include <mstcpip.h>   
  3.   
  4. int  
  5. socket_set_keepalive (int fd)  
  6. {  
  7.   struct tcp_keepalive kavars[1] = {  
  8.       1,  
  9.       10 * 1000,        /* 10 seconds */  
  10.       5 * 1000          /* 5 seconds */  
  11.   };  
  12.   
  13.   /* Set: use keepalive on fd */  
  14.   alive = 1;  
  15.   if (setsockopt  
  16.       (fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &alive,  
  17.        sizeof alive) != 0)  
  18.     {  
  19.       log_warn ("Set keep alive error: %s.\n", strerror (errno));  
  20.       return -1;  
  21.     }  
  22.   
  23.   if (WSAIoctl  
  24.       (fd, SIO_KEEPALIVE_VALS, kavars, sizeof kavars, NULL, sizeof (int), &ret, NULL,  
  25.        NULL) != 0)  
  26.     {  
  27.       log_warn ("Set keep alive error: %s.\n", strerror (WSAGetLastError ()));  
  28.       return -1;  
  29.     }  
  30.   
  31.   return 0;  
  32. }  
Copyright © Linux教程網 All Rights Reserved