从别后,忆相逢,几回魂梦与君同……
TCP 三次握手和四次挥手
TCP是面向连接的可靠的协议,建立TCP连接需要三次对话(三次握手),拆除TCP连接需要四次对话(四次挥手),确保通信双方能够正常发送和接收数据。
三次握手
服务端调用listen()函数后就进入监听(等待)状态,这时候,客户端就可以调用connect()函数发起TCP连接请求了,客户端connect()会引发三次握手, 完成后会建立一个双向的连接通道。
1
2
3
4
客户端 服务端
| SYN ------------------> |
| <----------------- SYN + ACK
| ACK ------------------> |
- 第一次握手(客户端 → 服务端):
- 客户端向服务端发送一个带有 SYN 标志的数据包,表示请求建立连接。
- 此时客户端进入 SYN_SENT 状态。
- 第二次握手(服务端 → 客户端):
- 服务端收到客户端的 SYN 请求后,向客户端发送一个 SYN + ACK 数据包:SYN 表示服务端同意建立连接。ACK 表示确认收到客户端的 SYN 数据包。
- 此时服务端进入 SYN_RECV 状态。
- 第三次握手(客户端 → 服务端):
- 客户端收到服务端的 SYN + ACK 后,向服务端发送一个 ACK 数据包,表示确认。
- 客户端进入 ESTABLISHED 状态,服务端也随之进入 ESTABLISHED 状态,连接建立成功。
一些细节:
- 客户端的socket也有端口,一般为系统随机分配,不必关注。一般socket通信中的地址包括IP和端口号。
- 服务端的bind()函数,普通用户只能使用1024以上端口,root可以使用任意端口。
- listen()函数的第二个参数+1为已连接队列(ESTABLISHED状态)的大小。在高并发服务程序中应该调大一些。即等待被accept()函数受理的连接数。
- SYN_RECV也被称为半连接。
- CLOSED状态是假想状态,实际不存在。
四次挥手
TCP 四次挥手用于断开连接,确保双方都能安全地关闭通信。断开连接的时候,客户端和服务端需要相互总共发送四个包以确认连接的断开。在socket编程中,这一过程由客户端或者服务端任意一方执行close()函数触发。
1
2
3
4
5
客户端 服务端
| FIN ------------------> |
| <----------------- ACK
| <----------------- FIN
| ACK ------------------> |
- 第一次挥手(客户端 → 服务端):
- 客户端发送一个带有 FIN 标志的数据包,表示想要关闭连接。
- 此时客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(服务端 → 客户端):
- 服务端收到 FIN 后,发送一个 ACK 数据包,表示确认收到关闭请求。
- 此时服务端进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT_2 状态。
- 第三次挥手(服务端 → 客户端):
- 服务端发送一个 FIN 数据包,表示服务端也想关闭连接。
- 此时服务端进入 LAST_ACK 状态。
- 第四次挥手(客户端 → 服务端):
- 客户端收到 FIN 后,发送一个 ACK 数据包,表示确认关闭连接。
- 客户端进入 TIME_WAIT 状态,等待一段时间(通常是 2 倍的最大报文段生存时间)以确保服务端收到 ACK 后再关闭。
- 服务端收到 ACK 后直接进入 CLOSED 状态,连接断开。
细节:
- 主动断开的端在四次挥手之后,socket进入TIME_WAIT状态,持续2MSL(30s/1min/2min),超过这个时间的报文会被丢弃。因为主动关闭时对端可能还在向自己发消息。
- 如果是客户端主动断开,那么TIME_WAIT对socket基本无影响和危害。
- 如果是服务端主动断开,那会有两方面的危害:一是socket没有立即释放,二是端口号只能在2MSL后才能重新启用。遇到过这种情况:5005端口提示地址已经被使用。
TCP缓存
系统为每个socket创建了发送缓冲区和接收缓冲区,应用程序调用send()/write()函数发送数据的时候,内核把数据从应用进程拷贝到socket的发送缓冲区中;应用程序调用recv()/read()函数接收数据,内核会把数据从socket接收缓冲区拷贝到应用进程中。
发送数据即把数据放入发送缓冲区。
接收数据即从接收缓冲区中取数据。
细节:
- send()函数会阻塞吗:会的,如果自己的发送缓冲区或者对端的接收缓冲区都满了,会阻塞。
- 向socket中写入数据后,如果关闭了socket,对端还能收到数据吗:会的,因为关闭socket的事件在接收缓冲区里排在发送的数据之后。
TCP发送端Nagle算法
一个时间段内只能有一个未被确认的TCP分组在传输中。如果当前未确认(没有收到接收端的ACK回复)的数据包(Unacknowledged Packet)存在,新的数据将被暂存在缓冲区中,而不是立即发送。只有在以下情况下才能发送新的数据:
- 缓冲区中的数据足够填满一个数据包(MTU大小)。
- 已经收到之前数据包的确认(ACK)。
对实时性要求很高的系统一般会禁用Nagle算法,譬如证券交易、联机游戏。
TCP接收端ACK延迟机制
延迟确认(Delayed ACK):
- TCP的一种机制,推迟发送ACK,以便在接收到更多数据时合并ACK和回复的数据包。
- Nagle算法和延迟确认的结合可能导致糟糕的延迟现象(“延迟病”),尤其是在交互应用中。