网络:TCP、UDP协议

目录

netstat

pidof

xargs

UDP协议

TCP协议

setsockopt函数

确认应答机制

超时重传机制

流量控制

滑动窗口

拥塞控制

延迟应答

捎带应答

面向字节流

粘包问题


在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信

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

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

知名端口号:

ssh服务器,:使用22端口

ftp服务器:使用21端口

telnet服务器:使用23端口
http服务器:使用80端口
https服务器:使用443


netstat

netstat是一个用来查看网络状态的重要工具

常用选项:

n:拒绝显示别名,能显示数字的全部转化成数字

I:仅列出有在Listen (监听)的服務状态

p:显示建立相关链接的程序名

t :(tcp)仅显示tcp相关选项

u :(udp)仅显示udp相关选项

a :(all)显示所有选项,默认不显示LISTEN相关

后面使用时最常用的就是netstat -nltp / -natp


pidof

在查看服务器的进程id时非常方便

用法:pidof [进程名]

功能:通过进程名,查看进程id


xargs

xargs是将标准输入的内容,转化成为命令行参数

例如我们前面所学的kill命令,kill -9 [pid]可以杀死这个进程,这里的pid是按照命令行参数的方式传给kill的,而管道则是标准输入的方式,下面举个例子:

我们前面实现的http的demo代码,在左边窗口运行HttpServer,右边窗口grep查看,可以看到是pid为22461的进程

按照前面的使用方式,再grep查出来PID后,再kill -9 PID

但是我们今天学习了pidof和xargs后,可以直接如下图所示的方式:

pidof HttpServer | xargs kill -9

即pidof HttpServer找到进程的pid,通过管道的方式标准输入,再通过xargs将标准输入的内容,转化成为命令行参数,所以就可以执行kill命令了


UDP协议

几乎任何协议都要首先解决两个问题: a. 如何分离(封装) b.如何交付

如何分离:

UDP采用的是定长报头的策略,其中有16位的源端口号、16位的目的端口号、16位的UDP长度、16位的UDP检验和

报头标准长度8字节,所以可以轻松将报头和有效载荷分离

如何交付:

根据报头中的16位端口号,进行向上交付,因为进程bind了端口号

所以我们在应用层编写代码的时候,每一次写端口号的时候,都喜欢uint16_t,因为协议用的端口号是16位的

udp如何正确的提取整个完整报文的呢?

udp有固定长度的报头,即16位udp长度,再16位udp长度-8就是有效载荷的长度

因此UDP是具有将报文一个一个正确接收的能力的

16位UDP长度,,表示整个数据报(UDP首部+UDP数据)的最大长度

如果校验和出错, 就会直接丢弃


理解报文本身

报头在内核中其实就是一个结构体:(位段)

struct udp_hdr

{

uint32_t src_port:16;

uint32_t dest_port:16;

uint32_t udp_len:16;

uint32_t udp_check:16;

}


UDP特点:不可靠、无连接、面向数据报

不可靠:(不可靠是中性词,不含贬义)没有确认机制,,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息
**无连接:**知道对端的IP和端口号就直接进行传输,不需要建立连接
**面向数据报:**不能够灵活的控制读写数据的次数和数量,应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并


UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作
UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致,如果缓冲区满了,再到达的UDP数据就会被丢弃

UDP的socket既能读,也能写,这个概念叫做全双工

需要注意的是:UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是2^16=64K(包含UDP首部)

然而64K在当今的互联网环境下,是一个非常小的数字,如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装


基于UDP的应用层协议

**NFS:**网络文件系统
**TFTP:**简单文件传输协议
**DHCP:**动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
**DNS:**域名解析协议


TCP协议

TCP全称为 "传输控制协议

TCP报文本身结构:

首先解决两个问题: a. 如何分离(封装) b.如何交付

如何交付:

TCP报头也有16位的源端口号和16位的目的端口号,所以可以进行向上交付

如何分离:

在TCP报头中包含了4位首部长度,0000~1111,即0~15,又因为单位是4字节,所以报头大小就是0*4 ~ 15*4也就是0~60字节

又因为最开始的基础长度就是20字节(可能会有选项),所以报头的长度范围就是20~60字节,而这个首部的值假设为x,最小就为x*4==20,x==5,最大为x*4==60,x==15,所以x取值就是5~15,转换为二进制就是[0101, 1111]

