【网络编程·传输层】UDP和TCP的报头


目录

一、端口号划分

二、部分指令

1、pidof(用于查看进程id)

2、netstat(查看网络状态)

三、UDP协议

1、UDP协议格式

2、UDP协议如何进行封装、解包、分用

2.1封装、解包

2.2分用

3、UDP协议的特点

3.1UDP协议的特点

3.2UDP协议的缓冲区

3.3UDP协议16位UDP长度

四、TCP协议(传输控制协议)

1、TCP协议格式

2、TCP协议的可靠性

2.1不可靠性的体现

2.2如何保证可靠性

3、TCP协议的报头

3.1封装、解包(4位首部长度)

3.2分用(16位源端口号、目的端口号)

3.3TCP协议的32位序号和确认序号(滑动窗口、超时重传去重有用到该字段)

3.4TCP协议的16位窗口大小(用于控制发送报文的速度)

3.5TCP协议的6个标志位(区分报文类型)

4、确认应答机制(ACK)

5、超时重传机制

6、连接管理机制

6.1三次握手(建立连接是客户端发起)

6.2四次挥手(断开连接是双方的事情)

7、流量控制

8、滑动窗口/快速重传机制

8.1滑动窗口的本质

8.2滑动窗口的作用

8.3关于滑动窗口的一些问答

9、拥塞控制

10、延迟应答

11、捎带应答

12、理解TCP的面向字节流和UDP的面向数据报

13、粘包问题

14、TCP建立连接异常的问题

五、UDP/TCP协议总结

1、TCP的可靠性和性能

2、UDP和TCP的协议的适用场景

3、关于listen的第二个参数


一、端口号划分

端口号是一个16位的无符号整型,取值范围为0-65535。

0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的。

如下是一些约定俗成的端口号:

ssh服务器, 使用22端口

ftp服务器, 使用21端口

telnet服务器, 使用23端口

http服务器, 使用80端口

https服务器, 使用443

1024 - 65535: 操作系统动态分配的端口号。 客户端程序的端口号, 就是由操作系统从这个范围分配的。 

二、部分指令

1、pidof(用于查看进程id)

pidof httpServer | xargs kill -9//xargs用于将标准输入转换为命令行参数

2、netstat(查看网络状态)

n 拒绝显示别名,能显示数字的全部转化成数字
l 仅列出有在 Listen (监听) 的服务状态
p 显示建立相关链接的程序名
t (tcp)仅显示tcp相关选项
u (udp)仅显示udp相关选项
a (all)显示所有选项,默认不显示LISTEN相关

三、UDP协议

1、UDP协议格式

【网络编程·传输层】UDP和TCP的报头_第1张图片

如果校验和出错,该报文就会被丢弃。

UDP报头本质上是一个结构体对象或者包含位段的结构体。

struct udphdr {
    uint16_t uh_sport;  /* 源端口号 */
    uint16_t uh_dport;  /* 目的端口号 */
    uint16_t uh_ulen;   /* UDP数据报长度(包括头部+数据) */
    uint16_t uh_sum;    /* 数据校验和 */
};

2、UDP协议如何进行封装、解包、分用

2.1封装、解包

我们之前在自定义协议的时候,采用特殊字符进行区分报头和有效载荷。

UDP协议直接按照报头长度固定的方式,认为前8个字节是报头。

2.2分用

UDP的报头中包含16位的目的端口号,在对方应用层中存在了一个绑定了这个端口号的进程,上层绑定该端口号的进程就可以通过文件描述符的形式读到,交给应用层具体的协议进行处理。

3、UDP协议的特点

3.1UDP协议的特点

UDP传输的过程类似于寄信,把信寄出去就完事了,不会去管对方有没有收到。

无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;

不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方导致丢包, UDP协议也不会给应用层返回任何错误信息;

面向数据报: 不能够灵活的控制读写数据的次数和数量,sendto几次就recvfrom几次。应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并; 用UDP传输100个字节的数据: 如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节

