非阻塞的Sokcet的学习

Connect函数

声明:
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen)

功能:
使用套接字sockfd建立到指定网络地址serv_addr的socket连接,参数addrlen为serv_addr指向的内存空间大小,即sizeof(struct sockaddr_in)。

返回值:
1)成功返回0,表示连接建立成功(如服务器和客户端是同一台机器上的两个进程时,会发生这种情况)
2)失败返回SOCKET_ERROR,相应的设置errno,通过errno获取错误信息。常见的错误有对方主机不可达或者超时错误,也可能是对方主机没有进程监听对应的端口。

非阻塞connect(non-block mode connect)

套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时;如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。

select判断规则:
1)如果select()返回0,表示在select()超时,超时时间内未能成功建立连接,也可以再次执行select()进行检测,如若多次超时,需返回超时错误给用户。
2)如果select()返回大于0的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:
A) 当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)
B) 当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)
因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将B)和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写。
对于Unix环境,可通过调用getsockopt来检测描述符集合是连接成功还是出错
A)如果连接建立是成功的,则通过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0
B)如果建立连接时遇到错误,则errno 的值是连接错误所对应的errno值,比如ECONNREFUSED,ETIMEDOUT 等

更有效的判断方法:
再次调用connect,相应返回失败,如果错误errno是EISCONN,表示socket连接已经建立,否则认为连接失败。

Linux下常见的socket错误码:

ERROR NUM DESCRIPTION
EACCES, EPERM: 用户试图在套接字广播标志没有设置的情况下连接广播地址或由于防火墙策略导致连接失败。
EADDRINUSE 98: Address already in use(本地地址处于使用状态)
EAFNOSUPPORT 97: Address family not supported by protocol(参数serv_add中的地址非合法地址)
EAGAIN: 没有足够空闲的本地端口。
EALREADY 114: Operation already in progress(套接字为非阻塞套接字,并且原来的连接请求还未完成)
EBADF 77: File descriptor in bad state(非法的文件描述符)
ECONNREFUSED 111: Connection refused(远程地址并没有处于监听状态)
EFAULT: 指向套接字结构体的地址非法。
EINPROGRESS 115: Operation now in progress(套接字为非阻塞套接字,且连接请求没有立即完成)
EINTR: 系统调用的执行由于捕获中断而中止。
EISCONN 106: Transport endpoint is already connected(已经连接到该套接字)
ENETUNREACH 101: Network is unreachable(网络不可到达)
ENOTSOCK 88: Socket operation on non-socket(文件描述符不与套接字相关)
ETIMEDOUT 110: Connection timed out(连接超时)

测试代码:

client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <error.h>
#include <fcntl.h>

#include <iostream>

using namespace std;

int ConnectServer(int port_, string host_)
{
int ret = 0;
int socket_client_fd_;
struct sockaddr_in server_addr_;

cout << "StartConnect()" << endl;
// Create the socket to connect the server and set socket is unblocked.
socket_client_fd_ = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (socket_client_fd_ < 0) {
cout << "Connect to server failed." << endl;
return -1;
}

// Set connect mode and ip host.
server_addr_.sin_family = AF_INET;
server_addr_.sin_port = htons(port_);
server_addr_.sin_addr.s_addr = inet_addr(host_.c_str());

// Trying to connect the server.
int maxfd = 0;
fd_set wset;
fd_set rset;
int error = 0;
struct timeval timeout;
socklen_t len;
bool conncet_successful = false;

cout << "Trying to connect " << host_ << ":" << port_ << "..." << endl;
ret = connect(socket_client_fd_, (struct sockaddr *)&server_addr_, sizeof(server_addr_));
if (ret < 0) {
if (errno != EINPROGRESS) {
cout << "Connect error. " << strerror(errno) << endl;
return -1;
}
// Create the select to listen the unblock connect successful.
// Set the select catch event param.
maxfd = socket_client_fd_ + 1;
FD_ZERO(&wset);
FD_ZERO(&rset);
FD_SET(socket_client_fd_, &wset);
FD_SET(socket_client_fd_, &rset);
timeout.tv_sec = 10;
timeout.tv_usec = 0;

do {
sleep(5);
cout << "Trying to connect " << host_ << ":" << port_ << "..." << endl;
ret = select(maxfd, &rset, &wset, NULL, &timeout);
if (ret > 0) {
// cout << "Select write/read ok ret = " << ret << endl;
// Catch the socket read and write event.
if (FD_ISSET(socket_client_fd_, &rset) || FD_ISSET(socket_client_fd_, &wset)) {
// Get the fd num event.
if (getsockopt(socket_client_fd_, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
// LOG(ERROR) << "ERROR CODE : " << errno;
// LOG(ERROR) << "ERROR MSG : " << strerror(errno);
continue;
}
// When error is 0 then connect successful.
if (error != 0) {
// LOG(ERROR) << "Connect to server failed";
continue;
}
// Retry connect the server.
connect(socket_client_fd_, (struct sockaddr *)&server_addr_, sizeof(server_addr_));
if (errno == EISCONN) {
cout << "Connect to server ok" << endl;
conncet_successful = true;
return socket_client_fd_;
}
continue;
}
} else {
// No select the socket read or write event change.
cout << "ret = " << to_string(ret) << endl;
cout << "select: " << strerror(errno) << endl;
continue;
}
} while (!conncet_successful);
}
// Connect failed.
return -1;
}

int main(int argc, char const *argv[])
{

return 0;
}