非阻塞socket调用connect,而连接没有立即建立时会出错,返回errno值为:EINPROGRESS
。
代码
非阻塞connect应用代码示例:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1023
int setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
int unblock_connect(const char* ip, int port, int time){
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
int fdopt = setnonblocking(sockfd);
ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
if(ret == 0){
printf("connect with server immediately\n");
fcntl(sockfd, F_SETFL, fdopt);
return sockfd;
}
else if(errno != EINPROGRESS){
printf("unblock connect not support\n");
return -1;
}
fd_set readfds;
fd_set writefds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(sockfd, &writefds);
ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
if(ret <= 0){
printf("connection time out\n");
close(sockfd);
return -1;
}
if(!FD_ISSET(sockfd, &writefds)){
printf("no events on sockfd found\n");
close(sockfd);
return -1;
}
int error = 0;
socklen_t length = sizeof(error);
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0){
printf("get socket option failed\n");
close(sockfd);
return -1;
}
if(error != 0){
printf("connection failed after select with the error: %d\n", error);
return -1;
}
printf("connection ready after select with the socket: %d\n", sockfd);
fcntl(sockfd, F_SETFL, fdopt);
return sockfd;
}
int main(int argc, char* argv[]){
if(argc < 2){
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int sockfd = unblock_connect(ip, port, 10);
if(sockfd < 0){
return 1;
}
close(sockfd);
return 0;
}
编写测试用的server用例:
#include <sys/socket.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char* argv[]){
const char* ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &addr.sin_addr);
addr.sin_port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = 0;
ret = bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
assert(ret != -1);
ret = listen(listenfd, 5);
for(int i = 0; i < 5; i++){
struct sockaddr_in client;
socklen_t len = sizeof(client);
ret = accept(listenfd, (struct sockaddr*)&client, &len);
printf("accpet %d", i);
if(ret != -1){
printf("\terror\n");
}
else printf("\tsuccess\n");
close(ret);
}
close(listenfd);
return 0;
}
运行
运行环境:Ubuntu 18.04.4 LTS
启动server:
modao@modao-hp:~/socket_io$ ./server 127.0.0.1 4523
accpet 0 error
accpet 1 error
accpet 2 error
accpet 3 error
accpet 4 error
使用client尝试connect:
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection time out
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection time out
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection ready after select with the socket: 3
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection time out
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection ready after select with the socket: 3
modao@modao-hp:~/socket_io$ ./nonblocking_connect 127.0.0.1 4523
connection failed after select with the error: 111
client连接五次后server端关闭。
结论
非阻塞的connect存在几处移植性问题:
- 非阻塞的socket可能导致connect始终失败**(验证确实如此)**
select
对处于EINPROGRESS
状态下的socket可能不起作用- 对于出错的socket,
getsockopt
在有些系统(比如Linux)上返回-1,而在有些系统(比如源自伯克利的UNIX)上则返回0
原书
- Linux高性能服务器编程,游双,9.5