UDP成功读取都将读取到一个报文,我们不用考虑报头的问题,只需要做好有效载荷的序列化与反序列化问题即可。

3.2UDP协议的缓冲区

不要考虑多个UDP报文粘连的问题,因为UDP没有真正意义上的发送缓冲区. 调用sendto会直接交给内核(应用层发一个,传输层送走一个), 由内核将数据传给网络层协议进行后续的传输动作;

UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;所以UDP协议是一种全双工的协议。

3.3UDP协议16位UDP长度

UDP协议报头中有一个16位UDP长度的字段,就是说一个UDP报文最大可传输的长度为2^16即64KB(包含UDP首部8字节),如果需要传输大于64KB的数据,就需要应用层多次分包,并在接收端手动拼装。

四、TCP协议(传输控制协议)

1、TCP协议格式

【网络编程·传输层】UDP和TCP的报头_第2张图片

其中报头就是一个结构体对象:

struct tcphdr 
{
    uint16_t th_sport;  /* 源端口号 */
    uint16_t th_dport;  /* 目的端口号 */
    uint32_t th_seq;    /* 序列号 */
    uint32_t th_ack;    /* 确认号 */
    uint8_t th_off;     /* 偏移量,指明TCP报文头的长度,单位是4个字节 */
    uint8_t th_flags;   /* 控制标志,如SYN、ACK、FIN等 */
    uint16_t th_win;    /* 接收窗口大小 */
    uint16_t th_sum;    /* 校验和 */
    uint16_t th_urp;    /* 紧急指针 */
};

2、TCP协议的可靠性

2.1不可靠性的体现

网络传输距离很长,路上会经过多个设备节点,所以在路上可能会存在丢包、乱序、校验错误、报文重复等问题。

2.2如何保证可靠性

【网络编程·传输层】UDP和TCP的报头_第3张图片

一条报文只有收到了对方的应答,才能保证报文的可靠性。双方通信,一定存在最新的数据,例如图中“现在是12点”这条报文,暂未收到对方的应答,所以这条报文有没有被收到是不可靠的。

报文的应答既可以单独发送,也可以和要发送的数据可以合为一条报文进行发送,例如上图,后续服务器回复“我收到现在是12点这条消息了,那我们13点去玩吧!”这就是将应答和要传的数据塞到一条报文里。

无论是客户端给服务器发消息还是服务器给客户端发消息,每条消息都必须给应答。当然不用给纯应答报文做应答哈。

3、TCP协议的报头

3.1封装、解包(4位首部长度)

可以将TCP协议的前20个字节转换为一个结构化的数据,就可以提取出标准报头中的4位首部长度。TCP报文总长度=4位首部长度*4字节=【20字节(最低20字节),60字节】。可以根据首部字段计算得到的TCP报文总大小-标准报文大小,得到的就是剩余报头的大小,剔除所有报文信息,剩下的就是有效载荷。

3.2分用(16位源端口号、目的端口号)

TCP的报头中包含16位的目的端口号,服务器启动时绑定了端口号,就可以找到该TCP报文对应的应用层进程了。操作系统维护网络进程PCB和端口号的数据结构是哈希表。

【网络编程·传输层】UDP和TCP的报头_第4张图片

3.3TCP协议的32位序号和确认序号(滑动窗口、超时重传去重有用到该字段)

【网络编程·传输层】UDP和TCP的报头_第5张图片

客户端可能短时间内发送多个报文,注意这些报文到达服务器的顺序是不确定的。那么客户端收到服务器的应答之后,该如何区分该条应答对应我之前发送的哪条请求呢?这是因为TCP协议中存在32位序号和32位确认序号。

序列号(Sequence Number)是TCP协议中用于标识数据包的字段,它表示发送方发送的数据包的字节流中的第一个字节的序号。序列号的作用是确保数据包的顺序和完整性。在TCP协议中,每个数据包都有一个唯一的序列号。

