Posix API 和网络协议栈
一、应用管理TCP网络连接的API
对于客户端而言,大多数情况下显式调用bind()是非必须的。
1. API介绍
1.1 socket()
调用socket()会创建一个套接字(socket)对象。套接字由两部分组成,文件描述符(fd)和TCP Control Block(tcb)。
socket会保存一个五元组(remote IP,remote PORT, local IP, local PORT, protocol)作为自身的标识。
1.2 bind()
调用bind将socket绑定到本地的IP和端口(port)上。
1.3 listend()
服务端调用listen()后,开始监听网络上发送给socket的连接请求。
listen(fd,size),fd是socket的文件描述符,size在Linux是指全连接队列的长度,即一次最多能保存size个连接请求。
1.4 connect()
客户端调用connect()函数,向指定服务端发起连接请求。
1.5 accept()
accept()函数只做两件事,将连接请求从全连接队列中取出,给该连接分配一个fd并返回。
1.6 recv()
接收数据函数,此函数将数据从内核态的接收缓冲区拷贝到用户空间。
1.7 send()
发送函数,此函数将数据从用户态拷贝到内核态的发送缓冲区,具体发送由协议控制。
1.8 close()
关闭连接函数。
2. TCP三次握手
三次握手由客户端调用connect()函数开始发起。服务端必须在客户端发起connect之前调用listen()函数,才能接收到连接请求。
2.1 报文
syn是请求报文,seqnum是发送发的报文编号。acknum是接收方希望发送方发送的报文编号,也是通知发送方前面编号的报文都接收到了。
在TCP建立连接过程中,服务端将确认报文和连接请求报文一起发送,所以这个双向连接过程只有三次握手。
2.2 三次握手与API的关系
在TCP协议栈中,服务端第一次接收到syn报文,将该请求存入半连接队列。当接收到ack报文后,服务端会在半连接队列中通过五元组查找是否存在对应的半连接,如果存在,才会将该连接请求转入全连接队列,然后等待服务端调用accept()。
在连接请求放入半连接队列时,服务端会为该连接申请一块TCB。在调用accept()函数之后,该连接获得fd。
connect()函数发起第一次握手并阻塞到第二次握手完成。
2.3 TCP特性
事实上,对于一个操作系统而言,只有65535个端口,这样子它们是怎么创建百万连接的?这就涉及到端口复用(本文不讲解该内容)。
TCP传送消息有一个常见的问题,粘包。粘包问题是由TCP协议栈将我们发送的数据合并或拆分后发送所产生的问题。对此一般由两次解决方法:(默认接收到的数据的顺序的)
- 在协议头加上包长信息
- 在消息包尾部设置分隔符
TCP发送消息有超时重传机制,接收数据确认有延迟ack机制,都是用定时器控制的。TCP主要有四个定时器,分别是:
- 重传定时器:Retransmission Timer
当TCP发送报文段时,创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位。定时器时间一般设置为2RTT(RTT为客户消息到服务端往返的时间,一般需要动态计算)。
- 坚持定时器:Persistent Timer
坚持定时器专门为对付零窗口通知(对方没有开启接收端口)而设立的。当发送端收到零窗口的确认时,就启动坚持计时器,当坚持计时器截止期到时,发送端TCP就发送一个特殊的报文段,叫探测报文段,这个报文段只有一个字节的数据。探测报文段有序号,但序号永远不需要确认,甚至在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端TCP,确认已丢失,必须重传。坚持计时器的截止期设置为重传时间的值,但若没有收到从接收端来的响应,则发送另一个探测报文段,并将坚持计时器的值加倍和并复位,发送端继续发送探测报文段,将坚持计时器的值加倍和复位,知道这个值增大到阈值为止(通常为60秒)。之后,发送端每隔60s就发送一个报文段,直到窗口重新打开为止。
- 保活定时器:Keeplive Timer
每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(每75秒发送一个)还没收到响应,则终止连接。
- 时间等待定时器:Time_Wait Timer
在连接终止期使用,当TCP关闭连接时,并不认为这个连接就真正关闭了,在时间等待期间,连接还处于一种中间过度状态。这样就可以时重复的fin报文段在到达终点后被丢弃,这个计时器的值通常设置为一格报文段寿命期望值的两倍(2MSL)。
3. TCP的11个状态
4. TCP的四次挥手
当通信双方,有一方主动调用close(),TCP开始四次挥手。被动接收到通信关闭通知的一方,在将此次通信业务处理完再调用close()。
通信双方会进入如上图的状态。
当被动方的业务处理比较久时或因其他原因没有及时调用close(),主动方会长时间阻塞在FIN_WAIT2的状态。,该如何解决长时间阻塞在FIN_WAIT2状态:
- 被动方:在允许的情况下,将业务处理放到业务队列
- 主动方:主动方有一个keeplive定时,超过这个时间主动方会直接终止这个通信连接
主动方在接收到ACK并发送了FIN后进入TIME_WAIT状态,此状态一般是持续2MSL(一段报文在网络中的最大存活时间)。
如果双方都在接收的FIN报文前主动发起关闭通知,则状态图如下所示:
5. UDP
UDP提供了比TCP更高的实时性,并且不需要消息确认报文,更适合弱网环境。
在上面提到,TCP有ACK机制,当有报文丢失时,需要从丢失的编号开始重传后面的全部报文(哪怕已有接收,但是发送方不会知道)。这样子会占用带宽。但是,UDP不需要,UDP没有消息确认机制。
TCP还有一个重要的特性是拥塞控制,但是UDP也没有,所以UDP可以用来抢占带宽(嗯,迅雷加速下载就是这种东西)。UDP抢占带宽会导致同一网络环境下其他使用者的可用网络带宽降低。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南