TCP协议通讯流程(如图)
1 服务器的初始化(服务器端)
- 调用socket,创建文件描述符
- 调用bind,将当前文件描述符与IP地址跟端口号绑定在一起;如果该端口号已经被其它进程占用了,就会bind失败
- 调用listen, 声明当前文件描述符为服务器的文件描述符
- 调动accept,阻塞等待客户端连接
2 建立连接的过程(三次握手)
- 客户端调用socket,创建文件描述符
- 客户端调用connect,向服务器端发起连接请求
- connect会发出SYN段并阻塞等待服务器应答(第一次握手)
- 服务器端收到客户端的SYN,会应答一个SYN-ACK段,表示“同意建立连接”(第二次握手)
- 客户端收到SYN-ACK后会从connect返回,同时应答一个ACK段(第三次握手)
这样,这个连接的过程,称为三次握手。
3 数据传输的过程:
- 建立连接后,TCP协议提供全双工的通信服务
- 服务器从accept()返回后立刻调用read(),读socket中的数据,如果没有数据就阻塞等待
- 这时,客户端调用write(),向服务器发送请求,服务器收到请求后read()返回,对客户端请求进行处理,在此期间,客户端调用read阻塞等待服务器应答
- 服务器调用write将结果发回给客户端,再次调用read阻塞等待下一个请求
- 客户端收到应答后,read返回,发送下一条请求...
4 断开连接过程(四次挥手)
- 如果客户端没有请求了,就调用close关闭连接,客户端会向服务器发送FIN段(第一次挥手)
- 服务器收到FIN段,会回应一个ACK段,同时read返回0(第二次挥手)
- read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这时服务器也会向客户端发送一个FIN(第三次挥手)
- 客户端收到FIN,再返回一个ACK给服务器(第四次挥手)
这样,这个断开连接的过程称为四次挥手。
TCP通讯过程中的状态变化
1 服务端状态变化
- CLOSED -> LISTEN 服务器端调用listen后进入LISTEN 状态,等待客户端连接
- LISTEN -> SYN_RCVD 一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文
- SYN_RCVD -> ESTABLISHED 服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,连接完成了,可以进行读写了
- ESTABLISHED -> CLOSE_WAIT 当客户端调用close主动关闭连接时,服务器会收到结束报文段,服务器返回确认报文并进入CLOSE_WAIT状态
- CLOSE_WAIT -> LAST_ACK 进入CLOSE_WAIT状态说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,回向客户端发送FIN,服务期进入LASK_ACK状态,等待最后一个ACK的到来
- LAST_ACK -> CLOSED 服务器收到了最后一个ACK ,就会彻底关闭连接
2 客户端状态变化
- CLOSED -> SYN_SENT 客户端调用connect,发送同步报文段
- AYN_SENT -> ESTABLISHED connect调用成功,建立连接,可以进行读写数据
- ESTABLISHED -> FIN_WAIT_1 客户端调用close,向服务器发送结束报文段,同时进入FIN_WAIT_1
- FIN_WAIT_1 -> FIN_WAIT_2 客户端收到服务器的确认报文段,进入FIN_WAIT_2,开始等待服务器的结束报文段
- FIN_WAIT_2 -> TIME_WAIT 客户端收到服务器发来的结束报文段,进入TIME_WAIT, 并发出LASK_ACK
- TIME_WAIT -> CLOSED 客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态
TIME_WAIT状态的理解
我们在运行一个简单的服务器程序,用一个客户端程序连接上服务器程序,如果此时我们让服务器进程断开连接,又立刻重新启动服务器进程,就会发现启动不了:
这是因为虽然进程终止了,但连接并未完全断开,这时,我们可以用netstat查看网络连接状态:
- TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间才能回到CLOSED状态
- 我们使用Ctrl+C结束了server进程,server进程处于TIME_WAIT状态,在TIME_WAIT状态期间不能再次监听同一server端口
- MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s
可以通过下面的方法查看:
为什么TIME_WAIT的时间是2MSL?
1 MSL是TCP报文生存的最大时间,因此TIME_WAIT持续在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,否则服务器立刻重启可能会收到上一个进程迟到的数据。
2 同时理论上保证最后一个报文的可靠到达(假设最后一个ACK丢失,那么服务器会重发一个FIN,这是客户端虽然不在了,但TCP连接还在,仍可以重发LAST_ACK)