epoll を使ってクライアントコードを書いてみた

今回は libevent ではなく、epoll を生で使ってサーバ/クライアントのセットを書いていました。

クライアントがなぜか必ずタイムアウトして終了するという謎の現象に悩まされましたが、

よくよくみてみると、


ev.events = (u_int32_t) EPOLLIN;
としていたことが原因でした・・・

EPOLLOUT が正解の模様。

せっかくのソケットネタなので、サンプルとしてウェブサーバから情報を引っ張ってくるコードでも載せてみます。

main() しかなかったり、エラー処理も適当ですが、あくまでサンプルなのでご了承ください。



下記のコードを a.c とでも名前をつけて、cc a.c でコンパイルすると a.out という実行プログラムが得られるので、

./a.out d.hatena.ne.jp 80
として実験可能です。


#include
#include

#include
#include
#include
#include
#include
#include

#include

#define TIMEOUT 500

int
main(int argc, char **argv)
{
char buf[1460];
int epfd, nepfds, sockfd, error, flags, rc, connected = 0, val = 0;
socklen_t len;
struct addrinfo hints = {0, 0, 0, 0, 0, NULL, NULL, NULL};
struct addrinfo *res, *res0;
struct epoll_event ev = {0, {0}};
struct epoll_event event;

(void) argc;

hints.ai_socktype = SOCK_STREAM;

if ( (error = getaddrinfo(argv[1], argv[2], &hints, &res0)) != 0) {
if (error == EAI_NONAME || error == EAI_SERVICE) {
/* ホスト、サービスの指定に間違いがある */
fprintf(stderr, "Usage: %s hostname port\n", argv[0]);
}
return (-1);
}

for (res = res0; res; res = res->ai_next) {
if ( (sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
/* ソケットが作れなければ次の addrinfo で作ってみるためコンティニュー */
continue;
}

/* ソケットをノンブロッキングモードに変更 */
if ( (flags = fcntl(sockfd, F_GETFL, 0)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
if ( (fcntl(sockfd, F_SETFL , flags | O_NONBLOCK)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}

if ( (rc = connect(sockfd, res->ai_addr, res->ai_addrlen)) == -1) {
if (errno != EINPROGRESS) {
perror("connect");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
} else {
break;
}
} else if (rc == 0) {
/* ソケットをブロッキングモードに変更 */
if ( (flags = fcntl(sockfd, F_GETFL, 0)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
if ( (fcntl(sockfd, F_SETFL , flags & ~O_NONBLOCK)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
connected = 1;
}
}

if (connected == 0) {
if ( (epfd = epoll_create(1)) == -1) {
perror("epoll_create");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}

ev.events = (u_int32_t) EPOLLOUT;
ev.data.fd = sockfd;

if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
perror("epoll_ctl");
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}

for (;;) {
if ( (nepfds = epoll_wait(epfd, &event, 1, TIMEOUT)) == -1) {
if (errno != EINTR) {
perror("epoll_wait");
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
} else {
continue;
}
} else if (nepfds == 0) {
fprintf(stderr, "timed out\n");
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);
return (-2);
}

len = (socklen_t) sizeof(val);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *) &val, &len) == -1) {
perror("getsockopt");
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
} else {
if (val == 0) {
/* ソケットをブロッキングモードに変更 */
if ( (flags = fcntl(sockfd, F_GETFL, 0)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
if ( (fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK)) == -1) {
perror("fcntl");
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
(void) close(epfd);
break;
} else {
fprintf(stderr, "timed out\n");
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);
return (-1);
}
}
}
}

(void) send(sockfd, "GET / HTTP/1.1\r\nHost: d.hatena.ne.jp\r\nUser-Agent: a.out\r\n\r\n", 59, 0);
buf[sizeof(buf) - 1] = '\0';
(void) recv(sockfd, buf, sizeof(buf), 0);
(void) printf("%s\n", buf);
(void) close(epfd);
(void) close(sockfd);
freeaddrinfo(res0);

return (EX_OK);
}