目录
主要通过 TCP 连接整个过程来了解 posix api 是如何执行的。
一、连接建立
Socket()
创建 fd 和空的 tcp control block,通过bitmap算法分配fd。
Bind()
通过 fd 找到 tcb 把 ip 和端口设置进去,对于客户端这个函数不是必做的,不调用的话会随机分配端口。
Listen()
a. 将 tcb->status = TCP_STATUS_LISTEN,如果没有监听的话,客户端连接时会报错:connection refused;
b. 为 tcp 连接分配 tcb->syn_queue(半连接队列), tcb->accept_queue(全连接队列)。
在完成监听之后,才到三次握手的过程。
三次握手
Sequence number 双方都会设置一个随机值。它可以用来保证数据包不丢失,不乱序,不重复。
思考题
三次握手的过程,发生在哪些函数里面?
不发生在任何函数里,三次握手是一种被动的由内核协议栈实现的过程,三次握手是由客户端调用 connect 函数发起,accept 函数是三次握手完成以后,取出一个已经建立好的连接。listen完之后服务器才能三次握手。
第一次握手的连接放在半连接(SYN)队列中,第三次握手完成之后将半连接队列中的 tcp 连接move到全连接(ACCEPT)队列中。
Tcp连接的生命周期,从什么时候开始?
连接在第一次握手协议栈分配一个tcp连接的时候,生命周期就已经开始了。Fd是在accept函数创建的。
第三次握手的数据包,如何从半连接队列找到匹配的节点?
从数据包中就能提取到五元组(源ip,源端口,目的ip,目的端口,协议类型)找到对应的节点。
如何防止syn泛洪
通过 Listen(fd, backlog) 中的 backlog 来防止。
Backlog参数不同时期的意义:
Syn队列数量:有效防止泛洪,但是越来越鸡肋,在接收到数据之前已经隔离了,比如防火墙。
Syn + accept队列总长度,未分配的fd的tcb的数量:有效防止accept队列长时间不处理。
Accept 队列长度(现版本):可以大大提升接入量,新建数量(建链的吞吐量)。
Accept()
分配fd给客户端连接,fd与tcb做映射。在 epoll 中,listenfd 用的是水平触发(LT)还是边沿触发(ET),水平触发符合需求,有数据就会取。如果用 ET accept 该如何写呢?循环 accept 直到fd返回 -1。
二、传输数据
send, recv这些函数仅仅是把数据copy到内核当中或者从内核拿出来,这些函数调用和网络传输实际上是异步的。
如下图所示,send发送多次交给协议栈后,由于tcp是面向字节流的,可能会将其合并成一个数据包发送,而对端收到后也不一定一次读完,可能会分多次接收。一次全部接收或发送和多次接收或发送性能的差异其实不大。
慢启动、拥塞控制、滑动窗口、延迟确认、超时重传这些机制都是 tcp 为了保障数据能发送到对方。
三、断开连接
Close() 和四次挥手
回收 fd,发送 fin 包。
如果前面调用 send 的数据和 close 的 fin 包粘在一起也不会有影响,协议栈接收完以后会有两个事件,一次 recv 会把数据读完,再次 recv 返回是0。
四次挥手特殊情况
1. 如果主动关闭方在发送 fin 包后没有收到 ack 直接接收到了对方的 fin,状态如下图所示:
2. 如果双方都在没有收到对方的 fin 之前调用了 close(),状态如下图所示:
服务器出现大量 time_wait 有可能是上述情况。
P2P的实现
如果双方都调用connect(),并且都没有调用listen(),没有服务器,那么双方都是服务器也都是客户端。
代码实现参考这篇文章:linux网络编程5——Posix API和网络协议栈,使用TCP实现P2P通信
参考资料
https://github.com/0voice