确认号(ACK Number)是TCP协议中用于确认接收方已经成功接收到数据包的字段,它表示接收方期望下一个收到的数据包的序列号。在TCP协议中,接收方需要发送一个确认包(ACK包)来告诉发送方已经成功接收到数据包。确认包中的ACK Number字段就是接收方期望下一个收到的数据包的序列号。

搞两组序号的原因是TCP协议是全双工的,客户端、服务器均可能充当发送方或接收方,所以需要使用两组序号。

3.4TCP协议的16位窗口大小(用于控制发送报文的速度)

TCP协议在发送数据时,发送方发送过快会导致接收方来不及读取,报文将塞满对方的输入缓冲区,对方缓冲区都满了还发,新发的报文将会被直接丢弃。那么就需要控制发送报文的速度。

TCP协议中存在16位窗口大小,用于填写自己的输入缓冲区的大小。该报文传输至对方,对方就知道自己这边剩余输入缓冲区的空间还剩多少,就能控制发送数据的速度了。

16位窗口大小的存在实现了通信双方交换接收能力的作用,达到流量控制。

3.5TCP协议的6个标志位(区分报文类型)

服务器可能会面对多种不同类型的客户端,可能有发起连接的报文,可能有应答的报文等,为了区分不同类型的报文,TCP报头设置了多种01标志位。

  1. URG:置位了,紧急指针字段才有效
  2. ACK:确认报文段已收到
  3. PSH:催促接收方,让其上层尽快取走输入缓冲区数据(push),即便缓存区未满
  4. RST:复位,强制关闭一个异常连接
  5. SYN:请求建立一个连接
  6. FIN:断开一个连接

4、确认应答机制(ACK)

【网络编程·传输层】UDP和TCP的报头_第6张图片

TCP将每个字节的数据都进行了编号。即为序列号。每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发。

5、超时重传机制

网络传输的一大不可靠性就是丢包问题,TCP协议的解决方式是超时重传。

【网络编程·传输层】UDP和TCP的报头_第7张图片

如果主机A发送数据给B后,由于网络问题丢包了,B自然不会给A应答。如果主机A发现一段时间间隔没有收到确认应答,就会进行重发。

【网络编程·传输层】UDP和TCP的报头_第8张图片

如果主机A发送给主机B后,主机B接收到了报文,但是返回的应答丢了,主机A在一段时间后也会重传。

这样接收方将会收到两份一样的报文,收到重复的数据,也体现了不可靠性,接收方需要进行根据32位序号进行去重。

重传时间:由于数据传输和当时的网络环境有关,所以重传时间不能设定为固定值,而是由操作系统动态的调整重传时间。

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。

Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。

如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。

如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增。

累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。

6、连接管理机制

三次握手是创建TCP连接结构体的基础。TCP连接结构体是维护超时重传、按序到达、流量控制、连接状态等保证可靠性的数据结构。

TCP的面向连接间接的保证了通信的可靠性,同时UDP协议因为是无连接的,并不会维护超时重传、按序到达、流量控制等数据结构,所以UDP是不可靠的。 

【网络编程·传输层】UDP和TCP的报头_第9张图片

  1. CLOSED: 初始状态,表示TCP连接未建立或已终止。
  2. LISTEN: 表示该TCP端口正在等待连接请求。
  3. SYN-SENT: 表示TCP连接请求已经发送给远程主机,等待响应确认。
  4. SYN-RECEIVED: 表示TCP连接请求已被服务器接受,并且已回复以确认连接请求。
  5. ESTABLISHED: 表示TCP连接已成功建立,可以开始进行数据传输。
  6. FIN-WAIT-1: 表示TCP连接已经关闭,但本地端点仍然可以发送数据,等待远程端点的确认。
  7. CLOSE-WAIT: 表示TCP连接已经收到对端的关闭请求,本地端口仍然可以发送数据,然后关闭连接。
  8. FIN-WAIT-2: 表示TCP连接已准备好关闭,但远程端点还有数据需要传输,等待远程端点的关闭请求。
  9. LAST-ACK: 表示TCP连接在本地端口和远程端口两端都已发出关闭请求并得到确认,发送最后的确认完成连接关闭。
  10. TIME-WAIT: 表示TCP连接已正常关闭,正在等待所有相关网络报文被清除,通常需要经过一个固定的时间间隔后方可进入CLOSED状态。

