5 connect()
connect() 系统调用将文件描述符 sockfd 引用的套接字连接到 addr 指定的地址。
2.1 包含头文件
#include <sys/types.h>
#include <sys/socket.h>
2.2 函数主体
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数解释:
- int sockfd
socket()函数返回的未被使用的文件描述符
- const struct sockaddr *addr
该地址为所要连接的socket的地址,connect() 系统调用将文件描述符 sockfd 引用的套接字连接到 addr 指定的地址。
如果套接字 sockfd 的类型为 SOCK_DGRAM,则 addr 是默认情况下数据报发送到的地址,也是接收数据报的唯一地址。如果套接字的类型为 SOCK_STREAM 或 SOCK_SEQPACKET,则此调用将尝试与绑定到 addr 指定的地址的套接字建立连接。通常,基于连接的协议套接字可能只成功连接()一次;无连接协议(例如UDP数据报)套接字可以多次使用 connect() 来更改其关联。
- socklen_t addrlen
addrlen 参数指定 addr 的大小。
2.3 返回值
如果连接成功,则返回0;出错时,返回-1,错误类型如下:
错误类型 | 解释 |
---|---|
EACCES, EPERM | 用户尝试连接到广播地址,但未启用套接字广播标志,或者由于本地防火墙规则,连接请求失败。 |
EACCES(本地套接字) | 对套接字文件的写入权限被拒绝,或者对路径前缀中的某个目录的搜索权限被拒绝。 |
EADDRINUSE | 本地地址已在使用中。 |
EAFNOSUPPORT | 传递的地址其sa_family字段中不正确。 |
EAGAIN | 路由缓存中的条目不足。 |
EALREADY | 套接字为非阻塞套接字,且上一次尝试连接还未完成。 |
EBADF | sockfd不是有效的文件描述符。 |
ECONNREFUSED | 流套接字上的 connect() 发现没有在监听的远程地址。 |
EFAULT | 套接字结构地址位于用户的地址空间之外。 |
EINTR | 本次系统调用被捕获的信号中断。 |
EISCONN | 套接字已连接。 |
ENETUNREACH | 网络无法访问。 |
EPROTOTYPE | 套接字类型不支持请求的通信协议。例如,尝试将 UNIX 域数据报套接字连接到流套接字时,可能会发生此错误。 |
ETIMEDOUT | 尝试连接时超时。 服务器可能太忙,无法接受新连接。 对于 IP 套接字,在服务器上启用 syncookie 时,超时可能很长。 |
2.4 小结
介绍完connect()函数之后,socket编程的五个基本函数便已经全部介绍完毕,基于这五个函数的两个或多个套接字便可以正常建立连接,进行读写操作,读写操作用到的函数将在后面的小节中继续总结更新。此处,先对服务端和客户端建立连接的过程进行总结。
我们创建的socket的数据结构如下图,每个进程都会维护一个这样的文件描述符表。
服务端与客户端建立连接的流程如下:
- 服务端:socket()—bind()—listen()—accept()(阻塞…)
- 客户端:socket()—connect()(阻塞…)
在客户端调用connect()尝试连接服务端时,双方三次握手建立连接的流程如下:
对上图进一步补充说明:
-
客户端调用connect()时,双方开始三次握手建立连接,建立连接流程如下:
- 客户端调用connect()后阻塞,向服务端发送SYN J包,此时客户端进入SYN_SENT状态,等待服务端发送ACK+SYN
- 服务端收到SYN报文后,进入SYN_RCVD状态,发送ACK J+1,SYN K给客户端,发送成功后进入ESTABLISTED状态。
- 客户端收到ACK+SYN后,connect()返回,进入ESTABLISTED状态,向服务端发送ACK K+1。
- 服务端收到ACK包后,accept()函数返回,返回值为监听socket维护的第一个队列中的第一个socket。
-
监听套接字会维护两个连接队列,第一个为进入ESTABLISTED状态的套接字队列,第二个为ESTABLISTED状态之前的套接字队列。可以通过“netstat -an”命令查看主机目前所维护的来连接。
-
服务端和客户端socket初始化步骤为何有区别:
- 服务端,顾名思义用来提供服务,不同的端口提供不同的服务,因此需要绑定唯一的端口来表明该服务的地址,这样客户端才可以找到该服务的位置。
- 客户端,建立连接也需要一个端口,但是并不需要固定一个端口,因此默认有系统随机分配。同时面向连接的TCP数据报中会包含主机与目的主机的IP和端口,因此,不需要再应用层使用bind()进行绑定。
函数的简单实战可以通过这个项目:代码传送门