負けは負けと自覚しつつ、このままではちょっと嫌なので、、、、
TCP のタイムアウト時間を syscall 等でいじるとか、実際的な解決方法はあると思うけど、
とりあえずここでは自分がどうプログラミングをすれば回避できるかを実装してみた。
基本的には、man connect(2) に書かれていることをそのまま実践しただけだけど。
ポイントは、
1. SOCK_STREAM型のソケットを non-blocking に設定
2. non-blocking ソケットに対して connect() を発行すると、
connect() はすぐに -1 を返し、errno には EINPROGRESS がセットされる。
3. EINPROGRESS が返ってきた場合には、select(2) の write 用 FD を監視しておけば、
その connect が完了した際に select() 経由で知ることができる。
というところ。
つまり、v4/v6 の両方のアドレス用に開いたソケットをそれぞれ non-blocking に設定し、
連続して connect() を発行する。non-blocking にすることで v6 の到達性がない等の
理由により connect() がブロックされることもないので、本当に連続して v4/v6 の両方
のアドレスに対して connect() が発行される。後は、select() で write 用 FD を
監視しておき、先に応答を返してきた方のソケットを生かしつつ、反応が遅い方は閉じる。
これにより、先にSYN/ACKを返してきたソケットを利用して通信が出来る。
v4/v6 の dual stack 環境を想定してコードを書いてみたけど、
実は一つの FQDN に対して複数の v4 アドレスがあるような環境に対しても、
使い回し可能なような気がする。
複数の v4 アドレスに対して一斉に SYN を送って一番最初に SYN/ACK を返してきた
ところとセッションを張るというような感じで。
接続が確立できた段階で他のソケットはきちんと閉じないと自分・相手のソケットを無駄に
確保し続けることになるので、その辺りはきちんと処理する必要があるけど。
かなり端折ったコードだけど、一応サンプルコードを乗せてみる。
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int
main (void)
{
int ret;
int cnt;
int max;
int sock[2];
fd_set wfd0, wfd;
struct addrinfo hints, *res, *next;
char hostname[256] = "www.example-hogehoge.com";
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
ret = getaddrinfo(hostname, "80", &hints, &res);
if (ret) {
fprintf(stderr, "getaddrinfo() failed:%s\n",
gai_strerror(ret));
return 1;
}
FD_ZERO(&wfd0);
cnt = -1;
for (next = res; next; next = next->ai_next) {
cnt++;
sock[cnt] = socket(next->ai_family, next->ai_socktype,
next->ai_protocol);
if (-1 == sock[cnt]) {
freeaddrinfo(res);
return 1;
}
ret = fcntl(sock[cnt], F_SETFL, O_NONBLOCK);
if (-1 == ret) {
freeaddrinfo(res);
close(sock[cnt]);
continue;
}
FD_SET(sock[cnt], &wfd0);
ret = connect(sock[cnt], next->ai_addr, next->ai_addrlen);
if (-1 == ret)
continue;
}
max = sock[0] > sock[1] ? sock[0] : sock[1];
while(1) {
wfd = wfd0;
ret = select(max+1, NULL, &wfd, NULL, NULL);
fprintf(stderr, "ret:%d\n", ret);
if (-1 == ret)
return 1;
if (FD_ISSET(sock[0], &wfd)) {
fprintf(stderr, "sock[0] successed\n");
close(sock[1]);
break;
} else if (FD_ISSET(sock[1], &wfd)) {
fprintf(stderr, "sock[1] successed\n");
close(sock[0]);
break;
}
}
sleep(100);
return 0;
}
0 件のコメント:
コメントを投稿