6.1三次握手(建立连接是客户端发起)

【网络编程·传输层】UDP和TCP的报头_第10张图片

SYN_SENT是TCP/IP协议中的一种状态,它代表了一个已经发送了连接请求(SYN)的TCP客户端,并正在等待对方返回连接确认(ACK)的过程。在TCP三次握手连接过程中,当客户端发送SYN包后,客户端的TCP状态就会变成SYN_SENT。在这个状态下,TCP客户端将继续尝试向服务器发送SYN包,直到收到服务器返回的ACK包才能建立连接。

SYN_RECV,它是TCP/IP协议中的一种状态。在TCP连接建立的过程中,服务器端在收到客户端的SYN包后,会发送ACK和SYN包作为回复,表示已经成功接收到客户端的连接请求,并且向客户端发起连接确认请求。此时服务器的TCP状态就变成了SYN_RECV。在这个状态下,服务器会等待客户端的ACK包,以完成TCP三次握手连接的过程。如果在一段时间内没有收到客户端的ACK包也不用担心,服务器会认为自己的ACK+SYN包被丢失了,会发起超时重传。

客户端正在第三次握手发出ACK时就认为三次握手完成,服务器只有收到了客户端的这个ACK应答,才认为三次握手完成。客户端、服务器三次握手完成的时间是有差别的。三次握手不一定非得成功,尤其是客户端最后一个ACK的是否丢失客户端是不知道的,但是我们有超时重传等机制解决这个问题。

为什么一定要三次握手?

因为TCP在传输层,属于操作系统管理,维护TCP连接是有时间和空间成本的。

如果握手次数仅一次,客户端仅发送一次SYN即建立连接成功,那么某些恶意分子就可以使用客户端疯狂connect发起连接,服务器维护连接是有成本的,会导致服务器上的可用连接越来越少(SYN洪水)。

如果握手次数是两次。和握手一次的情况是一样的,客户端发送完SYN后,服务端返回SYN+ACK后,服务端即认为连接建立成功。同样会引发SYN洪水。

为什么要采用三次握手呢?首先TCP是全双工的通信协议,三次握手是验证全双工通信正常与否的最小代价。其次为了防止SYN洪水,三次握手的最后一次发起连接的是客户端,客户端最后一次发出ACK的时候,代表连接成功,客户端想在服务端建立连接就必须现在客户端建立连接。客户端想发动SYN洪水攻击其本身也会受到同等的消耗,三次握手可以有效规避单主机下的SYN攻击。(不过需要注意的是,防止SYN洪水攻击,并不是TCP协议来解决的,TCP协议的主要任务是网络通信!)

如果握手次数是四次,是不行的,最后一次服务端发送ACK应答后,就会在自己这里建立连接,只要客户端想办法不收到这个连接,就不会在客户端建立连接关系,存在SYN洪水攻击的风险。

如果握手次数是大于三次奇数次,握手三次已经能满足要求了,没必要增加握手次数浪费双方的时间。

6.2四次挥手(断开连接是双方的事情)

断开连接是双方的事情,需要征得双方同意。

【网络编程·传输层】UDP和TCP的报头_第11张图片

四次挥手阶段,客户端和服务器都可以主动发起断开连接。

一方想断开连接的时候,另一方可能也想断开连接,可能会导致四次挥手变成三次挥手。

主动断开连接的一方,最终状态是TIME_WAIT,被动断开连接的一方,两次挥手完成会进入CLOSE_WAIT状态。如果被断开一方出现大量的CLOSE_WAIT状态,要么是服务器压力过大来不及执行close(服务端还有数据没有推送完),要么是你的close直接就是忘写了。

