Linux で非ブロッキング接続時に発生するエラーコード EINPROGRESS の対処法
非ブロッキング(connect)を利用しているとき、接続処理の進行中を示すエラーコードEINPROGRESSが返される可能性があります。これは非ブロッキング接続が非同期であるため、即座に返却され、接続処理はバックグラウンドで行われているためです。この問題を解決するためには以下のいずれかの方法を使用できます。
- selectやepollなどのマルチプレクサ系技術を用いて、接続完了まで待つことにより、接続完了後に処理を続行できます。
- 接続が確立しているかどうかを確認するには(非ブロッキングI/Oを使用している場合)、pollやepollなどの関数を使って、ソケットの書き込み可能イベントを調べることで判断できます。
- 非同期I/Oを使用する場合には、connect()呼び出しは非同期で行われ、すぐに返りますが、それ以降はpoll()やepoll()などの関数で接続状態を確認して、接続が完了するまで確認を繰り返す必要があります。
以下は、非ブロッキングconnectを使用するサンプルコードです
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int connect_nonblock(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
int flags, ret;
fd_set rset, wset;
struct timeval tv;
// 设置套接字为非阻塞模式
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
// 发起非阻塞连接
ret = connect(sockfd, addr, addrlen);
if (ret == 0) {
// 连接成功,恢复套接字阻塞模式
fcntl(sockfd, F_SETFL, flags);
return ret;
} else if (ret < 0 && errno != EINPROGRESS) {
// 连接出错
return ret;
}
// 使用select等待连接完成
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tv.tv_sec = timeout;
tv.tv_usec = 0;
ret = select(sockfd + 1, &rset, &wset, NULL, &tv);
if (ret <= 0) {
// 连接超时或出错
return ret;
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
// 连接完成,恢复套接字阻塞模式
fcntl(sockfd, F_SETFL, flags);
return ret;
}
// 连接超时
errno = ETIMEDOUT;
return -1;
}
int main() {
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(80);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
if (connect_nonblock(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr), 5) < 0) {
perror("connect_nonblock");
return -1;
}
// 连接完成,进行后续操作
// ...
close(sockfd);
return 0;
}
selectなどのマルチプレクサは、このサンプルコードで接続完了を待つのに使用されていますが、他のニーズに基づいて、置き換えるために他のマルチプレクサ技術を選択できます。connect_nonblockでは、ソケットはノンブロッキングモードに設定され、ノンブロッキング接続が開始されます。接続が成功すると、ソケットはブロッキングモードに復帰し、戻ります。接続にエラーが発生した場合は、エラーコードが直接返されます。接続が完了していない場合、selectは接続完了を待ってからソケットをブロッキングモードに復帰し、戻します。接続がタイムアウトした場合は、NSError ETIMEDOUTが返されます。