因此解包顺序如下:
1.提取20字节标准报头
2.根据标准报头,提取4位首部长度,如果* 4 =20,就读取完成,若大于20,执行下面步骤3
3.读取[提取4位首部长度* 4- 20]字节数据,即为选项
4.读完了报头,剩下的都是有效载荷

注意:TCP是没有整个报文的大小的(面向字节流)

读取的TCP报头在内核中其实就是一个struct tcp_hdr的结构体对象,TCP报头也是一个位段结构


可靠性的理解

并不存在100%可靠的协议

在网络中,发送数据的双方,都无法保证自己作为最新发送数据的一方,发送出去的数据能够被对方收到

但是在局部上,我们能做到100%可靠

我所发出去的所有的消息,只要有匹配的应答,那么我就能保证,我刚刚发出去的消息对方一定收到了
TCP协议的确认应答机制:但是只要一个报文收到了对应的应答,就能保证我发出的数据对方收到了


序号与确定序号

client一次可能向服务端发送多个报文,就有一个问题,发送的顺序,不一定是接受顺序

那么client如何确认,哪一个应答,对应哪一个请求呢?

其实每一个请求都有一个序号,而对应的应答就有一个确认序号,确认序号一般是序号+1,即序号1000,则确认序号是1001

确认序号表示:确认序号对应的数字,之前的所有的报文已经全部收到了,告诉对方下次发送时,从确认序号指明的序号发送

总结:

1.将请求和应答进行一一对应

2.确认序号表示的含义:确认序号之前的数据已经全部收到

3.允许部分确认丢失,或者不给应答(由2得出的结论,例如收到了2001,1001不论收没收到应答,都默认收到了)

4.为什么要有两个字段数字,不能只使用一个序号呢?

因为任何通信的一方,工作方式都是全双工的,在发送确认的时候,也可能携带新的数据,例如server端既想给对方确认(要有确认序号),又想同时给对方发送它的消息(要有序号)

5.序号为1000 2000 3000发送过去变为了 2000 1000 3000,乱序了,因为任何一方都会收到报文,报文中会携带序号,所以可以对序号进行排序,就可以解决乱序的问题了


TCP中有发送缓冲区和接收缓冲区,我们前面所使用的send并不是直接发送到网络中,而是发送到发送缓冲区中,如何发送、什么时候发送、丢失了怎么办等问题,都是由TCP决定的

同样recv也不是从网络中读取的,而是从接收缓冲区中读取的

因为TCP有发送和接受缓冲区,所以如果我们有client和server, 我们就有了两对接受和发送缓冲区,一方发送是由该方的发送缓冲区向对方的接收缓冲区中发送,而自己的接收缓冲区并不影响,所以TCP就能够实现全双工通信


16位窗口大小

client发送数据,既不能太快,也不能太慢,那么如何保证发送方发送数据,不要太快,或者太慢了呢?

需要进行流量控制,接收方需要向发送方同步自己的接收能力,由于发送方发送的数据是发送到接收方的接收缓冲区的,所以接收方的接收能力是由接收缓冲区的剩余空间的大小决定的

而报头中的16位窗口大小存储的就是接收缓冲区的剩余空间的大小,这里的16位窗口大小填的是接收方自己的剩余空间

由于双方通信是存在两对接受和发送缓冲区的,所以就可以实现两个方向的流量控制


6个标记位

6个标记位:1bit位是表示某种含义的

那么为什么需要多个标记位呢?

因为服务端会受到大量的不同的报文,而标记位是可以标记报文类型的

各个标记位的含义:

**SYN:**该报文是一个连接请求报文

**FIN:**该报文是一个断开连接请求的报文

**ACK:**确认应答标志位,凡是该报文具有应答特征,该标志位都会被设置为1

大部分网络报文ACK都是被设置为1的;也有例外,例如第一个连接请求报文

**RST:**reset,连接重置

在连接建立后,双方通信过程中,有可能连接建立异常,导致对方不知情,反而继续发消息,我们要关闭掉对方连接,让对方重新建立连接

**PSH:**push,督促对方尽快将数据进行向上交付

当发送方发现接收方的16位窗口大小为0,即接收缓冲区为0时,就发送PSH标记位,表明督促对方尽快将数据进行向上交付

