TCP的乱序和丢包判断(附Reordering更新算法)-实例case

2023-11-10

写前一篇文章TCP的乱序和丢包判断(附Reordering更新算法)-理论的时候,我觉得我在一边拉一边吃,玩的都是排泄物,言之无味,不知所云,我想把一些能看得见摸得着的东西独立出来,就成了本文,如果有一天我忘掉了TCP的细节,我想我直接把本文的例子跑一遍,应该就能拾起个大概了。

声明

本文完全旨在解释上一篇文章里那些枯燥的理论,我实在是觉得自己文字功底差,一直以来都倾向于用例子来给出解释。花了点时间整理了几个用例,希望能把问题解释清楚。在用实例解释问题的时候,最忌讳的是把很多因素杂糅了一起,因为本来就是通过特例解释,并没有完备性支撑,所以我尽量把问题孤立化,加之以前也写了不少文章解释其它的方面,所以本文的所有用例均采用以下的配置:

net.ipv4.tcp_fack = 0
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = cubic

关于fack开启的情形,请自行分析。另外本文的主要目的是解释reordering相关的,不是拥塞算法,所以默认采用了cubic算法,至于其它的比如bbr算法(以及任何携带cong_control回调的拥塞控制算法)下是什么表现,本文不涉及。

  另外,本文的所有实例均是packetdrill脚本,如果你不会的话就请自行谷歌百度packetdrill的使用方法并下载安装,本文并不负责介绍关于packetdrill的任何东西,给出一个链接算是比较厚道了:Packetdrill 简明使用手册

case 1:认识乱序空洞左边缘确定

这个用例可以让你清晰看懂如何来确定乱序空洞的边缘在哪里以及如何确定。为此我准备了两个packetdrill脚本来进行对比性解释。首先看第一个,packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000
// 确保已经确认到了1001
+.1 < . 1:1(0) ack 1001 win 32792
// 连续发送10个段
+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
// 重复确认1001,同时携带一个6001-7001的SACK
+.1 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>
// 随即重复确认1001,同时再携带两个SACK
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%

// 最终完全确认10个数据包
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

以下是脚本的输出:

3  
8  
8

下面我给出一个解析,解释发生了什么。我把整个事情发生的过程整理成下图:

这里写图片描述

可见,虽然输出的是3,8,8,但事实上,由于reordering值的更新是单调递增的,所以reordering值经过的第二次8到7的更新是失败的,真实的reordering更新尝试应该是3,8,7.

  最终我们来看一下以上脚本过程的抓包:

这里写图片描述

第一个脚本到此结束了。

  接下来我们看下case 1的第二个例子,该例子说明了乱序空洞的右边缘如何确定。首先还是先给出packetdrill脚本的代码:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3  
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0 

+0  bind(3, ..., ...) = 0  
+0  listen(3, 1) = 0  

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>  
+0  > S. 0:0(0) ack 1 <...>  
+.1 < . 1:1(0) ack 1 win 32792  
+0  accept(3, ..., ...) = 4  

+0  write(4, ..., 1000) = 1000 
+.1 < . 1:1(0) ack 1001 win 32792  
+0  write(4, ..., 10000) = 10000  

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>  
+.0 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>  
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257 
+.0 %{ print tcpi_reordering }%

它的输出是:

3
4
7

以下是关于该脚本运行时的图解:

这里写图片描述

依然有抓包来确认:

这里写图片描述

也许,到底为止,事情应该结束了,你也应该完全明白了乱序空洞左右边缘的确定方法以及reordering值的更新机制,但是且慢,还有一个更好的例子呢。该case的最后一个例子,旨在解释在收到携带SACK的ACK包时,遍历传输队列以求取乱序空洞左右边缘的过程。该例子的packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 1001 win 32792
+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

它的输出是:

3
3
3

为什么reordering值没有变化?这是一个问题。按照左边缘和右边缘的确定方法,右边缘显然是9001开始的数据包,而左边缘是4001开始的数据包,这样一来reordering的值应该更新为6才对啊!但事实证明,reordering值并没有变化,依然是3,这是为什么?因为后一个ACK包携带的第一个SACK包在之前的最右边的SACK包3001之后,这明显是一个按序的确认,何来乱序呢?

  从常理上分析更容易理解,第一个SACK确认了3001开始的一个数据包,第二个SACK确认了从4001开始以及从9001开始的两个数据包,先发送的先被确认,这并不能表明出现了乱序啊,事实证明确实没有判定为乱序。确认了这一点之后,我们再来看收到SACK之后遍历TCP传输队列以确定乱序空洞左右边缘的算法:

for each skb in write-queue  
    if thisSACK contains skb && skb.SACKed == FALSE && skb.RETRANS == FALSE && skb < Hr
        Hl = skb
    if thisSACK contains skb  
        Hr = skb
        skb.SACKed = TRUE

if Hr > Hl && (Hr - Hl + 1 > reordering)
    reordering = Hr - Hl + 1

请注意skb < Hr这个判断条件,想要让一个最新被SACK的数据包成为左边缘,不仅仅要求它此前没有被SACK以及此前没有被重传,还要求它不超过此前的右边缘。有了这个判定,对于事情的理解就简单多了。有了这个理解,我们来猜一下下面脚本的结果:

...
+0  write(4, ..., 10000) = 10000
+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001 8001:9001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

显然,答案是:

3
6
8

好的,现在我们进入下一个更加简单些的用例。


case 2 认识RACK机制与reordering值的关系

依旧先看一个脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 间隔一些时间再SACK新的数据包
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3 
5 
6

以下给出一个图解,解释一下为什么:

这里写图片描述

下面是抓包的分析确认:

这里写图片描述

如果我们连续发送两个SACK,不给RACK定时器超时的机会会怎样?下面我们就试一下,请运行下面的packetdrill脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 立即SACK新的数据包
+.0 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3 
5 
7

下面是一个图解:

这里写图片描述

依然给出抓包分析:

这里写图片描述

看来,归根结底这并不是RACK的影响,而是RACK定时器的触发影响了TCP的拥塞窗口,导致两种情况下重传的数据包数量不同,进而造成了两种情况下乱序空洞的左边缘不同(左边缘不能被重传过),最终左右边缘的间距不同。

  接下来,我们看个更简单的用例。


case 3 认识reordering更新与快速重传的先后顺序

假设当前的reordering值是默认值3,开启SACK的情况下,有人会说,为什么已经有3个数据包被SACK,依然没有触发快速重传呢?比如下面的脚本所示:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
// 如果下面的这行不是随即确认,而是间隔一段时间(把+.0改成+.1),待RACK超时发生重传后,情况将会有大不同,请自行分析!
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3  
8  
8

从输出上看,在收到第二个包含两个SACK段的包后,虽然被SACK的包量达到了3个,但是此时的reordering值已经更新,可见reordering值的更新在判断快速重传条件之前!

  下面是一个图解:

这里写图片描述

下面给出抓包分析:

这里写图片描述

注意,虽然数据包被重传了,但不是快速重传所触发,而是RACK超时所触发!作为一个反过来的例子,下面的脚本可以印证上面的说法:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 7001:8001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3   
3  
6

该脚本几乎和上面的脚本没有什么区别,只是把第3个SACK从2001开始的包改成了从7001开始的包,按照reordering值的更新算法,这次将不会引发reordering值的更新,进而在收到3个SACK后触发快速重传,从下面的抓包分析中能看出来确实触发了快速重传:

这里写图片描述

以下是针对上述脚本的图解:

这里写图片描述

我们来看最后一个用例。


case 4 理解TCP记分板Mark lost算法的自适应性

这块内容比较独立,理论上的东西请参考我此前的文章:关于TCP快速重传的细节-重传优先级与重传触发条件。本文给出两个实际的例子来解释枯燥的理论。首先看第一个例子,该例子中,被SACK的包聚集于后面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000
// 被SACK的包量大于3,触发快速重传,快速重传前进行数据包的LOST标记
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:10001,nop,nop>                     

+5 < . 1:1(0) ack 10001 win 257

很显然,按照Mark LOST算法,在最后保留reordering个被SACK的数据包,本例中,前面所有的数据包均将被标记为LOST,事实也正是如此!在我的probe输出中,一共有4个包被标记为LOST:

这里写图片描述

下面是此例的抓包分析:

这里写图片描述

再看一个例子,该例子中,被SACK的数据包聚集于前面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:6001 9001:10001,nop,nop>
// 以下的打印不一定准确,因为存在一个异步的RACK定时器超时的过程,该过程同样会进行LOST标记,且与tcp_get_info的调用顺序不确定。
// 因此需要采用tcp_probe的方式,HOOK住tcp_xmit_recovery函数,然后打印tp->lost_out的值最为准确和实时。
+.0 %{ print tcpi_lost }%

+5 < . 1:1(0) ack 10001 win 257

依然用probe输出,被标记为LOST的包量仅为1!如下图所示:

这里写图片描述

我们再看下抓包以确认上述分析:

这里写图片描述

