相手ホストのクラッシュを検知するために、用意されているソケットオプションにSO_KEEPALIVEがあります。
TCPソケットにSO_KEEPALIVEオプションが設定され、クライアントーサーバ間で2時間以上データが
交換されなかった場合、TCPは相手に向けてキープアライブプローブを送信するのがデフォルトの動作です。
2時間というアイドル時間は、ソケットオプションで変更出来ます。
TCP_KEEPIDLE
キープアライブを投げ始めるまでの時間(秒)デフォルトは7200秒(=2時間)
設定個所:/proc/sys/net/ipv4/tcp_keepalive_time
ただ、ふと、このキープアライブアイドルタイマってソケット毎に独立してるのかなって?
ギモンに思いました。
つまり、プロセスAで”ソケット”に設定したアイドル時間が、
プロセスBの”ソケット”に自動的に反映されることがあるのか?ってギモンです。
組込みとかでは、タイマリソースを節約したりするケースもあるので気になったという次第です。
ただ、予想としては、普通のOSでは、”ソケット毎”にタイマーが割り当てられると思いますが...ね。
いちおー、実験。
シナリオとしては、
サーバプロセスA(192.168.11.11:9877)
ソケットオプション:キープアライブアイドルタイマ 300秒に設定
サーバプロセスB(192.168.11.11:9878)
ソケットオプション:キープアライブアイドルタイマ 420秒に設定
を走行させ、
クライアントプロセスA(192.168.11.7)からサーバプロセスAのポート9877へアクセス
クライアントプロセスB(192.168.11.7)からサーバプロセスBのポート9878へアクセス
ソケット毎にキープアライブアイドルタイマが割り当てられていたら、
サーバプロセスA、Bそれぞれ、300秒後、420秒後にキープアライブブローブパケットを送出する。
キープアライブアイドルタイマがソケットで共有されていたら、
サーバプロセスA、Bで同じタイミングでキープアライブプローブパケットが送出される。
動作環境
クライアント:Mac OS Lion(192.168.11.11)
エコークライアント。サーバipアドレスを指定して実行
サーバ:Ubuntu 12.04LTS(192.168.11.7)
エコーサーバ
ソケットオプション:TCP_KEEPIDLE=300/TCP_KEEPINTVL=30/TCP_KEEPCNT=3
キープアライブを投げ始めるまでの時間が2時間というのはちょっと長いので、今回は300秒(=5分)としました。
加えて、プローブ送信間隔を30秒、送信回数を3回へ変更しました。
クライアントプログラム
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
int maxfdp1;
struct sockaddr_in servaddr;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
FD_ZERO(&rset);
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
// ここは、プロセスA,B毎に書き換え
// プロセスA では、9877を設定、プロセスBでは9878を設定
servaddr.sin_port = htons(9877);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
for (;;){
FD_SET(fileno(stdin), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(stdin), sockfd)+1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset)){
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
if(FD_ISSET(fileno(stdin), &rset)){
if(Fgets(sendline, MAXLINE, stdin) == NULL)
return;
Writen(sockfd, sendline, strlen(sendline));
}
}
exit(0);
}
サーバプログラム
#include "unp.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
int
main(int argc, char **argv)
{
int listenfd, connfd;
int flag;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// ここは、プロセスA,B毎に書き換え
// プロセスA では、9877を設定、プロセスBでは9878を設定
servaddr.sin_port = htons(9877);
flag = 1;
Setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flag, sizeof(flag));
// ここは、プロセスA,B毎に書き換え
// プロセスA では、300を設定、プロセスBでは420を設定
flag = 300;
Setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&flag, sizeof(flag) );
flag = 3;
Setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPCNT, (void*)&flag, sizeof(flag) );
flag = 30;
Setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPINTVL, (void*)&flag, sizeof(flag) );
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket }
}
計測結果
注目するところは、No 7452/7453(プロセスA)および、No9350/9361(プロセスB)のパケット。
プロセスA(バインドポート9877)、プロセスB(バインドポート9878)からそれぞれ違うタイミングで
キープアライブプローブパケットを送出している。
結果、ソケット毎にキープアライブアイドルタイマーが割り当てられている。