**URG:**紧急标志位,需要配合报头中的16位紧急指针使用

16位紧急指针表示特定的一个数据,在有效载荷位置中的偏移量,在指向位置只有一个字节的数据是紧急数据,一般用于服务器的异常检测


三次握手

建立连接时需要三次握手,断开连接需要四次挥手

因为有大量的client将来可能连接server,所以server端一定会存在大量的连接,OS要不要管理这些连接呢?当然要:先描述,在组织

所谓的连接:本质其实就是内核的一种数据结构类型,建立连接成功的时候,就是在内存中创建对应的连接对象,再对多个连接对象进行某种数据结构的组织

维护连接是有成本的(内存+ cpu)

再具体说明三次握手四次挥手时,需要注意的是发送的不是标记位,而是整个报文

下面是三次握手的示例图:

其中我们发现建立连接时双方连接的线是斜的,这表明是时间线,即接收方收到报文的时间一定是小于发送方的

下面思考一个问题:是不是三次握手一定要保证成功?

不一定,因为前两次都有应答,而最新发送的ACK,发送出去后,不能保证对方有没有收到

当client端第三次将ACK报文发出,自己的状态就改为了ESTABLISHED,表示连接建立

但并不能保证这个ACKserver端一定能够收到,可能会丢失

为什么要3次握手?

1. server可以嫁接同等的成本给client

例如如果只进行一次握手,那么client端每次只需要发送SYN,并不关心server端是否建立成功连接,client端可以连续发送大量的SYN,自己没有受到什么影响,反而会导致服务器出现异常,这种情况被叫做SYN洪水;

同理可得,如果是两次握手,client端同样可以连续发送大量的SYN,也根本不理会server端发送的SYN+ACK,可以选择直接丢弃,这时同样有SYN洪水的问题,

所以选择三次握手,当client端发送SYN,server端发送SYN+ACK后,client端还需要建立连接才能发送ACK报文,此时如果发送失败,server端没有受到任何影响,反而是client端成本增加

2.验证全双工

在client端和server端都可以验证全双工,因为第一次client端发送报文,可以验证自己是否能够发送,而发送是否成功可以由server端给client端发送的ACK报文判断,此时就可以保证client端数据既能发出又能被接收

而server端在第一次收到client端发送的SYN数据后,表示自己可以接收消息,接收信道是正常的,所以也要保证自己能够发送数据,即向client端发送SYN+ACK,那么如何证明自己可以发送成功呢,可以通过是否能够成功收到client端第三次发送的ACK判断


四次挥手

四次挥手示例图:

四次挥手是因为连接断开时双方的事情,需要征求双方的同意

四次挥手也有可能是三次挥手,因为server端的ACK和FIN标记位也有可能同时放在一个报文中,给client端发送过去

如果我们发现服务器具有大量的close_ wait状态的连接的时候,原因是什么呢?

应用层服务器写的有bug,忘了关闭对应连接的sockfd(文件描述符),所以服务器一直无法发送FIN


虽然4次挥手已经完成,但是主动断开连接的一方要维持一段时间的TIME_WAIT状态,才能变为CLOSED状态,在TIME_WAIT状态下,连接其实已经结束,但是地址信息ip, port依旧是被占用的

所以这就出现了之前绑定一个端口的进程,该进程结束后可能暂时无法继续绑定,而这在实际情况中是非常严重的事故,如果一个服务器在人流量非常大的时间挂掉了,几分钟之内无法重新连接,会造成非常大的经济损失,所以在创建套接字时,需要调用setsockopt函数,此时如果出现服务区崩溃退出的情况,就可以立即重启了,不会需要等待一段时间才能重新绑定ip、port:

setsockopt函数

所以在Sock.hpp中,在创建套接字函数Socket中,需要加入如下语句:

这样就能够地址复用,让对应的端口号和ip地址,可以在TIEE_WAIT状态期间,被服务端立刻绑定


上面解决了TIME_WAIT状态如何地址复用,那么为什么要有这个TIME_WAIT状态呢?

TIME_WAIT状态需要等2MSL的时间,才变为CLOSED状态,其中MSL就是指报文从一端到另一端所需的最大时间

TIME_WAIT状态等待的这2MSL的时间,可以尽量保证历史数据从网络中消散,尽量避免历史数据影响新建立的连接

