TCP/IPでコネクションを張ったあと、サーバーがネットワークから切り離された
場合、パケットはどう流れるのでしょうか?
”サーバーがネットワークから切り離された状態”というのは、
中間ルータの故障や、LANケーブルが引き抜かれた状態を言っています。
サーバーのTCPから、クライアントへFIN,ACKパケットを送信している
状態は含んでいません。
今回も、単純なエコーサーバ、クライアントを使って検証してみます。
エコーサーバ:ubuntu 12.04LTS (192.168.11.6)
エコークライアント:ubuntu 12.04LTS(192.168.11.4)
エコーサーバ、クライアントを立ち上げ、クライアント、サーバ間でTCP/IPコネクションを確立します。
いくつか、クライアントからデータを送信し、サーバからのエコーが返ってくるのを確認します。
少し⬇にwiresharkでのパケットキャプチャをスクリーンショットしてます。
No.145〜151までのパケットが、データ送信の一連の流れです。
No.158のパケット送信する前に、サーバのLANケーブルを引き抜いてネットワークから切り離します。
その状態で、エコークライアントターミナルへ、入力するとNo.158のパケットがエコーサーバへ
送信されます...が、
エコーサーバがネットワーク上にないため、
No.160、163、171、175、176、255のパケットで
Retransmissionパケットが立て続けに送信されています。
その後、サーバからの応答がないまま、約17分間経過した後、クライアントTCPがエラーを返しています。
readline error: No route to host
というエラーをここで吐いています。
パケットの再送
No.160、163、171、175、176、255で
立て続けに送信されているパケットは、Retransmission(再送)パケットといい、
サーバからのACKを得ようと、パケットの再送を繰り返しています。
色々調べてみると、
Man page of TCPに
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man7/tcp.7.html
再送回数の設定項目がありました。
/proc/sys/net/ipv4/tcp_retries2
実験の環境では、15と設定されているにも関わらず(つまり15回再送)、
再送パケットとして検出しているのは、6件。
キャプチャ漏れなのか?どうなのか?
TCP/IPの実装をもう少し見てみる必要がありそうです。。。
あ、あと
readline error: No route to hostを吐くまでの時間(再送タイムアウト?)の設定ってどこでやるの?
という疑問も。
echoサーバ
#include "unp.h"
#include "unistd.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
Signal(SIGCHLD, sig_chld);
for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* back to for() */
else
err_sys("accept error");
}
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
Close(connfd);
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
echoクライアント
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9877);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
str_cli
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Writen(sockfd, sendline, strlen(sendline));
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
}