大连房地产网站开发html5网站引导页模板
news/
2025/9/22 20:44:04/
文章来源:
大连房地产网站开发,html5网站引导页模板,廊坊哪些公司做网站,金华网上商城网站建设前言 学了TCP 和UDP之后#xff0c;感觉UDP就像是初入职场的年轻人#xff0c;两耳不闻 “窗外事”#xff0c;只管尽力地把自己的事情做好#xff0c;但收获的却是不可靠#xff0c;而TCP更像是涉世极深的职场老油条#xff0c;给人的感觉就是 “城府极深感觉UDP就像是初入职场的年轻人两耳不闻 “窗外事”只管尽力地把自己的事情做好但收获的却是不可靠而TCP更像是涉世极深的职场老油条给人的感觉就是 “城府极深深不可测”不仅事事考虑的周全懂得人情世故而且深得上层 和 “下层” 的信任因此大家都说TCP可靠TCP 也不禁感慨“我只需要略微出手就已知这个分段的极限了。” “UDP” 的不如意也就有了大家的那句话 “以前的我不屑一顾现在的我逐帧向 「TCP」学习”这也就是题目理解 UDP成为TCP 的深意。
一、回顾
1.UDP
特点 面向数据报不可靠无连接。适用于对实时性要求高的应用。可以发数据也可以接收数据即全双工。有接收缓存区没有发送缓存区。
2.TCP 特点 面向字节流可靠有连接。适合文件和视频等信息的传输。可以发数据也可以接收数据即全双工。有接收缓冲区有发送缓存区。 形象记忆 三次握手 四次挥手
在以后的协议讲解时我们都会先讨论以下两点
报头和有效载荷是如何封装和分离的。数据是如何往上进行分用的。
一、UDP
1.协议格式 报头与有效载荷是如何封装和分离的 通过截取定长的8字节长度将报头和有效载荷进行分离。 数据是如何分用的 在截取报头之后可以再通过分析头部信息进而获取到目的端口号而目的端口号可以通过一些哈希的方式比如端口号映射到进程的pid进而找到上层的进程而对应的进程采用N与UDP相关的应用层协议。因此数据完成到应用层的分用。
内核中关于报头的结构体
struct udphdr {//typedef unsigned short __u16;__u16 source; //源端口__u16 dest; //目的端口__u16 len; // 报文长度__u16 check; // 检验和
};博主采用的是linux-2.6.11.1版本的内核源码。
从编码的角度我们可以看出其实截取报头之后会放在相应的结构体中通过使用结构体中的变量完成实际的操作。 UDP检验和是如何检验数据的完整性即在传输路途中没有被损坏呢 不为人知的伪头部也叫伪首部。 说明如果报文误传给了UDP所在传输层伪首部的目的ip地址是报文实际所在主机的ip而不是目的主机的地址。通过校验和可检测出报文所在主机是否等于目的主机。 计算方法
按每16位求和得出一个32位的数如果这个32位的数高16位不为0则高16位加低16位再得到一个32位的数重复第2步直到高16位为0将低16位取反得到校验和(32位数取低16位)。 说明所有的数据都是以网络序列进行传输的。那么如果我们想要进行比对首先要将整个报文里面的数据先转换成主机序列提取出校验和然后再将伪首部以及整个UDP报文的其它数据分成一个一个的unsigned short int用unsigned int 变量将这些变量加起来然后按照上述的步骤进行计算得出校验和进行比对进而确保数据的准确与完整性。 注意 既然数据肯定能分成一个一个的unsigned short int且为了保证报文完整方便传输即一个UDP报文看起来是一个完整的矩形即协议格式的形状那么UDP报文的长度必然是4字节的整数倍。这样我们可以提出三点并在之后验证: 报文可能是含有填充字段的这个填充字段一般设置为0数据校验和计算是包含填充字段但是伪首部中的UDP报文长度是报头 与 有效载荷的总长度为了截取出有效载荷报头中UDP的长度是不含填充字段即只含UDP报头的大小和有效载荷的大小。 下面我们实验进行验证 下载与简单使用抓包工具WireShark 下载可能需要有点科技。 随便截取一个UDP报文 进行数据分析 提取数据编写代码
#includefunctional
#includeiostream
using namespace std;
int main()
{//源IP: 339F C766 —— 51.159.199.102字符串转int再转16位方便进行截取16位的2进制数相当于截取16进制数的4位。//目的IP:C0A8 1D0F ——192.168.29.15字符串转int再转16位//填充字段 协议号0x0011 —— 17//报文长度 0x001D //源端口82D3 —— 33,491// 目的端口DFB5 —— 57269//检验和0xebd8//数据 填充字段5f55 ae84 de67 b6a6 fb71 63af b868 4bdf ba13 4d2e cb 00 0000//按照伪首部 UDP头部 数据包括有效数据 和 填充字段unsigned short check_arr[22] { /*源IP*/0x339F,0xC766,/*目的IP*/ 0xC0A8,0x1D0F,/*协议号*/0x0011,/*报文长度*/0x1D,\/*源端口*/0x82D3,/*目的端口*/0xDFB5,/*报文长度*/0x1D,/*检验和置为0*/0x0000,\/*数据 有效载荷 填充字段*/0x5f55, 0xae84, 0xde67, 0xb6a6, 0xfb71, 0x63af, 0xb868,\0x4bdf, 0xba13, 0x4d2e, 0xcb00,0x0000};/*说明实际数据字节后面还要补0,凑成一个unsigned short后面的只是为了表示完整的UDP报文实际计算并没有意义。*/int checksum 0xebd8;functionunsigned short int(unsigned short*, int) check [](unsigned short int* arr, int size){int sum 0;for (int i 0; i size; i){sum arr[i];}while (sum 16){//sum 为低16位 加上 高16位的sum (sum 16) (sum 0xffff);}//对sum取反。return (unsigned short int)(~sum);};unsigned short int check_sum check(check_arr, 22);if (check_sum checksum){cout 验证成功 endl;}return 0;
}结果 谈到这里想必我们已经对UDP的协议格式有了基本的了解。下面我们从缓存区和数据的发送方式来进一步理解UDP。
2.数据报 从UDP(User Datagram Protocol)名字上即用户数据报协议我们可以先简单的理解数据报是面向用户的往下再说一层就是将用户发来的数据视为一个完整的报文。这里的完整 有两层意思。
第一层是可以直接将数据原封不动的封装后传给下层因此就不需要发送缓存区不过从协议的格式来看一个UDP报文最多有65535个字节如果超过了这个最大值而且又没有发送缓存区这也就导致了要把切分数据的任务交给了应用层即用户数据报第二层是报文具有明确的界限那么发送方一次发多少数据接收方就一定能收多少数据因此接收方没必要分次读取一个完整的报文但是接收方可能没必要立马读取报文这也就有了接收缓存区进一步缓存区是有一定的大小限制的如果发送方的数据占满了接收方的数据那么之后发送方再发数据接收方是收不了的因此接收方只能丢弃
进一步我们可以从UDP即用户数据报协议中窥探出UDP的本职工作是只负责 ——
简单的接收下层传来的UDP报文然后通过校验和进而检验数据是否完整和准确完整并且准确传给用户层不完整或者不准确则丢弃直接封装用户传来的合理大小的数据并往下层传。
那么就会有两种问题
网络的情况不太好此时数据在网络中可能会丢失即丢包现象而且UDP不负责传丢失的数据而且也因为没有发送缓冲区而重传不了那么接收方有的数据就会收不到。接收方收到的报文是完整的但是由于数据在网络中传输网络的状况是千变万化的这就可能存在先发的数据报后收到后发的数据先收到。因此数据报在接收缓冲区会存在乱序的现象虽然数据报之间是独立的但是呈现给上层的处理顺序就会发生变化。 举个简单的例子 小红和小明开始都有1000块要去往杭州。小红先走此时1000块最好只能买一张火车票于是小红走了但是当小明隔天再去买票发现现在飞机票降价了于是小明买了飞机票也走了。于是最终小红先走却比小明后到。数据在网络中传输基本上就是这个道理。假设你玩英雄联盟如果采取UDP协议此时你要先按R放大控住再按Q补伤害。发送给服务端可能就会呈现你要先释放Q再释放R。于是反应给你的就是Q R而不是R Q但是Q之后对方就直接闪现了R都按不出来。UDP这纯纯演员啊哈哈其实这也不怪UDP人家UDP不负责管这事。 从现实的角度来看UDP就像一个只顾自己一亩三分地的人对其它的人的事充耳不闻不通人情世故这也就怪不得其它层的 “人” 说“UDP真是干不了大事。” 即「UDP不可靠」。但是UDP把自己的分内事情做的很好因此也有人说“UDP已经尽职尽责了” 即「UDP尽最大可能交付数据」。
3.无连接
下面我们从套接字编程的角度继续分析。 上述图只是比较理想的情况下面我们来谈一谈比较现实的情况
Server端都还没有启动或者在Server端还未运行到recvform。这时客户端发送数据那不是白发了么即不可靠。对客户端同理。网络很不好Client端发送数据此时Server端大概率是收不到数据的。客户端梅开二度即不可靠。对服务端同理。
回过头再来看像这种事先不知道双方的运行情况以及网络状态就直接发送数据我们称之为无连接即UDP不可靠的一种。反之如果要确认双方以及网络状况需要消耗一定时间以及资源为代价侧面上突出了无连接的一种优点 —— 成本低不太消耗时间与资源。 最后我们总结一下UDP:
面向用户数据报。协议职责面向用户只负责发送来自和接收送往用户的数据。无连接。请读者从socket编程角度进行理解。全双工。收发消息有接收缓存区有发送缓存区。
从以上几个方面我们列表分析一下UDP的不可靠。
方面不可靠面向用户数据报不对网络中的数据进行负责因此可能存在丢包和乱序的问题。详见上文两种情况无连接双方在发送信息之前并没有进行协商因此不知道双方的状态。详见上文的两种情况。全双工接收缓存区满之后发送方不知道继续发此后数据会被对方直接丢弃。
优点不可靠意味着更低的成本更快的传输更低的延迟。也就被应用于实际的场景中。
如下是采用UDP的一些协议
协议采取UDP的原因NFS网络文件系统应用层协议注重低开销和速度。自己采取相应机制保证可靠性TFTP(简单文件传输协议)要求轻量和低延迟。自己采取相应机制保证可靠性DHCPDynamic Host Configuration Protocol用于自动分配 IP 地址、子网掩码、网关地址等信息的应用层协议注重的是网络配置信息的快速分配和管理BOOTPBootstrap Protocol是一种用于无盘设备启动的网络层协议注重在网络引导阶段快速获取网络设施对可靠性要求较低。UDP天然具有广播的优势。DNS(Domain Name System是用于将域名解析为对应 IP 地址的应用层协议注重效率和低延迟自身对可靠性要求较低。
综合来看采用UDP的大多数场景要求速度低延时效率等但也不缺乏也要求可靠的因此有对应要求的相应协议可通过再采取某些可靠的机制进而保证可靠。 TCP这个职场老油条可是是深不可测因此博主只负责带领读者认识那么一丢丢 进一步的理解还需要各位日后的继续学习~
二、TCP
1.协议格式 先来经典两问 TCP报头与有效载荷是如何封装和分离的 在数据的前面加上TCP报头即完成有效载荷和TCP报头的封装。
截取报文前定长的二十字节然后提取出四位头部长度可表示的范围为[0 , 15]乘4表示实际报头的大小又因为报头的最小长度为20即实际表示的范围为[2060]如果转化出的实际报头大小大于20将剩余部分也就是选项部分再进行提取最终完成报头和有效载荷的分离。 数据是如何完成分用的 与TCP同理这里就不过多解释了。 说明校验和的原理也跟UDP大同小异 内核中TCP协议字段的结构体
struct tcphdr
{__u16 source;//源端口号__u16 dest;//目的端口号__u32 seq;//序号__u32 ack_seq;//发送序号//根据不同的主机序列进行条件编译。
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端机__u16 res1:4, //保留长度可以看见这里的保留长度可以用于进行扩展。doff:4,//头部的长度fin:1,//释放连接syn:1,//建立连接rst:1,//重置连接。psh:1,//推送数据ack:1,//确认序号是否有效。urg:1,//紧急数据。/*相比较上面的保留长度这是扩展的功能*/ece:1,//提示网络拥塞。cwr:1;//表示网络拥塞时对方已经做出了调整。
#elif defined(__BIG_ENDIAN_BITFIELD) //大端机__u16 doff:4,res1:4,cwr:1,ece:1,urg:1,ack:1,psh:1,rst:1,syn:1,fin:1;
#else
#error Adjust your asm/byteorder.h defines
#endif __u16 window;//窗口大小__u16 check;//校验和__u16 urg_ptr;//紧急指针即偏移量。
};协议格式剩余的内容将在下文进行穿插深入讲解。
2.面向字节流
先图解字节流~
分析 全双工即双方都具有收消息和发消息的能力TCP比UDP多个发送缓存区。发送缓存区是保证可靠性的基石。 发送缓存区使TCP具有存放信息的能力那么对于网络中丢包的数据就可以实现重发进而保证可靠性。 发送缓存区使TCP具有了管理用户传来数据的能力因此TCP可以控制什么时候发一次发多少以及应对出错的情况进一步保证了可靠性即 “议” 如其名——传输控制协议。 UDP相比就显得比较被动没有发送缓存区那么就只能一次一次的发送用户传来的完整的数据报因此没有能力保证可靠性。 发送缓冲区使TCP有底气对用户说 不 的能力即用户你怎么传是你的事我TCP怎么发是我的事。两者在某种程度上实现了解耦合。 那么TCP就可根据现实的情况对用户传来的数据按字节进行划分每次发送合适大小的数据因为还要保持有序所以还要对划分的数据块进行编号然后进行数据的传输如果在传输途中如果有丢包就重传等所有的根据字节划分的数据块到达目的主机之后再进行排序从而得到正确的字节序列。最终呈现在接收缓存区让用户进行读取。 那么就会有图中的问题即用户在读取时可能只读取到了TCP划分好的字节序列但是这个字节序列并不保证是一个完整的报文有可能比一个完整的报文少也可能比一个完整的报文还要多即上面所说的粘包问题。 那么如何解决粘包问题呢 定长字节进行截取这就要求应用层每次发和收「固定大小的数据」这样每个报文就有了固定单位。 封装报头这个报头可以只是简单用户报文的长度然后使用分割符与用户数据进行分开方便进行提取长度之前的文章实现网络版本计算器时就是采用的这种方式。 自定义协议格式。更加灵活可以根据实际的需求灵活应对比如之前学习的HTTP协议的格式。
HTTP请求报头 如何保证数据是按序到达的呢 我们先假设数据在网络中传输并没有产生丢包只是网络有一点点的波动然后导致数据乱序。根据上面字节流的图解我们可以简单的得知在发送之前数据是要进行编号的那么是如何实现编号呢这就不得不提及协议字段中的序号字段了。
如图 假设接收方收到的TCP报文是乱序的比如先收到1001的序号再收到1的序号最后收到2001的序号那么我们就可以根据序号排升序进而可以让报文按序到达再分用呈现给上层用户即可。
3.确认应答机制
但是TCP要保证已经发送的数据是可靠的即发送方要确保数据接收方已经收到因此接收方收到数据还要向发送方说一句: 你放心吧数据我已经收到了。 那接收方是如何让发送方放心的呢这就涉及到了TCP的另一个字段确认序号字段和ACK标志位。
图解
分析
首先客户端向服务端发送序列号为1大小为1000字节的数据服务端收到之后计算出数据的长度为1000字节因此将确认序号设置为1 1000即下一个应发送的序号为1001并将ack标记位设置为1对客户端进行响应。然后客户端收到了服务端发送的ack应答确认对方收到前面1000字节于是接着往下发序号为1001大小为1000字节的数据。此后重复12类似的步骤直到数据发送完毕为止。
按照上图的发送的方法是不是感觉接收方并不需要进行排序直接发一个收一个就行了确实是这样的不过这样做有一个缺点就是每次都得等对方确认数据收到之后发送方才能接着发实际上有效率较高的做法。
图解 分析
客户端一次可以按顺序发送批量的请求。服务端收到一批数据之后对每个数据进行批量的返回确认序号。
上述操作其实只需要一回请求与响应即可完成可谓是提高了效率节省了时间。更进一步分析如果在返回确认序号时有部分丢包现象也是不要紧的。以上面的Client 与 Server的图举的栗子在返回确认序号时如果客户端收到了携带确认序号3001的应答则说明之前的报文服务端都收到了那么携带确认序号10012001的报文即使丢了也不要紧。这是因为服务端在进行确认时是按序进行确认的如果中间某个报文丢失之后的携带确认序号的报文是不会进行发送的。
如果应答不仅仅是应答而且还带有数据效率就会进一步提升像这种我们称之为捎带应答即应答的同时我还捎带了一些数据。
4.超时重传 数据在网络中丢包了怎么办 如图
一次成功的收发消息以ACK确认应答为终点那么如果发送的数据丢包也就意味着是收不到应答的。那么如果要重传就要设置等待应答的最长时间如果超过了这个时间我们认为数据丢失然后重传。
与此相关在计算机中有两个专业术语
RTT全称为Round Trip Time即往返时延是指从发送数据包到接收到对应确认的时间。
图解 2. RTO 全称为 Retransmission TimeOut即重传超时时间在 TCP 中当发送端发送数据后会启动一个计时器来等待接收端对这些数据的确认。如果在 RTO 时间内未收到确认发送端会认为数据丢失并触发重传机制重新发送这些数据。
这里我们探讨的就是如何设置RTO的问题一般来说我们设置RTO的时间应该略大于RTT的时间这样能够最大程度上保证效率。
图解
如果设置的RTO的时间过长那么等待的时间就会越长效率就越低如果设置的RTO时间短于RTT即使能够收到应答还可能重发相同的数据因此效率也会降低。
除此之外不仅数据会丢包应答也可能会丢包 像应答丢了再进行超时重传相比于数据丢包多做了一个无用功无用功指的是接收端已经收到了对应的数据有用功指的是数据可以提醒接收端发出去的应答丢失了要重新发送应答。
5.快重传
在实际传数据时我们肯定不止一次只发送一个请求为了保证效率我们会一次发送多个请求那么中途有报文丢失了怎么办呢 可以看出上述在进行传输数据时除了第一次正常应答外之后由于数据丢包是无法正常进行应答的如果无法应答那我们就改为缺失报文的应答即都改为确认序号为101的应答用于提醒发送方有报文丢失了补发对应的缺失报文即可。
因此当接收方收到了连续三个重复异常的相同的应答时就认为是相应报文丢失了之后及时的补发缺失的报文。 之后补发缺失的报文之后接收方再发送应答只需发送最后一次报文的应答即可为啥是应答三次才被认定为是报文缺失呢别问问就是科学家通过大量数据测出来的。不过我们可以通过下图与采用超时重传进行对比来感受一下。 首先超时重传中途要等待较为长的一段时间而且中间接收方因为数据有缺失也无法发送正常的应答因此双方是处于空闲状态的而快重传就利用了这一段时间用于提示发送方补发缺失报文因此时间效率得到了提高。 说明超时重传只是较为理想的重发情况这是因为发送方是不知道到底丢了多少数据因此重发多少是不确定的。 6.滑动窗口
滑动窗口顾名思义可以移动的窗口我们就会产生疑问。滑动是什么意思窗口又是啥意思 在学习UDP的时候提过当发送方将接收方的缓存区打满时发送方再发送数据此后报文会被直接丢弃这被视为不可靠的一种那么TCP是如何解决这种问题呢这就不得不提及协议字段中的16位窗口大小。
在正式发送数据之前TCP会经过协商即三次握手过程中双方会通知对方自己最多能够接收多少字节即所谓的16位窗口大小这个窗口大小会在传输的过程中动态变化下面我们画图理解。
下面采用服务端与客户端进行互相通信。事先协商好客户端最多能收200字节的数据服务端最多能收100字节的数据。双方互发一次数据。 窗口大小会在TCP报头中动态变化实时进行更新。且通过上图我们可以简略地看出滑动这个字眼的痕迹。那么具体是如何进行滑动的呢我们继续以图解的方式进行呈现。
首先发送方窗口的构造是这样的 首先我们已知三个变量WND——发送窗口的大小UNA——已发送但未被确认的第一个序号NXT——下一个可发送的开头序号。当发送的数据返回应答时即UNA左移移动过的路程就变为了[已发送已确认的数据]。当我们继续发数据时需要计算出可发数据的大小即可用窗口大小SIZE WND - NXT - UNA。然后NXT在[NXTNXT SIZE)范围之间移动即可。当接收方的滑动窗口变大时[等待发送的数据]就会有一部分变为[可继续发的数据]。
其次接收方的窗口的构造是这样的 首先我们已知两个变量WND——接收窗口的大小NXT——即下一个接收报文的起始位置。我们可以通过计算获取填充数据的范围为[NXT, NXT WND)。当新的报文来临时NXT向右进行移动移动的部分变为[已经收到的数据]。当接收窗口扩大时[等待填充的数据]其中一部分就会变为[可进行填充的数据]
因此发送方和接收方在不断的确认应答之间移动指针(改变相关变量)更新窗口的大小进而完成滑动的效果。
关于内核中关于接收窗口与发送窗口的实现的相关结构体
struct tcp_sock
{
//......__u32 rcv_nxt;//下一个该收到的报文。__u32 snd_nxt;//下一个待发送的序列号__u32 snd_una;//已经发送但是未被确认的的第一个字节。__u32 snd_wnd;//发送窗口的大小__u32 rcv_wnd;//接收窗口的大小。
//.....
};从这个结构体我们也可以得出创建一个套接字就会生成一对发送缓存区和接收缓存区。
7.流量控制 谈及UDP的不可靠即双方无法并不知道双方收发信息的情况这也就导致了在一方发数据时并不考虑也无法考虑对方是否具有接收数据的能力也就是说当发送方发送的数据已经打满了接收方的缓存区此时发送方由于不知道接收方不能再接收数据之后继续再发数据那么接收方就会因为无法处理而导致报文丢弃进而导致不可靠以及效率的降低。
反观TCP可以通过协议字段中的滑动窗口的大小间接的知道双方能收多少数据那么当接收方不能再收数据时那么发送方就不会再发送数据了而是等接收方能够再接收数据时再进行发送。这样做保证数据能够可靠到达避免了重发的现象因此也在一定程度上提高数据传输的效率即有用功变多了。这样通过窗口大小能够控制收发消息的手段我们一般称之为流量控制。
下面我们来进一步深入 当接收方不能收消息时发送方并不一定是干等着接收方的窗口消息更新的应答的而是在「每隔一会儿」就发送发送窗口大小探测报文当然报文只含报头不含数据因此接收方解析出报头之后会返回一个实时的窗口大小这样发送方就能及时的获取接收方窗口的更新情况。 当接收方的缓存区满了而应用层迟迟的不去收消息这不是让发送方干着急么于是发送方可以发送带PSH标记的报文用于提醒用户 : “你赶紧把数据拿走我要发数据”。催促赶紧将数据移到应用层此后发送方继续发送消息。这在降低延迟以及某些对实时性要求的领域有重要的作用。 当正常发送消息时接收方也能进行正常的接收突然发送方有一些「紧急的数据」要处理就如抗日战争中的「鸡毛信」里面有着「重要的情报」这时就会把「URG标记位设置为1」然后设置「十六位紧急指针的大小」指向「紧急数据的最后一个字节的下一个位置」。一般来说紧急数据只包含一个数据且是夹杂在正常数据进行传输的。到达接收方时会被优先进行处理。
如图 相关接口
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);说明当设置flags为MSG_OOB时即为设置带外数据即out-of-data也就是我们所说的紧急数据。 当接收方收到数据时并不着急给对方发送确认应答而是等大概200ms左右期间等待上层处理数据「等待之后」 窗口大小就会比「不等」的情况 大一些发送方确认应答之后之后就能够发送更多的数据。这样来给 “双方留有回旋的余地” 的方式我们称为延迟应答。 当接收方能够接收比16位窗口表示的最大数据还大时此时报头中选项中就会有一个窗口扩大因子当实际计算时扩大因子会参与和窗口字段的计算进而计算出实际的窗口大小。当窗口扩大时也就意味能够一次发送更多的数据即更大的网络吞吐量。
补充选项
MSS即最大报文长度( Maximum Segment Size )是TCP协议定义的一个选项MSS选项用于在TCP连接建立时收发双方协商通信时每一个报文段所能承载的最大数据长度。这里挖一个坑后面谈IP协议时填上~。
与之相关
MTU 最大传输单元Maximum Transmission UnitMTU用来通知对方所能接受数据服务单元的最大尺寸说明发送方能够接收的最大有效载荷大小。 8.拥塞控制 在上述我们只是考虑了报文到达目的地之后接收方对报文接收的可靠处理方法那如果报文就根本没有到达目的地即网络的情况很差呢 我们这里说的网络很差通常来说是网络中流量过多而超出了网络设备的处理能力进而导致网络发生阻塞的情况。那么如果只考虑之前我们所提及的超时重传隔一段时间就进行重发但是网络很差即使重发也是白发更严重的是你越发网络中的数据越多网络越差越差你越发网络的数据就更多进而导致网络更差…… 陷入死循环了。
那么我们在发数据之前我们就要考虑网络的情况如何了如何实现呢 我们可以简单的推测既然流量控制有滑动窗口那么拥塞控制自然也拥塞窗口咯那么下面我们就来谈一谈拥塞窗口是如何实现的探测网络情况。
简单来说拥塞窗口是一个变量在内核中名为snd_cwnd用于动态的反映网络阻塞情况。
大致原理
当网络情况变好时snd_cwnd会变大。当网络情况变差时snd_cwnd会变小。
而在实际发送数据时我们在考虑加上拥塞窗口的大小那么实际的发送窗口大小就为snd_wnd min(max_window,snd_cwnd);即 「发送方实际发送窗口的大小」 就为 「阻塞窗口的大小」 和 「对方可以接收的最大窗口的大小」 的 「较小值」 。
那阻塞窗口是如何变大变小的呢 首先当TCP建立连接时我们会将snd_cwnd设置为1此后先按照如下的规则发送数据 收到一个ACK,意味着网络可以发送一个MSS那么之后发送两个MSS大小的数据将snd_cwnd设置为2。收到两个ACK,意味着网络可以发送两个MSS那么之后发送四个MSS大小的数据将snd_cwnd设置为4。收到四个ACK,意味着网络可以发送四个MSS那么之后发送八个MSS大小的数据将snd_cwnd设置为8。以此类推……以2n 进行指数级增长。
如图 像这种指数级增长如果不加以限制那么很快就变的很大2的32次方就是一个21亿多的数据变得很大就会失去防止网路阻塞的作用因此在设计时会限制一个最大大小我们称之为慢启动门限ssthresh一般设置为65535即unsigned short int的最大值。
因此上面初始状态一点一点增加发送量的启动过程即速度逐步加快但量很少我们称之为慢启动。为了方便举例下面我们就假设ssthresh 为 8。
当超过ssthresh时我们就会进入一个线性增长的阶段以免增长过快。 在之后再进行增长时每当收到snd_cwnd个MSS数据即一次传输轮次snd_cwnd就加1。直到增长到触发重传为止,因为大量重传就在一定程度上反应了网络的状况是不好的即网络可能发生阻塞。 重传有两种方式
对于超时重传将ssthresh设置为snd_cwnd / 2snd_cwd设置为1。 对于快重传 将ssthresh设置为snd_cwnd / 2, snd_cwd设置为 snd_cwnd / 2。之后将snd_cwd snd_cwd 3。然后将数据进行补发并等待新数据的应答。如果等待新数据的应答之后就将snd_cwd ssthresh继续陷入线性增长。 说明对于快重传来说最开始的snd_cwnd加三是因为「还能收到应答报文」说明网络状况还不是太差那我就给一个较快的速度把丢失的数据—— 一般不是太多进行补发。等收到新数据的应答之后说明丢失的数据都收到了但是网络由于之前丢包说明网络状况还不是太好不敢让snd_cwd太大尤其是最开始加了三因此还是将snd_cwd 设置为sshthresh让其慢慢增长确保网络不会发生阻塞。
最终我们总结第一次是加三为了将丢失的报文快速传过去第二次是数据已经收到确保网络不发生阻塞而进行的一种复位行为。 中场休息我们这里整顿一下 “行李”进入下面的重头戏。
在最开始简单的认识了一下TCP协议通过之前的UDP协议我们可以理解源端口和目的端口以及校验和字段。在学习字节流的过程中提及了TCP报头中的序号并学习了数据报粘包的三种解决办法。在确认应答机制中提及了TCP报头的确认序号并逐步优化确认应答的方式部分应答可以缺失提出了捎带应答。在重传部分超时重传中RTO应该略大于RTT。在中间报文丢失部分丢失的情况下提出了快重传对效率进行了优化。在滑动窗口和流量控制部分我们理解了流量控制主要是通过滑动窗口进行实现的并补充了PSHURG两个标记位选项中的窗口扩大因子延迟应答并拓展认识了MSS , MTU对数据是如何处理的有了进一步的了解。在拥塞控制部分进一步补充了实际发送的窗口的影响因素即拥塞窗口。并画图理解慢启动以及重传数据时进行的两种碰撞避免算法。 9.三次握手
打个比方三次握手就像是有情人在经历了磨难之后 “终成眷属”。 为啥说是 “磨难” 呢因为在每一次握手都可能失败失败之后就要采取相应的措施进行弥补不那么顺风顺水即磨难。 在之前我们讨论过无连接的UDP是不可靠的那么有连接的TCP是如何保证可靠的呢
从理论上可靠意味着双方都能正常的收发消息这里就有一个值得探究的点如何保证都能双方收发消息——发数据进行试探和确认。
客户端给服务端发送消息收到服务端应答。说明 [客户端发送] 的消息能够被 [服务端收到]。那么就保证了从客户端到服务端的可靠性。 服务端给客户端发送消息收到服务端应答。说明[服务端发送] 的消息能够被 [服务端收到]。那么就保证了从服务端到客户端的可靠性。 前两步进行合并双方互发消息。即能保证双方能收发消息。 4. 服务端发送 「嗯。」 「你好! 」 其实可以合并成一条进行优化于是就有了最少三次就能够确认双方都能收发消息。 那么就有了一个问题之后发送和接收数据就一定能保证可靠吗答案是很显然的不一定因为网络是实时变化的就像股票一样谁也不知道哪一只股票在下一秒是升还是降。于是有了拥塞控制和重传等机制来保证数据在网络中传输的可靠性。那么我们建立连接的目的就是保证大概率的可靠性也就是能保证对方和自身有收和发数据的前提。而UDP是连这个前提都没有的。
再联系到TCP「你好!」 其实就是设置报头中的SYN为1「 嗯。」 其实就是设置报头中的ACK为1。那么「你好 嗯。」其实就是同时设置报头中的SYN和ACK为1像这种再携带SYN的同时也携带着ACK数据就是一种捎带应答。
画张图替换一下就是 补充一点窗口大小也是在这一阶段进行协商确认的。
因此理论上最少三次握手就能保证双方都具有收发数据的前提。但是再进行第三次握手的时候我们是不知道这个应答是否被服务端收到的那么如果应答在途中丢失了呢这就又涉及到TCP报头中的RST字段。
有两种情况 客户端可以赌一把即客户端假设这个应答对方收到了但是服务端没有收到那么客户端之后就会发送携带数据的报文那么服务端收到之后就会显的很奇怪“我并没有收到你的应答你咋就给我发数据了还不快重新把你的连接断开” 于是就会触发RST应答的报文客户端收到请求之后就会将自己的连接也进行释放。之后若想连接再进行三次握手。如果客户端赌的再狠一点在发送应答的时候就携带一些数据不仅给服务端的应答丢失而且给服务端的数据也丢失了真是赔了夫人又折兵啊~。 客户端保险起见客户端可不想自己建立好的连接白白就被一个RST浪费了于是就默不作声。于是服务端等到一定限度之后就触发了相应的超时重传机制认为发出的数据没有被收到于是再次补发数据此时客户端只需再补发应答数据即可应答被服务端收到之后。之后客户端就不用再冒着连接被释放的风险发数据了。
联系socket编程 简单理解
首先客户端发起连接即调用connect函数即发送SYN请求的报文不携带数据之后陷入SYN_SEND状态。然后服务端收到之后即调用listen函数保存客户端发来的连接信息服务端陷入SYN_RECV状态。其次服务端向客户端发送SYN ACK的报文进行响应等待客户端的ACK。然后客户端收到之后即connect函数返回客户端认为三次握手成功陷入ESTABLISHED状态。接着客户端向服务端发送ACK应答最后服务端接收之后也认为三次握手成功申请资源建立与客户端的连接。
埋坑listen在之前我们提及过第二个参数不过并不是很清楚只知道不能设置的太大也不能设置的太小。这涉及两个连接队列。
int listen(int sockfd, int backlog);连接还没有完全建立即三次握手没有完成收到了客户端的syn请求然后建立的连接队列我们称之为半连接队列也叫SYN队列。连接建立成功即三次握手完成然后从半连接队列中拿出对应客户端的连接放到一个连接队列称之为全连接队列也叫accept队列。区别半连接建立成功之后到被释放之间的时间间隔比较短
那么这里的backlog就是全连接队列的大小。更准确的来说Linux 内核 2.2 之前, backlog 半连接队列长度 全连接队列长度. Linux 内核 2.2 之后, backlog 全连接队列长度具体还是看内核的实现。
大致图解
那么全连接队列设置多少合适呢
太大就会导致总有一些闲置连接无法被及时的被处理因此会导致系统资源浪费的情况。太小就会导致还要从半连接队列中再拿中间有一定的时间消耗因此会导致效率的降低。
因此在内核中会有一个参数somaxconn在实际设置backlog时会在两者之间取较小值即min(somaxconn, backlog)而这个somaxconn默认为128在/proc/sys/net/core/somaxconn 可进行查看和修改。
SYN泛洪攻击是一种利用TCP半连接队列的缺陷由攻击者向服务端发送大量的伪造的SYN请求但是不进行ACK应答就会导致服务端有大量的半连接其次由于不进行ACK服务端还要进行维护甚至说对维护的半连接进行超时重传 SYN ACK这样就会损耗服务器资源从而导致系统资源不足。其次对于正常的用户来说也可能因为半连接的队列被打满进行正常SYN连接时因为服务端由于半连接队列已满只能将新来的连接释放而导致用户无法正常进行访问像这种泛洪攻击我们称之为DOS攻击。
那么抵御SYN攻击进而让合法用户进行正常访问呢 调大 netdev_max_backlog即当网卡接收数据大于内核的处理数据的速度时会有一个队列存放数据通过参数可以控制这个队列的大小增大队列的大小从而增加客户端正常访问的几率增大 TCP 半连接队列也是通过增加客户端正常访问的几率进而让合法用户进行正常访问开启 tcp_syncookies即当半连接队列满时服务端会将后来的syn请求进行加密放在报文中的序号后续接收ack请求会进行检验其合法性如果合法会将其放在全连接队列中。减少 SYNACK 重传次数因为对于超时的半连接服务端会进行重传重传次数越少对服务端的资源消耗就越小进而服务器的负荷就越小就能一直处于正常运转而不会被搞挂掉。 我们接着进行实验从实践的角度进一步理解三次握手以及listen的第二个参数。
说明实验代码使用的是TCP套接字编程编写了一套客户端与服务端的这里就不再贴出了具体可见文章 Socket —— “UDP“ “TCP“。
知识储备工作——熟悉 netstat 工具 的基本使用。 netstat是用来查看网络状态的工具以下是常见的选项。 n 拒绝显示别名能显示数字的全部转化成数字l 仅列出有在 Listen (监听) 的服務状态p 显示建立相关链接的程序名t (tcp)仅显示tcp相关选项u (udp)仅显示udp相关选项a (all)显示所有选项默认不显示LISTEN相关 首先在Xshell下创建四个会话进行客户端与服务端连接的实验需注意在本次实验过程中listen的第二个参数设置为1应用层并没有调用accpet也就意味着上层并没有拿走全连接下面我们进行如下步骤观察现象。
启动服务端并准备启动客户端1查看此时的网络状态。
命令解释
sudo netstat -nltp | head -2 sudo netstat -altp | grep -E 8888#netstat通常查看需要使用root权限所以用sudo进行提权;#sudo netstat -nltp | head -2 是将 netstat 的结果过滤出前两行即表头的描述信息。# 是在执行前面语句的同时执行后面的语句。#sudo netstat -altp | grep -E 8888 是将 netstat结果过滤出与服务端口号8888相关的。while :; do done
#此可看做一个while死循环一直进行。sleep 1
#每次执行休眠一秒防止打印速度过快。启动客户端1查看客户端与服务端的连接状态。 启动客户端2查看客户端与服务端的连接状态。 启动客户端3查看客户端与服务端的连接状态。 图解 结论
至少在Centos7.6版本下「全连接队列大小」等于「listen第二个参数」加「1」。当全连接队列满时即使收到客户端的ACK服务端也因拿不上去而处于SYN_RECV这个半连接的存活时间通常很短。
谈到这里我们已经有了对TCP三次握手有了一定的理解下面我们用一道面试题——TCP为啥是三次握手而不是两次四次 来进行收尾。
首先在最开始我们就已经阐明三次握手是理论上能够保证双方具有收发信息能力的最小次数。四次握手通过捎带应答优化为了三次握手减少了建立连接的次数进而提高了效率。也就是为啥不是四次的原因。其次不采用两次握手主要有以下两点无法保证~
第一点为无法阻止历史连接进而会导致造成服务器资源的浪费。 三次握手客户端可以通过将第三次应答改为发送携带RST复位标记的报文以此通知服务端关闭历史的连接进而防止服务器资源的浪费。 第二点无法同步初始序列号以及窗口大小等信息进而导致收发消息不可靠。 下面我们进入第二个重头戏~
10.四次挥手 打个比方四次挥手就像「迫切需要断干净关系好奔赴下一段恋情的一对狗男女」一样~
再拉出UDP鞭一下尸UDP是无连接的因此谁双方都不需要看对方的脸色自己把建立的套接字一关就啥也不管了~。那么TCP如何保证断干净关系的呢那就回到最开始的「三次握手」其保证的是双方都能收发消息那「四次挥手」就保证双方都不能收发消息就好了么~
那么跟三次握手一样这里我就直接将全过程的图解放出来了。 首先客户端发送我要和你分手然后服务端收到之后发送我知道了。其次客户端收到了服务端的应答但是由于「不确定服务端是否要分手」所以还要再等等。接着等「服务端决定好想分手」了向客户端发送我也要和你分手然后客户端收到之后向服务端发送我知道了。最后「等客户端确认服务端收到」之后双方分手完成奔赴下一段恋情 那么具体的将「分手」换成「SYN」「知道分手」换成「ACK」再代入理解一下。
FIN_WAIT1即代表客户端通知服务端要分手。CLOSE_WAIT即代表服务端知道服务端要分手。FIN_WAIT2即代表客户端知道服务端收到分手的消息但还要等服务端分手。LAST_ACK即代表服务端决定要和客户端分手。TIME_WAIT即代表客户端收到服务端确认分手的消息但「不确定服务端」是否知道「客户端收到服务端确认分手的消息」。CLOSED表示双方都收到「彼此」确认分手的消息。
至此我们已经对四次挥手有了简单的认识下面我们通过问题和实践进一步理解。 为什么是四次挥手而不是三次挥手呢 首先我们使用Wireshark 工具进行抓包提取出一个TCP的四次挥手的信息。 可见第一次「客户端FIN请求的应答」和 「服务端FIN请求」合并成了一条报文因此我们看到的实际上是三次挥手那就产生疑惑了既然可以是三次挥手那么为啥说是四次挥手呢其实这是一种 “做事留一线日后好相见” 的说法服务端没必要立马断开连接还可能要给客户端发送数据这种情况下是四次挥手。如果没有服务端之后没有数据要发立马断开连接这种情况下是三次挥手。那么退一步海阔天空因此我们说成四次挥手也没错~。 说明在第二次握手之后由于服务端没有发消息的能力不能发数据所以也就没有四次握手的说法反而四次握手是一种降低效率的一种行为。 下面我们接着做一个实验进一步理解TCP四次挥手。
说明一点这里我们只调用一次accept即上层只拿一个连接且服务器接收连接之后会立马将连接进行关闭。
启动服务端查看服务端的连接情况。 2. 启动客户端查看网络连接情况。 画个图解更加清楚 3. 客户端按下回车因为我们在后面的执行逻辑中设置了close函数所以按下回车之后会调用close。再查看网络连接的状态。 画一个图解更加清晰
此时我们终止掉服务端并重新启动。 说明TIME_WAIT等待的时间大致是2倍的「MSL」。
MSLMaximum Segment Lifetime 通常是指 TCP 连接在正常关闭后等待所有可能存在于网络中的数据包消失所需的时间。2 倍的最大报文段生存时间。
假如说京东正处于双十一结果因为连接过多而导致服务器挂掉了这时就会存在大量的TIME_WAIT状态等待进行处理而且由于服务器是要固定端口号的因此会导致服务器无法立即重新启动那么一秒就可能成交成千上百万单那损失。。。。
不过好在有相应的接口可以避免这种情况
//接口
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);//使用方式
int opt 1;
setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,opt,sizeof(opt));最后我们还是以一道面试题来给四次挥手收下尾「进程终止和重启」 与 「断电」的区别。
进制终止和重启操作系统还是可以给你整「私活」的即在背后默默的帮你把四次挥手的工作完成这也就是我们在关机时如果开的应用程序多了往往还要等待一段时间才能关机的一部分原因。断电操作系统就无力回天了这时就要看服务端是否正在向客户端发消息 如果发消息一段时间收不到之后会触发超时重传重传到一定次数之后服务端会自动将连接关闭。如果没发消息则要看服务端是否开启了「保活机制」~ 如果开启则服务端会定时发送一些探测报文检测客户端是否存活等到一定次数确定客户端消亡之后会将连接关闭。如果关闭则服务端的连接会一直处于ESTABLISHED状态。 最后希望这篇文章能够对各位读者有所帮助如果有误请及时的进行指出。
尾序
我是舜华期待与你的下一次相遇
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/910313.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!