也可以尽量保证最后一个ACK报文能够被对方收到


确认应答机制

我们理解TCP的发送缓冲区:我们可以将TCP的发送缓冲区看做一个char sendbuffer[N]这样的数组,所以就相当于天然拥有了下标,我们把TCP的发送缓冲区当做了char类型的数组,而一个char类型的大小就是1字节,所以这就叫做面向字节流

TCP将每个字节的数据都进行了编号,即为序列号,当我们想发某一段数据时,我们把这一段数据拿出来,这段数据最后一个元素的下标就作为它的序号

每一个ACK都带有对应的确认序号,确认序号一般是序号+1,意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发


超时重传机制

主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;

如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 主机A就断定发送的数据丢包了,就会进行重发

那么这里就会有两种情况,第一种就是上述所说的,报文丢包了;第二种是发出去的报文被对方收到了,但是对方发过来的应答丢包了

如果是第二种情况,带来的后果就是主机B有可能收到主机A发来的两份甚至更多的一样的数据,那么这时主机B就需要对数据进行去重

主机B收到了重复报文,可以通过序号进行去重


那么超时重传的超时时间应该如何设置?

如果超时时间设的太长, 会影响整体的重传效率

如果超时时间设的太短, 有可能会频繁发送重复的包

但是这个时间的长短, 随着网络环境的不同, 是有差异的,

网络好的时候,超时时间应该短一些,网络不好的时候,超时时间应该长一些

所以超时时间不是固定的

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

Linux中超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍

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

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

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


流量控制

主机A给主机B发消息,会根据主机B的接收能力,来动态调整自己发送的数据量,这种机制就叫做流量控制

接收端将自己可以接收的缓冲区大小放入TCP 首部中的 "16位窗口大小" 字段, 通过ACK端通知发送端

窗口大小字段越大, 说明网络的吞吐量越高

接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端

发送端接受到这个窗口之后, 就会减慢自己的发送速度

如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端

因为是全双工的,所以流量控制同样是双向的


在进行流量控制的时候,第一次发送数据我们如何得知对方的接收能力呢?我们所要的对方的接收能力(即接收缓冲区中剩余空间的大小,如何得知呢?)

交换报文,第一次发送数据 并不是 第一次交换报文

三次握手的时候,交换双方的接收缓冲区的大小,TCP报头中有16位的窗口大小,也就是接收缓冲区的大小


滑动窗口

像上面所说的超时重传、流量控制等机制都是为了保证可靠性,而这里的滑动窗口则是为了提高网络数据发送效率的机制

确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答;收到ACK后再发送下一个数据段

这样做有一个比较大的缺点,就是性能较差,尤其是数据往返的时间较长的时候

既然上述这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)


什么滑动窗口呢?如下图所示:

在发送缓冲区中,可以直接发送并且暂时不需要收到应答的数据所在的区间,称之为滑动窗口

滑动窗口在自己的发送缓冲区中,属于自己的发送缓冲区中的一部分

确认应答过的数据,就可以从缓冲区删掉

滑动窗口的本质:

sender方,可以一次性向对方推送数据的上限

滑动窗口也必须有上限,即由对方的接收能力决定(目前认知)

滑动窗口:

1.既想给对方推送更多数据

2.又想要保证对方来得及接收


由于我们将缓冲区理解为char类型的数组,所以滑动窗口本质就是指针或者下标

即int win_ start, win end

win_ start和win end更新时:

win_ start = 收到的应答报文中的确认序号

win end = win_ start + 收到的应答报文中的窗口大小

1.滑动窗口必须向右移动吗?

不一定,因为如果在给对方发送数据时,对方上层一直不取数据,只会导致滑动窗口左侧不断向右移动,而右侧可以不动,象征着对方的接收能力不断减少

2.滑动窗口可以为0吗?

可以,滑动窗口为0即为win_ start == win end,当我给对方发送数据,对方上层一直不取走数据,导致接收能力越来越差,而窗口也就越来越小,所以到最后就会导致滑动窗口为0了

3.如果没有收到开始的报文的应答,而是收到中间的应答,这种情况可能吗?影响结果吗?

这种情况是有可能,并不影响结果。因为有可能前面的应答丢了,接收到后面的应答了,不影响结果是因为,接收到的应答表示,该确认序号前的所有数据都收到了,所以即使没有收到前面的应答, 也默认前面的数据都收到了

