POSIX API与TCP连接的关系学习记录
posix api
posix api是一套标准的操作系统接口规范,用于统一linux,unix系统中的进程,文件,网络等接口。
tcp与tcb的关系

fd操作的API
socket
socket()主要做了两件事情:
- 分配一个文件描述符 fd,返回给用户空间作为访问句柄。
- 分配并初始化一个 TCB(传输控制块),即
struct tcp_sock,这个数据结构是未来完整描述和控制一条 TCP 连接的核心。刚创建时,它只标记了协议等基本信息,五元组等连接标识尚未确定,会在后续的bind()、connect()、accept()中逐步填充。
在内核中,文件描述符fd采用bitmap进行存储,比如当我们创建了要给数字为5的文件描述符,则bit位为5的索引的位置记为1,当我们对这个fd进行close()操作的时候,这个位置记录为0,并释放相应的资源。
bind
bind()函数将本地的源IP,源port写入到TCB控制块的结构体中。
在服务端,bind()函数是必须的,用于固定端口以及IP地址。
在客户端,bind()函数是可选的,如果不调用,connect() 时内核会自动选择合适的源IP和临时端口,再写入 TCB。
建立连接
connect
connect()的作用是:
- 客户端主动发起 TCP 三次握手
- 发送携带初始序列号(ISN)的 SYN(我想和你建立 TCP 连接)报文
- 通过 TCP 四元组与服务端建立连接关系
- 并等待服务端返回 SYN+ACK
listen
listen()主要实现了如下功能:
- 将 socket 状态设置为 LISTEN
- 使该 socket 进入被动监听模式
- 初始化连接请求队列(半连接队列与全连接队列)
- 告诉 TCP 协议栈:
后续允许接收客户端连接请求
半连接队列(SYN Queue)
对端发送syn信息的连接请求,syn queue存储的是处于 SYN_RECV 状态的 request_sock(半连接请求控制块),作用是暂存尚未完成 TCP 三次握手的连接请求。
半连接控制块中包含了如下信息:
源IP
目标IP
源端口
目标端口
ISN序号
MSS
窗口参数
TCP option
等...
全连接队列(Accept Queue)
存储的是已经完成三次握手的完整 TCP socket/TCB,作用是暂存等待应用层 accept() 获取的已建立连接。
建立一个完整的TCB控制块的过程中,通过源IP,目标IP,源端口,目标端口来定位到半连接队列syn queue中的半连接请求控制块,并将其move到Accept queue中,完成一个完整的TCB的创建,这时完成了TCP连接。
syn泛洪
SYN 泛洪就是:攻击者不断发送 SYN,但不完成三次握手,让服务器的“半连接队列”被占满,从而无法服务正常连接。
SYN Cookie解决syn泛洪
SYN Cookie 通过将连接状态编码进 SYN-ACK 的序列号中,使服务器在收到 SYN 时不分配半连接资源,而在收到合法 ACK 后再恢复连接状态,从而避免 SYN Flood 攻击导致的资源耗尽。
accept
-
从**全连接队列(accept queue)**中取出一个已完成三次握手的连接(该连接已拥有自己的 TCB,五元组齐备,状态为
ESTABLISHED)。 -
为这个连接分配一个新的文件描述符 fd,返回给用户进程,后续的读写操作都通过这个新 fd 进行。
而原来的监听 fd(
listen_fd)仍然保持在TCP_LISTEN状态,继续接收新的连接请求,其 TCB 和相关队列不变。
三次握手
TCP连接过程

三次握手中发送的Sequence number 是随机的,告知对方要发送的数据的编号。保证数据不丢失,不乱序。
状态转移图

状态的变化

发送数据
我们在用户态调用send() 时,内核通常会把用户态 buffer 中的数据拷贝到该 socket 的发送缓冲区(wmem);之后 TCP 协议栈根据拥塞控制、流量控制、MSS、网卡队列等条件异步地把数据发送到网络。对端内核收到 TCP 数据后,会按序重组并缓存到该 socket 的接收缓冲区(rmem);应用调用recv()时,再把接收缓冲区中的数据拷贝到用户态 buffer。

需要注意的是:
send() 成功返回,并不代表数据已经到达对端,也不代表对端应用已经 recv() 到了。它通常只表示:数据已经成功从用户态拷贝进本机内核发送缓冲区,或者至少部分数据被接收进内核缓冲区。
另外,TCP 是字节流协议,不保留消息边界。比如你调用两次:
send(fd, "hello", 5, 0);
send(fd, "world", 5, 0);
对端可能一次 recv() 到:
helloworld
也可能分多次收到:
hel
lowor
ld
所以不能认为一次 send() 对应一次 recv()。
send() 不是“发送完成”,而是“交给内核发送”;recv() 也不是“从网络直接读”,而是“从内核接收缓冲区读”。
如何查看最大传输单元mtu:
断开连接
close
close(sockfd)先让 fd 失效;如果这是 socket 的最后一个引用,内核默认会把剩余数据发完,然后发送 FIN,进入 TCP 关闭流程,最后释放资源。
close()函数主要做了这几件事情:
-
回收文件描述符fd
-
如果发送缓冲区还有数据,TCP 会尽量把它们发出去。
-
发送FIN报文,FIN 表示:本端不会再发送数据了。
-
本端 TCP 进入关闭状态机
例如主动关闭方会进入:ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
四次挥手

TIME_WAIT存在的解释
- 一是防止旧连接残留报文在新连接中造成数据污染,通过等待2MSL(Maximum Segment Lifetime × 2)的事件来让就旧连接的“所有残留报文”在网络中自然消失
- 二是保证最后一个 ACK 丢失时可以重传,从而确保 TCP 四次挥手的可靠完成。
两边同时发起close

在 TCP simultaneous close 场景下,由于双方几乎同时发送 FIN 并完成确认,没有明确的主动关闭方,因此双方都可能进入 TIME_WAIT 状态,这是 TCP 协议允许的正常现象。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)