我们发现不止1个数据包被重传,然而仔细观察前面的时间戳后,发现从第二个重传包开始,便不再是快速重传所起的作用,而是RACK超时重传了,当RACK超时被触发后,有4个包被标记为LOST!在早期的内核中,RACK尚未文档化的时候,内核当然并没有引入RACK机制,没有RACK,就只能timeout了。引入了RACK,反而更加令人迷惑,然而正如抓包看到的那样,虽然没有触发快速重传,但不那么慢的间隔内,重传确实发生了,这就是RACK带来的东西。

遗留的问题

本文根本没有详述关于RACK的细节,但这并不意味着它不重要,RACK是作为时间序丢包判断最近才出现的,以下三类丢包判断是并列的:

  • 空间序-dupthresh: 3 OOO packets delivered (packet count)
  • 序列假定-FACK: sequence delta to highest sacked sequence (sequence space)
  • 时间序-RACK: sent time delta to the latest delivered packet (time domain)

这些问题并没有在本文中进行论述,因为这个话题有点大,所以说就先略过了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP的乱序和丢包判断(附Reordering更新算法)-实例case 的相关文章

  • WCF TCP 客户端 - 如何使用它们的基本指南?

    我有一个 WCF 服务并希望使用 TCP 绑定连接到它 这一切都很好 但是你应该如何处理客户呢 我注意到 如果您为每个调用创建一个新客户端 它不会重新使用该通道 并会留下一堆 TCP 连接 直到超时 创建客户端 调用其方法 然后关闭它是正常
  • 用 C 处理 TCP 的部分返回

    我一直在读Beej 的网络编程指南 http beej us guide bgnet 获取 TCP 连接的句柄 在其中一个示例中 简单 TCP 流客户端的客户端代码如下所示 if numbytes recv sockfd buf MAXDA
  • 如何将我的 Kivy 客户端连接到服务器(TCP、套接字)

    因此 作为我的项目 2D 多人纸牌游戏 的一部分 我已经弄清楚如何在线托管和运行服务器脚本 我的计划是让两个单独的 kivy 客户端连接到服务器 这只是一个带有命令的脚本 但是我对操作顺序有些困惑 因为我think客户端连接可能与消息循环发
  • Boost ASIO:服务器如何知道客户端是否仍然连接?

    我在用boost asio对于服务器 客户端应用程序 服务器一次只接受一个连接 我想知道服务器验证客户端是否仍然连接的最佳方法是什么 这样做的目的是我希望能够知道客户端是否崩溃 以便我可以重新开始侦听新的连接尝试 在我的应用程序中 我使用以
  • Linux Socket write() 的错误文件描述符 错误的文件描述符 C

    我对 write 2 函数有一个有趣的问题 PrepareResponseForSetCoordinates 函数会导致写入时出现错误的文件描述符错误 这是错误行 perror 写入套接字时出错 总产量 写入套接字时出错 文件描述符错误 我
  • Nodejs TCP连接客户端端口分配

    我使用nodejs在客户端和服务器之间创建了tcp连接 网络模块 https nodejs org api net html 服务器正在侦听已经预定义的端口 并且客户端正在连接到该端口 据我了解客户端的端口是由节点动态分配的 那是对的吗 节
  • 很难理解带有 async_read 和 async_write 的 Boost ASIO TCP 的一些概念

    我很难理解使用 async read 和 async write 时构建 tcp 客户端的正确方法 这examples http www boost org doc libs 1 38 0 doc html boost asio examp
  • Node.js 找不到模块“tcp”

    节点在以下行崩溃 var tcp require tcp 错误文本 node js 201 throw e process nextTick error or error event on first tick Error Cannot f
  • Socket ReceiveAsync 合并数据包

    我打算通过套接字接收数据包 但由于它们是从发送方以高频率发送的 因此其中许多数据包被打包成一个byte array SocketAsyncEventArgs Buffer然后保存多个数据包 即使它们是单独发送的 使用验证wireshark
  • 由于将请求从主线程传递到工作线程,netty 中出现延迟?

    我有一些关于 Netty 服务器端 TCP IP 应用程序的问题 我想知道在将请求从老板线程传递到工作线程时是否会因为 netty 由于缺少配置等 而出现延迟 我在用 new OrderedMemoryAwareThreadPoolExec
  • 在 Python 中通过 TCP 套接字发送文件

    我已经成功地将文件内容 图像 复制到新文件 然而 当我通过 TCP 套接字尝试同样的事情时 我遇到了问题 服务器循环未退出 客户端循环在到达 EOF 时退出 但服务器无法识别 EOF 这是代码 Server import socket Im
  • Python套接字模块:Recv()数据响应被切断

    解释 我目前正在尝试使用 python 脚本控制智能电源板 为了实现这一点 我使用了带有套接字模块的 TCP 连接 大约 75 的情况下 我会得到我正在寻找的响应 数据 并且一切都运行良好 然而 大约 25 的情况下 响应会以完全相同的长度
  • 为什么turn服务器不支持tcp连接?

    我是 WebRTC 新手 我需要为我的 webrtc 应用程序配置我自己的 Turn 服务器 我使用以下命令安装了我的转弯服务器 apt get install coturn 我只需要通过 tcp 运行转变服务器 它不必使用 UDP 进行任
  • 自动打开命名管道和 tcp\ip

    我正在安装一个需要修改 SQL Server 的新产品 具体来说 启用 tcp ip 并打开命名管道 我知道如何手动完成 我想要的是一种通过 SQL 或 C 代码为新客户自动化执行此操作的方法 我希望有任何关于正确方向的建议 您可以使用 C
  • 访问 AWS 上的 Tensorboard

    我正在尝试访问 AWS 上的 Tensorboard 这是我的设置 张量板 tensorboard host 0 0 0 0 logdir train 在端口 6006 上启动 TensorBoard b 39 您可以导航到http 172
  • 无法分配请求的地址 - 可能的原因?

    我有一个由主服务器和分布式从服务器组成的程序 从属服务器向服务器发送状态更新 如果服务器在固定时间内没有收到特定从属服务器的消息 则会将该从属服务器标记为关闭 这种情况一直在发生 通过检查日志 我发现从站只能向服务器发送一个状态更新 然后永
  • 为什么 UDP 服务器中只有一个套接字?

    我正在准备考试 发现了这个问题 典型的 UDP 服务器可以使用单个套接字来实现 解释一下为什么 对于 TCP 驱动的服务器 我发现创建了两个套接字 一个用于所有客户端访问服务器 另一个用于每个客户端的特定 套接字 用于服务器和客户端之间的进
  • 使用 boost 异步发送和接收自定义数据包?

    我正在尝试使用 boost 异步发送和接收自定义数据包 根据我当前的实现 我有一些问题 tcpclient cpp include tcpclient h include
  • 当使用环回地址使用 TCP/IP 套接字进行 IPC 时,常见的网络堆栈是否会跳过将消息帧封装在较低级别的 PDU 中?

    在某些环境 例如 Java 中 很自然地使用 TCP IP 套接字通过 localhost 地址 IPv4 中的 127 0 0 1 或 IPv6 中的 1 在同一主机上的进程之间传递消息 因为Java倾向于不在其API中公开其他IPC机制
  • 如何在Linux内核源代码中打印IP地址或MAC地址

    我必须通过修改 Linux 内核源代码来稍微改变 TCP 拥塞控制算法 但为了检查结果是否正确 我需要记录 MAC 或 IP 地址信息 我使用 PRINTK 函数来打印内核消息 但我感觉很难打印出主机的MAC IP地址 printk pM