四次挥手完成,主动断开连接的一方会维持一段时间的TIME_WAIT。

维持多久:这个时间是两倍的MSL(MSL:一个TCP报文段在网络中的最长生存时间,超过这个时间,报文段将被丢弃。MSL的默认值为2分钟(CentOS7为1分钟)。)

为什么要维持一段时间:1、因为四次挥手的最后一次ACK应答,发起方无法确认对方是否收到,需要等待一段时间,如果被动方补发了第三次挥手的FIN报文,这时主动方就可以进行第四次挥手的ACK应答报文补发,尽可能保证四次挥手成功。2、双方在断开连接的时候,网络中还有滞留的报文,需要等待所有相关网络报文被清除。(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);

若服务器先关闭,一段时间内重启服务器将不能再绑定上一次绑定的端口,必须等上一次服务器状态变为CLOSED后,那个端口才可以重新用于连接。对于访问量大的服务器,如果挂了,那么大量的端口在一段时间内无法再次被绑定,所以这是不合理的。使用setsockopt()允许创建端口号相同但IP地址不同的socket描述符。

GETSOCKOPT(2)         
#include           /* See NOTES */
#include 
int getsockopt(int sockfd, int level, int optname,
              void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
              const void *optval, socklen_t optlen);
int opt=1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

7、流量控制

上面章节说过,TCP报文中存在16位窗口大小,实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是窗口字段的值左移 M 位。用于交换双方接收缓冲区的大小。在三次握手时,双方就在TCP报文中顺带的完成了接收缓冲区的大小的交换。

【网络编程·传输层】UDP和TCP的报头_第12张图片

当接收端接收缓冲区已满,会有两种策略:1、当接收端上层拿走了数据,接收端会向发送方发送一个窗口更新通知;2、发送端会时不时的询问接收端的窗口大小。

8、滑动窗口/快速重传机制

8.1滑动窗口的本质

【网络编程·传输层】UDP和TCP的报头_第13张图片

滑动窗口本质是发送缓冲区的一部分,发送缓冲区中已发送未应答的部分称为滑动窗口。

【网络编程·传输层】UDP和TCP的报头_第14张图片

滑动窗口的移动本质是数组下标的移动。

8.2滑动窗口的作用

【网络编程·传输层】UDP和TCP的报头_第15张图片

  1. 流量控制:TCP滑动窗口机制允许接收方控制发送方的发送速率,以确保接收方能够在处理能力范围内接收和处理数据。接收方通过调整滑动窗口的大小来告知发送方,它还能够根据接收方的处理能力动态地调整滑动窗口的大小。滑动窗口一定程度上提高了TCP对数据的传输效率。
  2. 拥塞控制:TCP滑动窗口机制能 够帮助控制网络中的拥塞情况。当网络发生拥塞时,接收方可以减小滑动窗口的大小以减缓数据的发送速率,从而避免对网络造成进一步的负载压力。通过动态调整滑动窗口的大小,TCP能够根据网络的拥塞程度自适应地控制数据的传输速率。
  3. 可靠性传输:TCP滑动窗口机制提供了可靠的数据传输。发送方只有在收到接收方确认的数据后才会发送下一个滑动窗口的数据,接收方同时也会对接收到的数据进行确认。如果发送方没有收到确认或者接收方收到失序的数据,发送方可以通过滑动窗口机制选择性重传这些数据,以确保数据的可靠传输。

8.3关于滑动窗口的一些问答

滑动窗口开始的大小是怎么设定的?未来如何变化:

滑动窗口大小指的是无需等待确认应答而可以直接发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。滑动窗口的大小和对方的接收能力有关。 

滑动窗口一定是向右滑动吗?会向左滑动吗:

滑动窗口可能会向右滑,也可能不动(对方接收缓冲区满了,暂停滑动)。但绝不会向左滑,因为左边的数据都是已发送已应答的。

