TCP FASTOPENの有効/無効で実際にどんな感じになるのかをsendto(2)でチェックしてみた。そんなに厳密なテストはしてませんけど。
実験は1台のLinuxマシンで実行でこのような構成。
cpu | Intel(R) Core(TM) i7-3770S CPU @ 3.10GHz |
memory | 32GiB |
Kernel | 3.10-rc3 |
glibc | glibc-2.16-31.fc18.x86_64 |
ディストリビューション | Fedora 18 x86_64 |
Ethernet controller | Realtek Semiconductor Co., Ltd. RTL8111/8168 PCI Express Gigabit Ethernet controller (rev 06) |
テスト方法は以下の通り
- クライアントは並列実行では無く、シーケンシャルにアクセスする形
- サーバはTCP FASTOPEN有効な状態
- TFOありの場合は、クライアントはconnect(2)を実施しないでsendto(2)を直接実行
- TFOなしの場合は、クライアントはconnect(2)を実行してからsendto(2)を実行
これで1000回アクセスした結果のグラフが↓です。
テストの回数が少ないうちは時間がかかっているけど、後半になるに従ってブレはなくなってきてます(これはTFO以外の要因が起因?)。
TFO無しの場合、0.00005msecあたりに落ち着き、TFOありの場合は0.00004msecあたりに落ち着いてます。やっぱり、平均してTFOありの場合の方が速いですね。
時間の計測はTFOありの場合はこの様に、sendto(2)の時間、
clock_gettime(CLOCK_REALTIME, &start);
ret = sendto(sock, buf, strlen(buf), MSG_FASTOPNE_VALUE,
(struct sockaddr *) &sin, slen);
clock_gettime(CLOCK_REALTIME, &end);
TFO無しの場合はconnect+sendtoの時間にしています。TFO無しの場合、conenct(2)しないとサーバに接続できませんし。
clock_gettime(CLOCK_REALTIME, &start); connect(sock, (struct sockaddr *) &sin, slen); ret = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *) &sin, slen); clock_gettime(CLOCK_REALTIME, &end);
テストに使ったサーバ、クライアントはこの前作ったものをちょっと改造した程度ですが一応使ったものを張り付けておくと・・・
サーバはこれです。
#!/usr/bin/env ruby require "socket" def start_server(port = 1111) sock = Socket.open(Socket::AF_INET, Socket::SOCK_STREAM, 0) # Defined in include/linux/tcp.h # #define TCP_FASTOPEN 23 /* Enable FastOpen on listeners */ sock.setsockopt(Socket::SOL_TCP, 23, 5) sock.bind(Addrinfo.tcp("127.0.0.1", port)) sock.listen(20) while true s = sock.accept req = s[0].recv(64, Socket::MSG_PEEK) s[0].printf "fuction is %s\n", req s[0].close end sock.close end if __FILE__ == $0 then start_server() end
クライアントはこちらです。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <time.h> #define SERVER_ADDR "127.0.0.1" #define SERVER_PORT 1111 // Basically MSG_XXX are, such as MSG_PEEK, defined in /usr/include/bits/socket.h // but glibc-headers-2.16-31.fc18.x86_64 does not define it. #define MSG_FASTOPNE_VALUE 0x20000000 static int tfo_sendto(int sock); static int notfo_sendto(int sock); typedef int (*send_data_func_t)(int sock); static struct timespec start, end; #define NUMBER_OF_TESTS 1000 static double results[NUMBER_OF_TESTS]; static void error_exit(void) { fprintf(stderr, "[-]Error: %s\n", strerror(errno)); exit(-1); } #define set_sockaddr_in(sin) \ do { \ sin.sin_family = AF_INET; \ sin.sin_addr.s_addr = htonl(INADDR_ANY); \ sin.sin_port = htons(SERVER_PORT); \ } while (0) static inline double get_msec(const struct timespec *st) { return (double) st->tv_sec + (double) st->tv_nsec * 1e-9; } static int tfo_sendto(int sock) { char *buf = "TFO"; struct sockaddr_in sin = { 0 }; socklen_t slen = sizeof(sin); int ret = 0; printf("[-]Start %s\n", __FUNCTION__); set_sockaddr_in(sin); clock_gettime(CLOCK_REALTIME, &start); ret = sendto(sock, buf, strlen(buf), MSG_FASTOPNE_VALUE, (struct sockaddr *) &sin, slen); clock_gettime(CLOCK_REALTIME, &end); return ret; } static int notfo_sendto(int sock) { char *buf = "NOT"; struct sockaddr_in sin = { 0 }; socklen_t slen = sizeof(sin); int ret = 0; printf("[-]Start %s\n", __FUNCTION__); set_sockaddr_in(sin); clock_gettime(CLOCK_REALTIME, &start); connect(sock, (struct sockaddr *) &sin, slen); ret = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *) &sin, slen); clock_gettime(CLOCK_REALTIME, &end); return ret; } static void tfo_test_start(int cnt, send_data_func_t func) { int sock = 0; int ret = 0; char buf[32] = { 0 }; printf("[-]Start %s\n", __FUNCTION__); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) error_exit(); ret = func(sock); if (ret < 0) error_exit(); ret = recv(sock, buf, sizeof(buf), MSG_PEEK); if (ret == -1) error_exit(); printf("[-] recv %s\n", buf); results[cnt] = get_msec(&end) - get_msec(&start); shutdown(sock, SHUT_RDWR); } int main(int argc, char **argv) { send_data_func_t func = &tfo_sendto; int i = 0; char *test_type = "use TFO"; if (argc == 2) { if (!strcmp("notfo", argv[1])) { func = ¬fo_sendto; test_type = "not use TFO"; } } for (i = 0; i < NUMBER_OF_TESTS; i++) { printf("[-]Run test %d\n", i); tfo_test_start(i, func); } printf("Test result %s\n", test_type); for (i = 0; i < NUMBER_OF_TESTS; i++) printf("Test[%d] %5.5f msec\n", i, results[i]); return 0; }
コンパイルはclock_gettime(2)を使っているので-lrtが必要です。
$ gcc tfo_test_client.c -lrt -o tfo_test_client -Wall