随机推荐

  • Java实现文件分片上传

    为什么需要文件分片上传 大文件上传中断 假如我们有一个5G的文件 上传过程中突然中断我们该怎么办 上文件上传响应时间长 假如我们有个10G的文件 单次上传时间长 用户体验长 该怎么办 大文件上传重复上传 某些大文件 我们已经上传过了 我们不
  • JavaScript基础知识总结(6张思维导图)

    以下导图均为学习pink老师js基础视频时 自主整理的 有不足的地方 欢迎大家多多指出
  • 如何正确的进行网站入侵渗透测试

    大家都知道渗透测试就是为了证明网络防御按照预期计划正常运行而提供的一种机制 而且够独立地检查你的网络策略 一起来看看网站入侵渗透测试的正确知识吧 简单枚举一些渗透网站一些基本常见步骤 一 信息收集 要检测一个站首先应先收集信息如whois信
  • FastDB简单介绍及实例(Linux)

    本文内容主要是通过学习官网 博客及阅读官网demo做出的总结 FastDB是一个内存数据库 通过把数据加载到内存中实现对数据的操作 相比于传统的数据库 操作的速度更快 但是存在一个缺点就是使用FastDB数据库的应用程序都必须运行在同一台主
  • 【LWIP】stm32用CubeMX(6.4版)配置LwIP+Ping+TCPclient+TCPserver发送信息到PC(操作部分)

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 零 更新 2022 08 07 一 实验平台 二 手把手全程配置步骤 1 配置电脑环境 2 配置cubeMX 3 配置MDK Keil5 4 配置TCPcli
  • 监听屏幕滚动开始和结束事件(scroll start 和scroll stop)

    背景 最近在写一个专题页 产品要求右边导航栏在滑动的时候收起 滑动停止时显示 这样就需要监听到屏幕滚动 scroll start 事件和滚动结束 scroll end 事件 但是又不能为了这个功能专门引入jQuery mobile这个库 所
  • 来,看看这20个常用的宏定义!

    关注 星标公众号 直达精彩内容 ID 技术让梦想更伟大 作者 李肖遥 写好C语言 漂亮的宏定义很重要 使用宏定义可以防止出错 提高可移植性 可读性 方便性等等 下面列举一些成熟软件中常用的宏定义 1 防止一个头文件被重复包含 1 ifnde
  • npm 查看安装包版本

    npm info 安装包包名 如 npm info webpack
  • Linux查看CPU信息[//proc/loadavg]

    工作原理 基于 proc 文件系统 Linux 系统为管理员提供了非常好的方法 使其可以在系统运行时更改内核 而不需要重新引导内核系统 这是通过 proc 虚拟文件系统实现的 proc 文件虚拟系统是一种内核和内核模块用来向进程 proce
  • 1Panel 和宝塔有什么区别?

    宝塔是一款被广泛使用的 Linux 面板 与宝塔 Linux 面板相比 1Panel 的特色是开源和现代化 开源 1Panel 强调开源开放 广泛获取社区使用反馈 并快速迭代 现代化 一方面 1Panel 采纳最新的前端技术 并通过精心设计
  • SpringBoot Restful API 请求响应总结

    Controller 映射注解分为两大类 url映射注解和参数绑定注解 url映射注解 Controller 修饰class 用来创建处理http请求的对象 RestController Spring4之后加入的注解 原来在 Control
  • 2020-11-14

    MySQL学习周报 视图 数据库数据导入导出数据 数据库设计 1 视图 view 1 1 什么是视图 站在不同的角度去看到数据 同一张表的数据 通过不同的角度去看待 1 2 怎么创建视图 怎么删除视图 create view myview
  • 使用宝塔面板安装Rocket.Chat多功能团队聊天室

    安装 官方文档 https rocket chat docs 环境要求 NodeJS 8 11 4 Mongodb 4 0 9 Nginx 非必需 提示 由于官方建议的最低内存1G 所以512M或更小的建议加点虚拟内存 可以使用Swap一键
  • Linux 配置SSH免密登录

    介绍 SSH原理其实是非对称加密 即公钥加密私钥解密 免密登录原理 即事先将RSA非对称加密的公钥由主节点拷贝到到其他节点 只需实现主节点至其他节点的免密即可 流程如下 Client将自己的公钥存放在Server上 追加在文件authori
  • 华为荣耀8手机微信登录显示服务器错误,华为荣耀8手机无法登录360会员圈由于华为荣耀8手机无法登录3 爱问知识人...

    2017 10 12 05 41 48 保存个人资料恢复出厂值试一下 全部 2017 10 12 05 41 48 2017 10 12 05 41 48 会员的名称密码还有么 全部 2017 10 12 05 41 48 2017 10
  • STL算法之排序

    stl 算法库中提供的排序相关算法主要有以下几种 除了 q sort 定义在 cstdlib 头文件中 其余都定义在 algorithm 头文件 算法 功能 sort 排序 stable sort 稳定排序 保证等值情况时的原有顺序 par
  • 【C++】教大家在七夕new一个对象

    new是啥 new 是C 的关键字之一 它一般用来在堆上创建对象 但它与一般的创建对象的方法不同 它在创建对象时 会返回这个对象的指针 堆是啥 还有 和栈有什么区别 栈是由编译器自动分配和释放的 一般存放函数的参数值 局部变量的值等 速度较
  • c++primer 第十七章笔记 01tuple类型

    tuple类型 tuple看作一个 快速而随意 的数据结构 三个成员都设置为0 tuple
  • flowable实战(四):构建命令行程序

    实际上前面那些都是前置知识 真正上手的话其实还是一脸懵的情况 这里我也是按照文档生成一个最简单的demo 这个demo就是一个简单的Maven工程 你只要启动main方法就可以看到效果 github实例代码地址 这里如果直接下载代码 如果不
  • TCP的乱序和丢包判断(附Reordering更新算法)-实例case

    写前一篇文章TCP的乱序和丢包判断 附Reordering更新算法 理论的时候 我觉得我在一边拉一边吃 玩的都是排泄物 言之无味 不知所云 我想把一些能看得见摸得着的东西独立出来 就成了本文 如果有一天我忘掉了TCP的细节 我想我直接把本文