导航

connect及bind、listen、accept背后的三次握手

Posted on 2019-12-26 15:39  leaaaaaaa  阅读(689)  评论(0)    收藏  举报

如图所示打上断点,分别找出connect() bind() listen() accpet()对应的函数

源码在上一次作业中已经分析过了

https://www.cnblogs.com/qwertyue/p/12067806.html

 

具体过程

下图即TCP编程的步骤

 

 

 

服务器调用listen进行监听
客户端调用connect来发送syn报文
服务器协议栈负责三次握手的交互过程。连接建立后,往listen队列中添加一个成功的连接,直到队列的最大长度。
服务器调用accept从listen队列中取出一条成功的tcp连接,listen队列中的连接个数就少一个

 

接下来分析4个函数的作用

1 connect()

对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。
通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回。
 
2 listen()
对于服务器,它是被动连接的。服务器在被动的等待客户端的连接。
也就是说listen() 函数的主要作用就是将套接字( sockfd )变成被动连接的监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度。
TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。
所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成。
 
3 accept()
accept()函数功能是,从连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。

 accept默认会阻塞进程,直到有一个客户连接建立后返回

 

4 三次握手的连接队列

 

三次握手如下图所示

 

 

 

 

 

已知内核会为任何一个给定的监听套接口维护一个队列,该队列由两部分构成,分别是完成连接接队列、未完成连接队列:

 

1、未完成连接队列(incomplete connection queue),当服务器每收到客户端的一个SYN分节,就会将该客户端放入未完成连接队列,而服务器套接口处于 SYN_RCVD 状态。

2、已完成连接队列(completed connection queue),当客户端和服务器彻底完成三次握手过程,客户端将从未完成连接队列升级成已完成连接队列,并从未完成连接队列中清空该客户端,这些套接口处于 ESTABLISHED 状态。

当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项

然后响应以三次握手的第二个分节:服务器的 SYN 响应,其中稍带对客户 SYN 的 ACK(即SYN+ACK),这一项一直保留在未完成连接队列中,直到三次握手的第三个分节(客户对服务器 SYN 的 ACK )到达或者该项超时为止。

如果三次握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。

那么内核如何得知连接队列的长度呢?

通过listen() 函数的第二个参数( backlog),它会告知内核连接队列的长度。

backlog 参数历史上被定义为上面两个队列的大小之和,大多数实现默认值为 5,当服务器把这个完成连接队列的某个连接取走后,这个队列的位置又空出一个,这样来回实现动态平衡。

 

6 总结

TCP的三次握手是由客户端调用connect()以及服务器端的协议栈共同完成的。


服务器端的listen()负责监听客户端的连接请求,并维护一个listen队列,当有客户端连接成功时,就把它放在listen队列里。


accept()函数负责查看listen队列里面有没有成功连接,如果有则从队列中取出,没有则阻塞(直到获得一个成功连接返回)。它每取出一个成功连接,就会生成一个对应的accept fd,用于唯一标识该连接成功的客户端。