TCP协议通讯流程(三次握手及四次挥手)
TCP协议通讯流程(三次握手及四次挥手)
一、服务器端:
- 调用socket函数,创建一个socket(文件描述符)操作句柄
- 调用bind函数,把刚创建的socket文件描述符 和 ip及端口 绑定在一起,注意一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定bind中进程和端口的关系
- 调用listen函数,对socket文件描述符进行监听(首先listen会在进程地址空间中维护一个缓冲区,如果有客户链接进来,就把其放进缓冲区)
- 进入主循环,循环处理客户端的请求(死循环)
- 调用accept函数并阻塞,等待客户端进来链接(在listen缓冲区中去出),accept成功就返回一个新的socket文件描述符操作句柄,在多进程或多线程等情况下,旧的socket在父进程中返回,继续accept,子进程拿到newsocket对客户进行操作(下面的操作都是newsocket)
- 调用recv函数,接受客户端发送的请求
- 对客户端发送来的请求在函数内部进行处理
- 调用send函数,把处理结果返回给客户端。
- 关闭socket
二、客户端
- 调用socket函数,创建一个socket(文件描述符)操作句柄
- 调用connect函数,向服务器发送链接请求
- 进入主循环,服务器和客户端可能进行多次交互,不止一次
- 调用send函数,把请求数据发送给服务器
- 调用recv函数,接受服务器返回的结果
- 关闭socket
三、三次握手四次挥手
1 . 三次握手
- 在客户端向服务器建立链接的过程中:客户端首先创建自己的socket
- 调用connect发起链接服务器的请求
- 其中connect函数会发出SYN段并阻塞等待服务器的响应 (第一次)
- 服务器收到客户端的链接请求SYN,会应答客户端,向客户端发送一个SYN-ACK,表示同意建立连接请求 (第二次)
- 客户端收到服务器端的SYN-ACK后会从connect返回,同时向服务器端再应答一个ACK段; (第三次)
2.四次挥手
- 在数据传输的过程中:建立连接后,TCP协议提供全双工的通信服务(全双工:在同一条链接中,同一时刻通信双方都可以写数据)。
- 服务器在调用socket创建出socket文件描述符后,再调用bind绑定ip及端口号,服务器在调用accept获取客户端,调用完accept后立即调用recv函数(阻塞,跟read读取管道一样)
- 在这个时间客户端调用send函数向服务器端发送请求(和write管道一样),服务器收到从recv返回的请求,在对客户端的请求进行处理,在这期间客户端调用send函数阻塞等待
- 服务器端调再用send把响应结果发给客户端,再次调用recv读取客户端请求
- 在断开链接的过程中(一般都是客户端主动断开链接,服务器毕竟不是为一个客户服务):如果客户端不在请求了,就会调用close函数关闭链接,客户端就会向服务器端发送一个FIN段,表示我要断开链接了 (第一次)
- 此时服务器端在响应完客户端的请求后,没有在收到客户端的请求,一直阻塞在recv处,此时收到客户端发送的FIN后,会向客户端回应一个ACK,同时recv会返回0 (第二次)
- 服务器端recv返回后,服务器端就直到客户端要关闭链接了,也调用close函数关闭连接,再向客户端发送一个FIN (第三次)
- 客户端收到ACK和FIN后,再向服务器端发送一个ACK (第四次)
三、具体流程
- 服务端状态转化:
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接;
- [LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文.
- [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文ACK, 就进入ESTABLISHED状态, 可以进行读写数据了
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段ACK并进入CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接
- 客户端状态变化:
- [CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段;
- [SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态冰箱服务器端发送一个ACK, 开始读写数据;
- [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段FIN, 同时进入FIN_WAIT_1;
- [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段;
- [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK;
- [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(MaxSegment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态
四、相关注意事项:
1 . 客户端为什么不需要bind?
首先服务器端bind是因为如果客户端要访问服务器端,就必须要服务器端端“地址”,也就是ip+端口号,这样客户端才能找到服务器,进行通信,如果服务器端不进行bind操作,那么操作系统就会随机分配端口号给服务器,那么客户端就不能和服务器端进行通信。而客户端为什么不需要bind,是因为如果客户端bind一个ip + 端口,那么如果一个pc上有多个用户,那么bind同一个端口就会出错(如果一台pc上只有一个用户也是可以bind)。
2 . 为什么要进行三次握手?
为了防止无效的连接请求报文到达服务器而引起错误。
TCP发起建立连接的一方不会一直等待对方的回复,如果超时,他再次发起这个请求,上一个作废。
- 假设A给服务器发送了一个请求,但是由于网络原因迟迟没有到达B服务器。由于一直没收到服务器端的回复确认,所以就进行超时重传,上个就舍弃了。
- 然后新的请求很快的到达了B服务器,然后B服务器也很快的进行了响应,如果是两次握手的话,这样就建立了连接,但是上次因网络问题迟迟未到的第一个请求这时到达了B服务器,服务器依然会当成新的连接请求进行响应,(服务器只要响应,第二次握手就完成了)这时又会建立连接,这就会出现建立了两个连接的局面,
- 然后这就会出现很多问题,例如服务器端认为完成了握手,可以发送数据了,于是一直处于等待数据状态,而发送端不理睬服务器端发来的请求(因为发送端的那个请求早就被清除了),不去发送数据,后果就是服务器一直等,这样就会浪费很多服务器资源
- 如果是三次握手的话,就会避免这个问题,因为比如第二次的新请求到达时,发送端就会又给服务器端发送个确认请求,表示接收到了,这样迟到的那个请求到达服务器端,然后返回到发送端时,发送端并不会响应。然后这个连接并不会建立,服务器也就不会白白等待。
3.为什么要进行四次挥手?
确保数据能够完整传输。
当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,(按照常理的话,第二次和第三次挥手应该一起回复FIN和ACK的,但是因为服务器端可能有数据没发完,所以不能也立刻去主动申请关闭,所以要把ACK和FIN分开)再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。
4.timewite的产生的原因及作用?
原因:
-
为实现TCP全双工连接的可靠释放
假设发起主动关闭的一方(client)最后发送的ACK在网络中丢失,由于TCP协议的重传机制,执行被动关闭的一方(server)将会重发其FIN,在该FIN到达client之前,client必须维护这条连接状态,也就说这条TCP连接所对应的资源(client方的local_ip,local_port)不能被立即释放或重新分配,直到另一方重发的FIN达到之后,client重发ACK后,经过2MSL时间周期没有再收到另一方的FIN之后,该TCP连接才能恢复初始的CLOSED状态。如果主动关闭一方不维护这样一个TIME_WAIT状态,那么当被动关闭一方重发的FIN到达时,主动关闭一方的TCP传输层会用RST包响应对方,这会被对方认为是有错误发生,然而这事实上只是正常的关闭连接过程,并非异常 - 确保第四次握手时发送方发送的ACK可以到达接收方 如果2MSL时间内没有收到,则接收方会进行重发
- 确保当前连接的所有报文都已经过期(每个具体TCP实现必须选择一个报文段最大生存时间MSL。它是任何报文段被丢弃前在网络内的最长时间,2MSL足够使所有报文过期了)
5.理解close_wait状态?
- close_wait状态介绍:
客户端主动关闭连接,服务器接收到客户端的FIN,但是还没有发送自己的FIN,此时的状态为close_wait状态,大量的close_wait状态拖累服务器性能
- close_wait产生的原因:
某种情况下客户端关闭了连接,但是我方忙于读写,没有关闭连接
- 解决方法:
思想:检查出客户端已经关闭的连接,他
之所以会出现这种问题,肯定是服务器端的连接释放的代码存在问题
1.当服务器读写失败时,可以选择关闭连接
2.定期向连接发送询问数据,检查收到的回复数据包(Heart-Beat线程发送指定格式的心跳数据包)
3.修改keep-live参数(超时时间,tcp检查间隔时间:keeplive探测包发送的间隔,tcp检查次数:如果对方不予应答,探测包发送的次数)