4.超时重传,背后的含义就是没有收到应答的时候,数据必须被暂时保存起来

5.滑动窗口如果一直向右滑动,越界问题?

不存在的,因为TCP的发送缓冲区是环状结构的,会进行取模操作,不会出现越界的问题


当1001~2000这一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001" 一样

如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送

这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中

这种机制被称为 "高速重发控制"(也叫 "快重传")


快重传既然又快,又能重传,为什么我们还要超时重传呢?

因为快重传是有条件的,需要连续三次收到了同样的应答,就会重新发送,但是也有些情况不会连续收到三个一样的应答,所以就需要用到超时重传了

因此他们两个不是对立的,而是协作的


拥塞控制

上面学的这些机制,例如超时重传、快重传、流量控制、链接管理、滑动窗口、去重、按序到达、序号机制、确认应答机制,都解决的是端到端的可靠性问题

但是数据不仅仅是在端到端之间通信,还需要经过网络,还需要考虑网络的健康状态

虽然TCP有滑动窗口,能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题

因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的

TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据

拥塞窗口:单台主机一次向网络中发送大量数据时,可能会引发网络拥塞的上限值

所以滑动窗口的大小 = min(拥塞窗口,对方窗口大小[接收能力])

发送开始的时候,首先定义拥塞窗口大小为1;每次收到一个ACK应答, 拥塞窗口加1(实际发送的数据是:1、2、4、8按照指数级别增长的)

每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口

拥塞窗口增长速度, 是指数级别的, "慢启动" 只是指初始时慢, 但是增长速度非常快,为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍

此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长


为何拥塞之后,前期是指数增长?

指数前期慢,后期非常快

一旦拥塞:

前期要让网络有一个缓一缓的机会,所以前期需要数据量少,速度慢一点

中后期,网络恢复了之后,尽快恢复通信的过程,否则可能会影响通信效率

又想解决网络拥塞问题,又想尽快恢复双方通信的效率,所以选择指数增长

拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案


延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小

假设接收端缓冲区为1M,一次收到了500K的数据,如果立刻应答, 返回的窗口就是500K,但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了

在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来; 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M

一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高

我们的目标是在保证网络不拥塞的情况下尽量提高传输效率,那么所有的包都可以延迟应答么?

那肯定也不是:数量限制: 每隔N个包就应答一次; 时间限制: 超过最大延迟时间就应答一次

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


捎带应答

在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 "一发一收" 的,意味着客户端给服务器发送一个数据时, 服务器也会给客户端回一个数据,那么这个时候ACK就可以搭顺风车,和服务器回应的数据一起发回给客户端


面向字节流

创建一个TCP的socket, 同时在内核中创建一个发送缓冲区和一个接收缓冲区

另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可 以写数据. 这个概念叫做 全双工

由于缓冲区的存在, TCP程序的读和写不需要一一匹配,例如:

写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节

读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次 read一个字节, 重复100次

对字节流中的数据进行解释是需要应用层自主完成的


粘包问题

因为TCP不关心字节流数据的格式,所以当我们进行上层任意读取时,极有可能对多个报文出现多读或少读,这样的情况称之为数据报的粘包问题

那么如何避免粘包问题呢?

归根结底就是一句话, 明确两个包之间的边界

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

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

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

对于UDP协议来说, 是否也存在粘包问题呢?

很明显并不存在,因为UDP有标准报头定长的,和16位UDP长度,所以当收到报文时,去掉报头剩下的就是有效载荷,就能够保证读到的就是完整报文


基于TCP应用层协议:

HTTP、HTTPS、SSH、Telnet、FTP、SMTP


相关推荐
小蜗牛慢慢爬行32 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
MARIN_shen38 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
打鱼又晒网1 小时前
linux网络套接字 | 深度解析守护进程 | 实现tcp服务守护进程化
linux·网络协议·计算机网络·tcp
m0_748240021 小时前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
終不似少年遊*1 小时前
华为云计算HCIE笔记05
网络·华为云·云计算·学习笔记·hcie·认证·hcs
蜜獾云2 小时前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小林熬夜学编程3 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Hacker_Fuchen3 小时前
天融信网络架构安全实践
网络·安全·架构
上海运维Q先生3 小时前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
ProtonBase3 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构