我们先来讲connect函数(本文针对的是TCP)
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
参数大家应该都知道,我在这里提一下,为什么第二个参数要用 struct sockaddr*?
- 这是因为ANSI C标准为我们定义了一个标准套接字的结构体,就是 struct sockaddr, 我们在之前声明的结构如 ipv4中struct sockaddr_in,它们的结构体长度是相同的,证明在强制转换的过程之前定义的结构体中数据没有丢失。如果我们不强制转换,编译器就会报错。
一般调用connect函数的都是客户端,客户端在调用connect之前不必非得调用bind函数进行地址绑定,因为一般内核会确定源IP地址,并选择一个临时端口作为源端口。
调用connect函数后将会激发TCP的三路握手过程
connect函数只在连接建立成功或者出错时返回,其中出错返回可能有以下几种情况
- TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT。(内核不会立即返回,而是会在固定的时间内多次发送SYN分节,如果都没有响应,则会返回错误)。
- 如果服务端对客户的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接,客户一接到RST就会立即返回ECONNREFUSED错误。
RST是TCP发生错误时发送的一种分节。产生RST的三个条件:
1、目的地为某端口的SYN到达,然而该端口没有正在监听的服务器。(这就是上面描述的情况)
2、TCP想取消一个已有连接
3、TCP接收到一个根本不存在的连接上的分节。
-若客户发出的SYN分节在中间的某个路由器上引发了一个"destination unreachable"(目的地不可达)ICMP错误,则认为是一种软错误(soft error)。客户主机内核保存该消息,并按第一种情况所述的时间间隔继续发送SYN。若在某个规定时间后仍未收到响应,则把保存的消息(ICMP错误)作为EHOSTUNREACH或ENETUREACH错误返回给进程。
上文讲了这么多,那到底为什么要用非阻塞connect
那先来说说阻塞connect。在用阻塞模式的connect时,如果出现了上文中第一个错误,connect迟迟没有完成连接,用户程序就会阻塞在connect函数,一般的connect的超时时间在75秒到几分钟之间,那用户程序就需要等待这么久才能返回接着运行,这样就降低了效率。
如果使用非阻塞的connect,如果连接没有立即建立,就要判断它的错误码,我们期望的是EINPROGRESS,表示连接建立已经启动但是尚未完成。这个时候我们就要将套接字加入到select的可写事件集中,然后我们设置一个时间,这个时间就是我们想要等待连接再进行多久的时间,如果这个时间段内select返回并且套接字可写,这个时候connect也不一定成功,我们还要接着判断,因为在POSIX中套接字出错也是会变成即可读又可写的,所以我们还需要用getsockopt函数来获取错误码,如果错误码为0则表示connect成功,否则就失败。
看到这大家也应该能了解到非阻塞connect的优点了。那就是我们可以自己定义connect调用的时间,而不是阻塞的去等待系统规定的时长。
下面就是我们非阻塞connect实现的代码
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<iostream>
#include<error.h>
#include<unistd.h>
#include