為了理解 TCP keepalive的作用。我們需要清楚,當TCP的Peer A ,Peer B 兩端建立了連接之后,如果一端突然拔掉網線或拔掉電源時,怎么檢測到拔掉網線或者拔掉電源、鏈路不通?原因是在需要長連接的網絡通信程序中,經常需要心跳檢測機制,來實現檢測對方是否在線或者維持網絡連接的需要。
什么是 TCP ?;??
當你建立一個 TCP 連接時,你關聯了一組定時器。其中一些計時器處理?;钸^程。當?;钣嫊r器達到零時,向對等方發送一個?;钐綔y數據包,其中沒有數據并且 ACK 標志打開。
由于 TCP/IP 規范,可以這樣做,作為一種重復的 ACK,并且遠程端點將沒有參數,因為 TCP 是面向流的協議。另一方面,將收到來自遠程主機的回復,沒有數據和ACK 集。
如果收到對 keepalive 探測的回復,則可以斷言連接仍在運行。事實上,TCP 允許處理流,而不是數據包,因此零長度數據包對用戶程序沒有危險。
此過程很有用,因為如果其他對等方失去連接(例如通過重新啟動),即使沒有流量,也會注意到連接已斷開。如果對等方未回復 keepalive 探測,可以斷言連接不能被視為有效,然后采取正確的操作。
為什么要使用 TCP keepalive?
1、檢查死節點 2、 防止因網絡不活動而斷開連接
檢查死節點
想一想 Peer A 和 Peer B 之間的簡單 TCP 連接:初始的三次握手,從 A 到 B 的一個 SYN 段,從 B 到 A 的 SYN/ACK,以及從 A 到 B 的最終 ACK。
此時,我們處于穩定狀態:連接已建立,現在我們通常會等待有人通過通道發送數據。
那么問題來了:從 B 上拔下電源,它會立即斷電,而不會通過網絡發送任何信息來通知 A 連接將斷開。
從它的角度來看,A 已準備好接收數據,并且不知道 B 已經崩潰。現在恢復B的電源,等待系統重啟。A 和 B 現在又回來了,但是當 A 知道與 B 仍然處于活動狀態的連接時,B 不知道。當 A 嘗試通過死連接向 B 發送數據時,情況自行解決,B 回復 RST 數據包,導致 A 最終關閉連接。
_____ _____
| | | |
| A | | B |
|_____| |_____|
^ ^
|--- >--- >--- >-------------- SYN -------------- >--- >--- >---|
|---< ---< ---< ------------ SYN/ACK ------------< ---< ---< ---|
|--- >--- >--- >-------------- ACK -------------- >--- >--- >---|
| |
| system crash --- > X
|
| system restart --- > ^
| |
|--- >--- >--- >-------------- PSH -------------- >--- >--- >---|
|---< ---< ---< -------------- RST --------------< ---< ---< ---|
| |
Keepalive 可以告訴您何時無法訪問另一個對等點,而不會出現誤報的風險。
防止因網絡不活動而斷開連接
keepalive 的另一個有用目標是防止不活動斷開通道。當你在 NAT 代理或防火墻后面時,無緣無故斷開連接是一個非常常見的問題。這種行為是由代理和防火墻中實現的連接跟蹤過程引起的,它們跟蹤通過它們的所有連接。
它們跟蹤通過它們的所有連接。由于這些機器的物理限制,它們只能在內存中保留有限數量的連接。最常見和合乎邏輯的策略是保持最新的連接并首先丟棄舊的和不活動的連接。
_____ _____ _____
| | | | | |
| A | | NAT | | B |
|_____| |_____| |_____|
^ ^ ^
|--- >--- >--- >---|----------- SYN ------------- >--- >--- >---|
|---< ---< ---< ---|--------- SYN/ACK -----------< ---< ---< ---|
|--- >--- >--- >---|----------- ACK ------------- >--- >--- >---|
| | |
| | < --- connection deleted from table |
| | |
|--- >- PSH - >---| < --- invalid connection |
| | |
Linux下使用TCP keepalive
Linux 內置了對 keepalive 的支持。涉及 keepalive 的過程使用三個用戶驅動的變量,可以使用 cat 查看參數值。
前兩個參數以秒表示,最后一個是純數字。這意味著keepalive 例程在發送第一個keepalive 探測之前等待兩個小時(7200 秒),然后每75 秒重新發送一次。如果連續9次沒有收到 ACK 響應,則連接被標記為斷開。
修改這個值很簡單,可以這樣修改:
echo 7000 > /proc/sys/net/ipv4/tcp_keepalive_time echo 40 > /proc/sys/net/ipv4/tcp_keepalive_intvl echo 10 > /proc/sys/net/ipv4/tcp_keepalive_probes
還有另一種訪問內核變量的方法,使用 sysctl 命令
setsockopt 、getsockopt 函數調用
在 Linux 操作系統中,我們可以通過代碼啟用一個 socket 的心跳檢測,為特定套接字啟用 keepalive 所需要做的就是在套接字本身上設置特定的套接字選項。函數原型如下:
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
第一個參數是socket;第二個必須是 SOL_SOCKET,第三個必須是 SO_KEEPALIVE。第四個參數必須是布爾整數值,表示我們要啟用該選項,而最后一個是之前傳遞的值的大小。
在編寫應用程序時,還可以為 keepalive 設置其他三個套接字選項。它們都使用 SOL_TCP 級別而不是 SOL_SOCKET,并且它們僅針對當前套接字覆蓋系統范圍的變量。如果不先寫入就讀取,將返回當前系統范圍的參數。
TCP_KEEPCNT:覆蓋 tcp_keepalive_probes
TCP_KEEPIDLE:覆蓋 tcp_keepalive_time
TCP_KEEPINTVL:覆蓋 tcp_keepalive_intvl
TCP keepalive 代碼實現
在寫TCP keepalive 服務程序時,除了要處理SIGPIPE外,還要有客戶端連接檢測機制,用于及時發現崩潰的客戶端連接。我們使用TCP的 keepalive 機制方式。
tcp_keepalive_client:
int main(int argc, char *argv[])
{
kat_arg0 = basename(argv[0]);
bzero(&cp, sizeof (cp));
cp.cp_keepalive = 1;
cp.cp_keepidle = -1;
cp.cp_keepcnt = -1;
cp.cp_keepintvl = -1;
while ((c = getopt(argc, argv, ":c:d:i:")) != -1) {
switch (c) {
case 'c':
cp.cp_keepcnt = parse_positive_int_option(
optopt, optarg);
break;
case 'd':
cp.cp_keepidle = parse_positive_int_option(
optopt, optarg);
break;
case 'i':
cp.cp_keepintvl = parse_positive_int_option(
optopt, optarg);
break;
case ':':
warnx("option requires an argument: -%c", optopt);
usage();
break;
case '?':
warnx("unrecognized option: -%c", optopt);
usage();
break;
}
}
if (optind > argc - 1) {
warnx("missing required arguments");
usage();
}
ipport = argv[optind++];
if (parse_ip4port(ipport, &cp.cp_ip) == -1) {
warnx("invalid IP/port: "%s"", ipport);
usage();
}
(void) fprintf(stderr, "going connect to: %s port %dn",
inet_ntoa(cp.cp_ip.sin_addr), ntohs(cp.cp_ip.sin_port));
(void) fprintf(stderr, "set SO_KEEPALIVE = %dn", cp.cp_keepalive);
(void) fprintf(stderr, "set TCP_KEEPIDLE = %dn", cp.cp_keepidle);
(void) fprintf(stderr, "set TCP_KEEPCNT = %dn", cp.cp_keepcnt);
(void) fprintf(stderr, "set TCP_KEEPINTVL = %dn", cp.cp_keepintvl);
rv = connectandwait(&cp);
return (rv == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
tcp_keepalive_server:
int main(int argc, char *argv[] )
{
/* 創建套接字 */
if((listen_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("socket()");
exit(EXIT_FAILURE);
}
/* 檢查 keepalive 選項的狀態 */
if(getsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) < 0) {
perror("getsockopt()");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("SO_KEEPALIVE default is %sn", (optval ? "ON" : "OFF"));
/* 將選項設置為活動 */
optval = 1;
optlen = sizeof(optval);
if(setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
perror("setsockopt()");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("SO_KEEPALIVE set on socketn");
/* 再次檢查狀態 */
if(getsockopt(listen_sock, IPPROTO_TCP, TCP_KEEPIDLE, &optval, &optlen) < 0) {
perror("getsockopt()");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("TCP_KEEPIDLE is %dn", optval );
/* 再次檢查狀態 */
if(getsockopt(listen_sock, IPPROTO_TCP, TCP_KEEPCNT, &optval, &optlen) < 0) {
perror("getsockopt()");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("TCP_KEEPCNT is %dn", optval);
/* 再次檢查狀態 */
if(getsockopt(listen_sock, IPPROTO_TCP, TCP_KEEPINTVL, &optval, &optlen) < 0) {
perror("getsockopt()");
close(listen_sock);
exit(EXIT_FAILURE);
}
printf("TCP_KEEPINTVL is %dn", optval );
/* 初始化套接字結構 */
bzero((char *) &serv_addr, sizeof(serv_addr));
int portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
...
}
程序創建一個 TCP 套接字并將 SO_KEEPALIVE 套接字選項設置為 1。如果指定了“-c”、“-d”和“-i”選項中的任何一個,則設置 TCP_KEEPCNT、TCP_KEEPIDLE 和 TCP_KEEPINTVL 套接字選項 在相應選項參數的套接字上。
通過測試程序,我們可以使用tcpdump、或者tshark是命令行抓包工具,來分析KeepAlive。
tshark -nn -i lo port 5050 tcpdump -nn -i lo port 5050
tcpdump -nn -i lo port 5050
整個keepalive過程很簡單,就是client給server發送一個包,server返回給用戶一個包。注意包內沒有數據,只有ACK標識 被打開。
ps -aux | grep tcp_keepalive
總結
keepalive 是一個設備向另一個設備發送的消息,用于檢查兩者之間的鏈路是否正在運行,或防止鏈路中斷。
-
電源
+關注
關注
184文章
17706瀏覽量
249995 -
TCP
+關注
關注
8文章
1353瀏覽量
79058 -
程序
+關注
關注
117文章
3785瀏覽量
81009 -
網絡通信
+關注
關注
4文章
797瀏覽量
29797
發布評論請先 登錄
相關推薦
評論