滑动窗口的大小会改变吗?如何改变:

滑动窗口根据对方的接收能力动态变化,可能增大,也可能减小。

滑动窗口中的数据收到应答的先后顺序并不相同,如果先ACK中间或者尾部的数据,滑动窗口应该怎么处理:

滑动窗口ACK乱序不影响,只要最左侧的ACK到了,滑动窗口就右移。

最怕的发生丢包问题:

1、如果是数据没丢,而是ACK丢了。这个不怕,因为TCP协议中有32位确认序号,例如1001-2000序号的ACK丢了,只要客户端收到2001-3000序号(比2000大就行)的ACK,就说明之前的报文都收到了,此时直接将窗口滑动至3001。

2、如果数据丢了(服务端没收到数据,当然ACK也是没有的),客户端后续收到三个相同序号的ACK包时,会触发快速重传机制

如果向后滑动,后方空间不足该怎么处理:

发送缓冲区被操作系统内核组织成了环形的结构。(和环形结构的生产者消费者模型类似)

9、拥塞控制

路上的车多了,难免会堵车。网络也一样,同时有多台主机进行TCP通信,也会造成网络拥塞。各个接收主机突然出现大量的丢包现象,如果此时贸然对大量丢失的报文进行超时重传,对网络状态的缓解无疑是雪上加霜。

当发生拥塞时,TCP会启用慢启动机制,所有主机先发送少量的数据用于探路(减少了同一时间发送的报文数量,大大缓解了网络压力),摸清网络的状况后再提升发送速率。如下图:

【网络编程·传输层】UDP和TCP的报头_第16张图片

图上还有一个拥塞窗口(大小根据网络状态变化)的概念,体现网络的的存储能力。

慢启动机制:

1、发送开始的时候, 定义拥塞窗口大小为1;

2、每次收到一个ACK应答, 拥塞窗口加1; (翻倍,1变2,2变4,4变8。指数级增长,前期利用指数增长进行慢启动探路,探路若成功,说明网络OK,同时指数快速爆炸,迅速将通讯速度恢复到最大。拥塞窗口在增长后期翻倍增长已经没有意义了,因此后期不能使拥塞窗口单纯的加倍,需引入一个叫做慢启动的阈值。当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长)

3、实际发送的窗口大小=min(拥塞窗口,对端主机提供的TCP报头中的16位窗口大小);

对于第三点的解释:

发送端发送能力取决于以下三种情况:

1、发送端的滑动窗口大小;

2、对端主机提供的TCP报头中的16位窗口大小。(对端接收缓冲区)

3、当前网络的拥塞窗口

【网络编程·传输层】UDP和TCP的报头_第17张图片

1、当TCP开始启动的时候, 慢启动阈值等于窗口最大值; (图中等于24)

2、在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1(图中阈值从24降为12)

10、延迟应答

发送端发了一个报文至对端的接收缓冲区中,对端接收到该报文后发现接收缓冲区还剩1M,如果立即进行应答,告诉发送端我的接收缓冲区还剩1M,那么发送端下一次将会按照1M的容量进行流量控制。如果对端采取延时应答,那么上层将有概率取走一些接收缓冲区中的报文,这样就可以应答一个更大的空间,同时可以减少应答次数(TCP中有32位确认序号,可以保证之前序号的报文已经收到了)。延迟应答可以增加网络吞吐量

当然并不是说每个包都延迟应答。

1、数量限制: 每隔N个包就应答一次;

2、时间限制: 超过最大延迟时间就应答一次;(一定小于超时重传时间)

具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;

11、捎带应答

一端给另一端发消息,另一端为了减少报文的传输,会把ACK标志位置1并且带上32位确认序号的同时,再加上有效载荷一并发送给对端。(携带有效载荷的应答,这也是TCP中比较常见的通信方式,能提高消息的传输效率)

12、理解TCP的面向字节流和UDP的面向数据报

