ソケット通信では、通常ネットワーク上に小さなパケットを減らす努力、Nagleアルゴリズムが適用されています。
Nagleアルゴリズムでは、コネクション上で承認されていない(Ackパケットを受信していない)
パケットがある場合、新たにパケットを送信しません。
一連の試行では、連続的に一定間隔(500マイクロ秒※)をおいて文字列をソケットへ書き出すことにします。
※500マイクロ秒は、テストする環境によって調整すべき値です。
試行前にpingを使って、クライアント-サーバ間での応答時間(RTT)を計測しました。
その結果、平均RTTは5msだったので、それより短い間隔(500マイクロ秒)でソケットへデータを書き出すことにしました。
第一の試行では、Nagleアルゴリズムをクライアント側ソケットオプションでOFFに設定したケースを検証します。
第二の試行では、NagleアルゴリズムをONに設定したケースを検証します。
サーバプログラムは、共通のエコーサーバを利用します。
試験環境
クライアント
OS:Mac OS Lion
文字列 "hello!"送出クライアント(192.168.11.11)
サーバ
OS:Ubuntu 12.04 LTS
エコーサーバ(192.168.11.5:9877)
第一の試行(NagleアルゴリズムOFF)
#include "unp.h"
#include <netinet/tcp.h>
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
int flag;
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));
int cnt = 0;
char s[] = "hello!";
while(cnt < 6){
Writen(sockfd, &s[cnt], 1);
usleep(500);
cnt++;
}
exit(0);
}
上記のプログラムを稼働させたとき、wiresharkでパケットをキャプチャした結果はこうなりました↓。
プログラムでは、ソケットへ文字列"hello!"から一文字ずつ500マイクロ秒間隔で書き出しており、そのとおりに
パケットが送出されていることが見えます。
No. 41-48のパケットそれぞれで、文字データ'h','e','l','l','o'.'!'を送出してます。
それ以外のパケットは、コネクション確立、解放のパケットなので無視。
port 9877 かつ、パケット送信先:192.168.11.5のパケットデータだけフィルタリング表示してます。
No.42 パケット送出データの内訳。送出データの末尾に"h"が見て取れる。
No.43 パケット送出データの内訳。送出データの末尾に"e"が見て取れる。
No.44 パケット送出データの内訳。送出データの末尾に"l"が見て取れる。
No.45 パケット送出データの内訳。送出データの末尾に"l"が見て取れる。
No.46 パケット送出データの内訳。送出データの末尾に"o"が見て取れる。
No.47 パケット送出データの内訳。送出データの末尾に"!"が見て取れる。
第二の試行(NagleアルゴリズムON)
#include "unp.h"
#include <netinet/tcp.h>
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
int flag;
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);
// Nagleアルゴリズムを無効に設定
flag = 1;
Setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
int cnt = 0;
char s[] = "hello!";
while(cnt < 6){
Writen(sockfd, &s[cnt], 1);
usleep(500);
cnt++;
}
exit(0);
}
上記プログラムを稼働し、同じくwiresharkでパケットをキャプチャした結果はこうなります↓。
port 9877 かつ、パケット送信先:192.168.11.5のパケットデータだけフィルタリング表示してます。
No.9、11以外のパケットはコネクション確立、解放時のパケットなので無視。
No.9 パケット送出データの内訳。送出データの末尾に"h"が見て取れる。
No.11 パケット送出データの内訳。送出データの末尾に"ello!"が見て取れる。
2パケットに送出データを分割することで、ネットワーク上に流れるパケットを減らす努力を行っています。
ちなみに利用したエコーサーバは、↓
エコーサーバ
#include "unp.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
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);
servaddr.sin_port = htons(SERV_PORT);
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 }
}