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协议栈将我们发送的数据合并或拆分后发送所产生的问题。对此一般由两次解决方法:(默认接收到的数据的顺序的)

  1. 在协议头加上包长信息
  2. 在消息包尾部设置分隔符

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抢占带宽会导致同一网络环境下其他使用者的可用网络带宽降低。


参考博客:Posix API 和网络协议栈

猜你喜欢

转载自blog.csdn.net/m0_58687318/article/details/126712667