应用层调用write的本质就是将数据拷贝到发送缓冲区,如果数据满将会被挂起;应用层调用read的本质就是将数据从接收缓冲区中读出,如果数据满将会被挂起。由于双方各有一个读/写缓冲区,所以TCP是一种全双工的通信协议。

对于TCP协议来说,它并不关心应用层给的数据中,多少长度为一个报文,在它眼里,应用层给的全部是一个个字节。由于缓冲区的存在,TCP协议的读和写并需要匹配,上层给多少或者想要多少以及如何将这些字节组合成一个个报文的,TCP根本不关心,想要组合成报文,应用层自己想办法例如应用层可以调用多次write写入总计写入100字节,对端可以调用一次read将100字节读出。(UDP是面向数据报,发一次必须读一次,每次收到的必定是一个报文,后续再对这个报文进行序列化和反序列化即可。对于这种独立的报文的传输称为面向数据报)

13、粘包问题

上面说了,TCP不管报文的合成,需要应用层自己解决粘包问题。

解决粘包问题的本质是明确报文和报文之间的边界。

如何明确边界:

1、定长读取策略:对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;

2、包头约定包长策略对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;

3、特殊字符分隔策略对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序员自己来定的, 只要保证分隔符不和正文冲突即可)

14、TCP建立连接异常的问题

1、TCP客户端和服务器某一方或者全部挂掉了

在挂掉之前,TCP客户端和服务器是存在连接的。一旦某一方挂掉了,操作系统会自动发起四次挥手,正常断开连接。(和手动调用close没有区别) 

2、机器重启

OS都寄了进程当也寄了。重启前操作系统会把进程先退出,所以这种情况和进程正常退出没有区别。

3、机器掉电或者网线被拔

被掉电的一端根本来不及给对端发消息就寄了,对端会定期发起询问,多次无应答后将会断开连接。(对于连接的管理机制,大都是应用层来管理的(HTTP、HTTPS等)应用层的某些协议, 也有一些这样的检测机制,例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ在断线之后, 也会定期尝试重新发起TCP三次握手进行连接)

五、UDP/TCP协议总结

1、TCP的可靠性和性能

可靠性:

校验和、序列号(按序到达)、确认应答、超时重传、连接管理、流量控制、拥塞控制

提高性能:

滑动窗口、快速重传、延迟应答、捎带应答、流量控制、拥塞控制

2、UDP和TCP的协议的适用场景

UDP用于对高速传输和实时性要求较高的通信领域, 例如, 直播、早期的QQ, 视频传输等. 另外UDP可以用于广播

TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;

用哪种协议最终还是得按公司领导说了算。

3、关于listen的第二个参数

int listen(int sockfd, int backlog);

TCP协议要为上层维护一个全连接队列,队列里面是已经完成三次握手的待连接对象,这个队列不能没有,它的存在就好比去外面吃饭,需要排队的场景,上一个顾客走了,在排队的用户马上可以接上来,大大提高TCP的速度,当然这个队列也不能太长,因为队伍太长你还愿意等吗,TCP也一样,队列太长没必要,因为维护这个队列是需要开销的。这个全连接队列受listen的第二个参数的影响。

listen的第二个参数设置为2,在第四次建立连接的时候(客户端36648),可以看到客户端认为自己建立连接成功了,但是在服务器端,连接建立的状态是SYN_RECV。(客户端发起建立连接的请求,服务器收到后处于SYN_RECV状态并向客户端发送SYN+ACK应答,使客户端建立成功连接,但是客户端发起第三次握手的时候会无视该客户端的ACK,说话以服务器并不会建立连接,仍处于SYN_RECV的状态称为半连接状态

【网络编程·传输层】UDP和TCP的报头_第18张图片

tcp底层最多允许backlog+1个全连接,后续来的全部都是半连接。(半连接如果没有尽快完成握手,会被server自动关掉)

1. 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)

2. 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)

你可能感兴趣的:(网络编程,网络,udp,tcp/ip)