文章目录
Posix API和网络协议栈,使用TCP实现P2P通信
本文介绍了linux Posix API涉及网络编程的常用函数,并解释其原理和涉及的网络协议栈。最后,利用TCP三次握手中同时打开建立连接的情况实现了P2P通信。
1. socket()
socke()的主要作用是分配fd和建立tcb(tcp control block)。
- 分配fd,内部通过一个bitmap标记一个fd是否已被使用。
- 建立tcb,alloc(),此时还没有分配接受缓冲区和发送缓冲区。
TCB 是操作系统内核中用于跟踪每个 TCP 连接的核心数据结构,包含了与连接相关的状态信息,比如源地址、目标地址、端口号、窗口大小、序列号等。
2. bind()
将本地ip和和端口绑定到fd对应的tcb中。客户端fd如果bind了的话,则本地端口就会固定。
3. listen()
- 将监听套接字tcb中的status设置为TCP_STATUS_LISTEN。
- 为监听套接字tcb分配两个队列:半连接队列syn_queue和全连接队列accept_queue
- syn_queue,存储还未完成三次握手的请求。Linux 中的
tcp_max_syn_backlog
决定了syn_queue
的最大容量。 - accpet_quque,存储已经完成三次握手,等待应用层调用accpet()的请求。如 Linux 中的
somaxconn
决定了其容量。
- syn_queue,存储还未完成三次握手的请求。Linux 中的
int listen(int sockfd, int backlog);
其中backlog最早70年代指的是syn_queue队列的长度,也就是收到SYN但是还没有完成三次握手的请求的队列长度。中间指的是两个队列的长度之和的最大值。现在指的是accpet队列最大长度。这样应用程序可以主要关注待处理连接请求的数量。
tcp连接的生命周期,从什么时候开始?
从收到第一个SYN报文的时候开始,开始创建tcb,连接开始建立。
第三次握手的数据包,如何从半连接队列查找匹配的节点?
每一个TCP报文段中都包含源ip、目的ip、源端口等五元组信息,据此可以查找匹配。
如何解决SYN泛洪/DDOS攻击?
限制半连接队列的最大长度。
4. connect()
connect()用于客户端主动建立与对端的连接,可能有两种情况。
三次握手主要是为了通信的同步,交换序号信息,保证之后的通信不丢失、不乱序。
- 正常主动打开

- 同时打开
适用于P2P通信,需要双方同时调用connect()。

5. accept()
用于从全连接队列中取出一个连接,并分配fd。
水平触发
每次只接受一个连接,效率较低。
边缘触发 + 非阻塞IO
使用一个循环,如果accept返回-1并且errno为EAGAIN或者EWOULDBLOCK,则退出循环。
while (1)
{
fd = accept(listen_fd, &clnt_addr, &clnt_addr_len);
if (fd == -1)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
}
// 处理新连接
}
6. read()/write(), recv()/send()
这些IO函数都是与系统内的接收缓冲区和发送缓冲区之间传输数据,而非直接与网卡。tcp报文段的接受与发送和这些IO函数没有一对一的关系。
7. 内核tcp数据传输
TCP协议头
7.1 TCP流量控制
滑动窗口
TCP使用了滑动窗口中的回退n自动重复请求机制。原理是当某个数据段未被正确接收,则接收方不会确认其后续的数据段;当发送方发现超时或者收到重复的ACK时,会回退到未被确认的第一个数据段继续发送。
延迟确认
如果数据乱序到达,可以等待一段时间,在进行确认。
超时重传
7.2 TCP拥塞控制——慢启动/拥塞避免/快速恢复/快速重传
慢启动是 TCP 在建立连接或从网络拥塞中恢复时使用的拥塞控制算法,旨在探测网络的可用带宽,并逐步增加发送速率,避免在一开始就超载网络。
发送端在收到一个ACK之前,发送窗口的大小是由接收端接收窗口大小和本端拥塞窗口的大小决定的。
慢启动算法是指发送端拥塞窗口在到达慢启动阈值之前(称之为慢启动姐阶段