In message <200308180959.h7I9xnb7001977 / sharui.nakada.kanuma.tochigi.jp>
	on Mon, 18 Aug 2003 18:59:50 +0900,
	nobu.nakada / nifty.ne.jp wrote:
> > (必須ではないかもしれませんが)EINPRROGRESSのときもやった方が良いと言え
> > ます。早くエラーが検知できて、connect(2)の呼び出しを1回節約できる場合
> > があります。
> 
> EINPROGRESSのような時間がかかっているときに、システムコール一回
> の間くらいではそうそう条件は変わらないのではないかという気がす
> るのですが。
手元のテストでは、

1. connect()失敗 (EINPROGRESS)
2. getsockopt()で ECONNREFUSED を検知

といったことが確認できています。相手がlocalhostだと、同一ホスト内なの
でEINPROGRESSに至る間もなく、ECONNREFUSEDであることがわかります。同一
LAN上のホストを相手にすると、EINPROGRESSの場合にgetsockopt(2)でチェッ
クしない場合は、

1. connect()失敗 (EINPROGRESS)
2. connect()失敗 (EINVAL)
3. getsockopt()で ECONNREFUSED を検知

となります。

CPUが高速、メモリやハードディスクがいっぱいになっても、システムコール
にかかるオーバーヘッドは、(システムコールの呼び出しを伴わない)ライブラ
リに比べるとはるかに大きいものです。必要のないシステムコールの呼び出し
をやめることができることができれば、それに越したことはありません。

> 対応としては、FreeBSD(*BSD全部?)でもWAIT_IN_PROGRESSを有効にす
> るということでいいんでしょうか。全システムで有効にしたほうがい
> いのかな?
以下の環境の確認はできました。

	NetBSD 1.6 and current
	FreeBSD 2.2.8
	OpenBSD 3.3

FreeBSDの最新のものはチェックできません。

	http://www.netbsd.org/cgi-bin/query-pr-single.pl?number=22522

に含めたテストプログラムを後ろに付けます。PR(Problem Report)にも書きま
したが、connect(2)でEINVALになる場合は、

	% cc test.c
	% ./a.out 192.168.32.10 12345
	connect to family = 2, port = 234, addr = 192.168.32.10
	connect: Operation now in progress
	getsockopt => Connection refused
	connect to family = 2, port = 234, addr = 192.168.32.10
	connect: Invalid argument
	getsockopt => 0

となります。

=====================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static int connect_test(int, struct sockaddr_in *, int, int);

#define GETSOCKOPT_AFTER_EINPROGRESS	1
#define GETSOCKOPT_AFTER_EINVAL		1

static int exit_after_getsockopt = 0;

int
main(int argc, char **argv)
{
	char *host, *port;
	struct hostent *ht;
	unsigned short p;
	int fd, status, n;
	struct sockaddr_in sin;

	if (argc == 3) {
		host = argv[1];
		port = argv[2];
	} else {
		fputs("Need host and port as parameter.\n", stderr);
		exit(1);
	}
	n = atoi(port);
	if (n <= 0 || n > 65535) {
		fputs("Invalid port number.\n", stderr);
		exit(1);
	}
	p = n;

	ht = gethostbyname(host);
	if (ht == NULL) {
		herror(host);
	}
	memset(&sin, '\0', sizeof sin);
	sin.sin_family = AF_INET;
	sin.sin_port = htons(p);
	memcpy(&sin.sin_addr, ht->h_addr_list[0], ht->h_length);

	fd = socket(PF_INET, SOCK_STREAM, 0);
	status = connect_test(fd, &sin, sizeof sin, 0);
	return(status);
}

static int
connect_test(fd, sockaddr, len, socks)
    int fd;
    struct sockaddr_in *sockaddr;
    int len;
    int socks;
{
    int status;
    int mode;
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1) || (GETSOCKOPT_AFTER_EINVAL == 1)
    int value;
#endif

    mode = fcntl(fd, F_GETFL, 0);

    fcntl(fd, F_SETFL, mode | O_NONBLOCK);

    for (;;) {
	printf("connect to family = %d, port = %d, addr = %s\n",
	       sockaddr->sin_family, ntohs(sockaddr->sin_port),
	       inet_ntoa(sockaddr->sin_addr));

	status = connect(fd, (struct sockaddr *)sockaddr, len);
	if (status < 0) {
		perror("connect");
		switch (errno) {
		case EAGAIN:
		case EINPROGRESS:
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1)
			len = sizeof(value);
			getsockopt(fd, SOL_SOCKET, SO_ERROR, &value, &len);
			printf("getsockopt => %s\n",
				(value != 0) ? strerror(value): "0");
			if (exit_after_getsockopt)
				goto error;
#endif
			continue;
		case EISCONN:
			status = 0;
			break;
		case EINVAL:
#if (GETSOCKOPT_AFTER_EINVAL == 1)
			len = sizeof(value);
			getsockopt(fd, SOL_SOCKET, SO_ERROR, &value, &len);
			printf("getsockopt => %s\n",
				(value != 0) ? strerror(value): "0");
			if (exit_after_getsockopt)
				goto error;
#endif
			break;
		default:
			break;
		}
	}
#if (GETSOCKOPT_AFTER_EINPROGRESS == 1) || (GETSOCKOPT_AFTER_EINVAL == 1)
    error: ;
#endif
	fcntl(fd, F_SETFL, mode);
	return status;
    }
}

=====================================================================

-- 
神戸 隆博 / Takahiro Kambe