socket non-blocking mode connect
对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET),在读写数据之前必须建立连接,connect()函数用于完成面向连接的socket的建链过程,对于TCP,也就是三次握手过程。
connect()函数
头文件:
#include<sys/types.h>
#include<sys/socket.h>
声明:
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()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用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来检测描述符集合是连接成功还是出错(此为《Unix Network Programming》一书中提供的方法,该方法在Linux环境上测试,发现是无效的):
A)如果连接建立是成功的,则通过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0
B)如果建立连接时遇到错误,则errno 的值是连接错误所对应的errno值,比如ECONNREFUSED,ETIMEDOUT 等
□一种更有效的判断方法,经测试验证,在Linux环境下是有效的:
再次调用connect,相应返回失败,如果错误errno是EISCONN,表示socket连接已经建立,否则认为连接失败。
综上所述,这里总结一下非阻塞connect的实现过程。
非阻塞connect的实现过程
1. 创建套接字sockfd
-
- int sock_fd;
- sock_fd = socket(AF_INET, SOCK_STREAM, 0);
2. 设置套接字为非阻塞模式
-
- #if 1
- int flags = fcntl(sock_fd, F_GETFL, 0);
- fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);
- #else
- int imode = 1;
- ioctl(sock_fd, FIONBIO, &imode);
3. 调用connect进行连接
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(PEER_PORT);
- addr.sin_addr.s_addr = inet_addr(PEER_IP);
- int ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
- if (0 == res)
- {
- printf("socket connect succeed immediately.\n");
- ret = 0;
- }
- else
- {
- printf("get the connect result by select().\n");
- if (errno == EINPROGRESS)
- {
- ....
- }
- }
connect会立即返回,可能返回成功,也可能返回失败。如果连接的服务器在同一台主机上,那么在调用connect 建立连接时,连接通常会立即建立成功(我们必须处理这种情况)。
4.调用select(),通过FD_ISSET()检查套接口是否可写,确定连接请求是否完成
- fd_set rfds, wfds;
- struct timeval tv;
-
- FD_ZERO(&rfds);FD_ZERO(&wfds);
- FD_SET(sock_fd, &rfds);
- FD_SET(sock_fd, &wfds);
-
- tv.tv_sec = 10;
- tv.tv_usec = 0;
- int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);
- switch (selres)
- {
- case -1:
- printf("select error\n");
- ret = -1;
- break;
- case 0:
- printf("select time out\n");
- ret = -1;
- break;
- default:
- if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))
- {
- .....
- }
- }
对于无连接的socket类型(SOCK_DGRAM),客户端也可以调用connect进行连接,此连接实际上并不建立类似SOCK_STREAM的连接,而仅仅是在本地保存了对端的地址,这样后续的读写操作可以默认以连接的对端为操作对象。
Linux下常见的socket错误码:
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(连接超时)
测试代码:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <errno.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/select.h>
- #include<sys/ioctl.h>
-
-
-
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
-
-
- #define PEER_IP "192.254.1.1"
- #define PEER_PORT 7008
- int main(int argc, char **argv)
- {
- int ret = 0;
- int sock_fd;
- int flags;
- struct sockaddr_in addr;
-
-
- sock_fd = socket(AF_INET, SOCK_STREAM, 0);
-
-
- #if 1
- flags = fcntl(sock_fd, F_GETFL, 0);
- fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);
- #else
- int imode = 1;
- ioctl(sock_fd, FIONBIO, &imode);
- #endif
-
-
-
- addr.sin_family = AF_INET;
- addr.sin_port = htons(PEER_PORT);
- addr.sin_addr.s_addr = inet_addr(PEER_IP);
- int res = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
- if (0 == res)
- {
- printf("socket connect succeed immediately.\n");
- ret = 0;
- }
- else
- {
- printf("get the connect result by select().\n");
- if (errno == EINPROGRESS)
- {
- int times = 0;
- while (times++ < 5)
- {
- fd_set rfds, wfds;
- struct timeval tv;
-
- printf("errno = %d\n", errno);
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- FD_SET(sock_fd, &rfds);
- FD_SET(sock_fd, &wfds);
-
-
- tv.tv_sec = 10;
- tv.tv_usec = 0;
- int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);
- switch (selres)
- {
- case -1:
- printf("select error\n");
- ret = -1;
- break;
- case 0:
- printf("select time out\n");
- ret = -1;
- break;
- default:
- if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))
- {
- #if 0
- int errinfo, errlen;
- if (-1 == getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &errinfo, &errlen))
- {
- printf("getsockopt return -1.\n");
- ret = -1;
- break;
- }
- else if (0 != errinfo)
- {
- printf("getsockopt return errinfo = %d.\n", errinfo);
- ret = -1;
- break;
- }
-
- ret = 0;
- printf("connect ok?\n");
- #else
- #if 1
- connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
- int err = errno;
- if (err == EISCONN)
- {
- printf("connect finished 111.\n");
- ret = 0;
- }
- else
- {
- printf("connect failed. errno = %d\n", errno);
- printf("FD_ISSET(sock_fd, &rfds): %d\n FD_ISSET(sock_fd, &wfds): %d\n", FD_ISSET(sock_fd, &rfds) , FD_ISSET(sock_fd, &wfds));
- ret = errno;
- }
- #else
- char buff[2];
- if (read(sock_fd, buff, 0) < 0)
- {
- printf("connect failed. errno = %d\n", errno);
- ret = errno;
- }
- else
- {
- printf("connect finished.\n");
- ret = 0;
- }
- #endif
- #endif
- }
- else
- {
- printf("haha\n");
- }
- }
-
- if (-1 != selres && (ret != 0))
- {
- printf("check connect result again... %d\n", times);
- continue;
- }
- else
- {
- break;
- }
- }
- }
- else
- {
- printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
- ret = errno;
- }
- }
- if (0 == ret)
- {
- send(sock_fd, "12345", sizeof("12345"), 0);
- }
- else
- {
- printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);
- }
-
- close(sock_fd);
- return ret;
- }