| |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| -> 网络协议 -> 知识体系之TCP/IP详解 -> 正文阅读 |
|
|
[网络协议]知识体系之TCP/IP详解 |
|
目录 1.3.5. listen的第2个参数(两个队列:syn请求队列/已连接队列) 1.4.3.TIME-WAIT存在的原因? 为什么主动方在TIME-WAIT阶段要等待2MSL? 1.4.4.TIME-WAIT过多? 危害? 应该怎么解决? 1.4.9.在Delay ACK开启时,一定要关闭Nagle算法 1.TCP1.1.TCP报头
?TCP粘包\拆包
1.2.TCP如何保证可靠性
1.2.1.校验和
1.2.2.确认应答与序列号
1.2.3.超时重传????????当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传(通常是在发出报文段后设定一个闹钟,到点了还没有收到应答则进行重传)。 重传时间的确定: ????????报文段发出到收到应答中有一个报文段的往返时间RTT,显然超时重传时间RTO会略大于RTT,TCP会根据网络情况动态的计算RTT,即RTO是不断变化的。在Linux中,超时以500ms为单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。其规律为:如果重发一次仍得不到应答,就等待2500ms后再进行重传,如果仍然得不到应答就等待4500ms后重传,依次类推,以指数形式递增,重传次数累计到一定次数后,TCP认为网络或对端主机出现异常,就会强行关闭连接。 Q: 超时重传时间间隔、重传次数:指数退避方式 A: 第一次发送数据后,设置的超时时间是1.5s,此后每次重传时间都增加1倍,一直到64s,一共重传12次,大约9min才放弃 1.2.4.流量控制(滑动窗口)目的:控制发送端的发送速度,防止数据发送过快,导致数据接收不过来。(接收端处理数据的速度是有限的,如果发送方发送数据过快,会导致缓冲区满,而发送方继续发送,会造成丢包,继而引起丢包重传等一系列反应) 原理:接收方每次接收到数据,向发送方返回ACK的时候,携带一个接收窗口大小(接收窗口的大小,是接收方根据自己的处理能力来确定的)。这就是滑动窗口机制 为什么引入滑动窗口?
滑动窗口的实现方式
零窗口 ????????当接收缓冲区满时,接收方在回复ACK的时候,会告诉发送方自己的接收窗口rwnd=0了,此时发送方接收到窗口大小为0后,就会停止发送数据。(此时会启动坚持定时器) Q1: 为什么引入坚持定时器? A1:?① B发送给A窗口为0的确认包后,A之后就不会再给A发送数据。② 等待一段时间后,B缓冲区有数据,就会向A发送一个报文,通知A现在可以发送数据了。③ 但是该报文在传输的过程中一旦丢失了,就会形成下面的“双方死等现象”:A等待B通知它发送数据,B发送报文后等待A发来数据。 Q2: 零窗口探测报文机制 A2:?坚持计时器(在A接收到一个窗口为0的报文后,将每隔一段时间,发送给B探测报文,即探测接收窗口大小的报文,一旦接收窗口大小不为0,就可以继续发送数据) 1.2.5.拥塞控制
1.3.三次握手1.3.0.三次握手过程
?1.3.1.为什么序列号syn是随机值?????????① 防止因为网络延迟导致后到的网络包建立连接,产生错误 ????????② 防止黑客伪造攻击 1.3.2.TCP三次握手,前2次握手的序列号有关系吗?答: 没有关系,都是随机值 1.3.3.为什么返回时ack值是seq值加一?????????仅当ACK标志为1时有效。确认号ack表示期望收到的下一个字节的序号 ?1.3.4.为什么要进行3次握手?而不是2次或者4次?
如果只进行2次握手,描述如下:C发送了创建链接后,S端回复了应答ACK后,就认为链接建立成功。 分析: ????????假设S回复的应答ACK丢失了,①因为C没有收到应答ACK,C会一直等待S的应答,②而S认为链接已经建立了,S就会想C发送数据,③C端接收到S端发来的数据,它是不会接收的,因为C要先收到S端的应答ACK后,才能接收S端到来的数据。 ===> 这样就会形成“死锁”,即①C一直等待S的应答ACK,②S一直向C发送数据 1.3.5. listen的第2个参数(两个队列:syn请求队列/已连接队列)
????????listen函数将主动套接字转换为被动监控套接字,其第二个参数backlog决定了内核的连接缓存队列长度。对于一个给定的监听套接字,内核维护两个队列:
① 未就绪队列,存放没有完成三路握手的连接,监听套接字收到SYN并返回ACK+SYN,连接处于SYN_RECV状态,等待对端发送ACK。如果已完成队列非满,则接收ACK,连接握手完成,进入已完成队列;如果已完成队列满则丢弃ACK,对端重发ACK(对端看到的连接是ESTABLISED状态),若未就绪队列中的SYN_RECV等待直到超时还没进入已完成队列则丢弃连接(对端不知道,只有在读写套接字时才知道)。 ② 已完成队列,存放已经完成三路握手的连接(ESTABLISHED),等待accept取走连接。 ????????backlog决定了两个队列的长度之和(并不是说两个队列之和等于backlog,而是存在个转换,依赖于具体实现)。 如果未就绪队列满则忽略新到来的SYN请求,对端重发,如果一直不能进入未就绪队列则对端connect失败返回。 当监听套接字关闭时:① 会对已完成队列中的每个连接发送复位分节RST,对端捕获RST被动关闭连接;② 直接释放未就绪队列的连接,这时对端不知道,对端的连接状态依然保持ESTABLISHED状态,直到对端主动关闭连接,由于监听端已经关闭连接,所以以RST响应对端的FIN,对端收到RST直接关闭连接。(类似于半打开连接) 1.3.6. SYN泛洪攻击/DDOC攻击
?1.3.7.??????连接建立是在accept函数么?
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ① | 如果connect返回值为-1,如果错误码不是EINPROGRESS,说明出现错误,直接return;如果错误码是EINPROGRESS,意味着:表示此时TCP三次握手仍在进行。之后可以使用select检查连接是否建立成功 |
| ② | 给select设置等待时间,并将打开的socket添加至select监控(即使用select函数等待正在后台连接的connect函数):
|
| 1. | C向S发送TCP报文(FIN标记位,seq=随机),试图断开连接,随后C进入FIN-WAIT-1阶段,即半关闭状态(即:停止C向S发送数据,但是C仍然能接收S传来的数据) |
半关闭状态:关闭了“写”方向,保留了“读”方向 |
| 2. | S接收到C发来的TCP报文后,回复确认报文,结束ESTABLISHED阶段,S进入CLOSE-WAIT阶段,即半关闭状态 此时,C接收到S回复的确认报文后,将进入FIN-WAIT-2阶段 | |
| 3. | S端经过CLOSE-WAIT后,将发送带有FIN的断开连接的报文,S随即进入LAST-ACK阶段。 C端收到S端发来的报文后,得知了S端已经做好释放连接的准备,随即进入TIME-WAIT阶段 | |
| 4. | C在进入TIME-WAIT状态时,会立即向S发送最后的确认报文。S收到该报文后进入CLOSED状态。 |
三次握手,是因为第二次的ack和seq是同时发送的,而四次挥手不行,因为
| close | close函数会关闭套接字,但是如果调用close时,有其他进程共享着该套接字,那么该连接仍然是打开的,该连接仍然可以被其他打开的进程读写。 |
| shutdown | shutdown会切断进程共享的套接字的所有连接,不管引用计数是否为0,都会关闭。 关闭的方向由第2个参数决定SHUT_RD/SHUT_WR/SHUT_RDWR。 |
| ※ 陈硕之muduo源码 | |
| 分析:在陈硕写的muduo源码库中的TcpCOnnection,没有提供close,而只是提供了shutdown,这么做的目的是为了保证收发数据的完整性。 | |
| 它相当于是将“主动关闭连接”这件事分两步做,①A调用shutdown关闭写方向/保留读方向后,B read将返回0,紧接着B将手动调用shutdown关闭读方向/保留写方向。(此时A/B都进入半关闭状态)②当B发送完数据后,B调用shutdown关闭写方向后,A read将返回0,紧接着A将手动调用shutdown关闭读方向(此时A/B都进入全关闭状态) | |
| ????显然,这样做的好处是:能够保证数据收发的完整性,即:当某个方向上没有数据收发时,可以手动地控制。保留另外一个方向数据的传递! | |
| 2MSL | 1MSL,即一段TCP报文在传输过程中的最大生命周期(centos-30s,unix-60s) |
| 存在 原因 | 等待2MSL的根本原因:为了实现TCP全双工连接的可靠释放。 在第4次挥手时,C端向S端发送最后的ACK,也可能会丢失,导致S端收不到该确认报文。 ==> 当S端在1MSL时间内没有接收到C端发送的ACK确认报文时,(由于TCP的重传机制)S端就会再次向C端重新发送FIN报文,这样C端接收到FIN报文后,会重新发送最后一个ACK报文,直到S端能成功的接收到ACK报文后,连接才真正的断开! |
| 为了使旧的数据包在网络中因过期而消失,防止新的连接接收到旧的数据报文 先假设没有TIME-WAIT状态的限制,如果当前有一个TCP连接(local_ip, local_port, remote_ip,remote_port)在断开的同时,以相同的4元组去建立一个新连接。那么TCP协议栈就无法区分前后两条TCP连接是不同的,在它看来,这根本就是同一条连接。(导致==> 前一条TCP已经关闭的连接发送出去的数据,通过之后的新连接,仍然可以发送给S端)。 由于TIME_WAIT阶段会等待2MSL,那么旧的数据包一定会超时而消失,就不存在上面的问题了! |
| 常识 | 主动调用close的一方,会在发送最后一个ACK后,进入TIME-WAIT状态 | |
| TIME-WAIT过多的原因:大量的短连接 | ||
| 许多短连接正在被快速的创建和关闭,由于关闭时需要等待2MSL,可能导致出现大量的TIME_WAIT | ||
| 危害 | TIME-WAIT的每个套接字都会消耗套接字,导致fd被耗尽 | |
| 套接字还绑定了端口/地址,如果不设置地址复用,在TIME-WAIT期间不能重用该地址 | ||
| 解决 方案 |
| |
|
| ||
| 作用 | 设置close()关闭TCP连接时的行为。当socket发送缓冲区有数据残留时,
【备注】网上很多人想用方式a避免TIMEWAIT,但是,这并不是一个好的注意,这种关闭方式的用途不在这里,实际用于在于服务器在应用层的需求! |
|
| |
| 分析 | 从“作用”可以看出,发送RST是一种【异常关闭】的方式,它会丢弃缓冲区中的数据,发送RST强制关闭。 【正常关闭】是指,通过四次挥手,等缓冲区的数据被清空后,再安全可靠的关闭 |
| 异常关闭的优点 | |
| 丢弃任何待发的无意义数据,立即发送RST报文,跳过TIMEWAIT,进入CLOSED状态 | |
| RST接收方,可以利用关闭方式来区分主动关闭方是正常关闭还是异常关闭 | |
| 补充 | |
| 值得注意的是,接收RST的一方的行为是什么呢? | |
| ? ? ? ? 接收RST的一方不会向发送方做任何的响应,它会立即终止连接 | |
| 原因 | 多半是程序的原因,还是交给程序猿吧!是因为被动关闭方未调用close() | ||
| 危害 | 大量的CLOSE_WAIT会消耗掉系统资源,导致fd不被释放。 | ||
| 分析 | 若被动关闭方不关闭发送FIN给主动关闭方,此时,被动关闭方就会进入CLOSE_WAIT状态。一个CLOSE_WAIT会维持至少2个小时的时间(系统默认超时时间的是7200秒,也就是2小时)。 | ||
| 场景 | Client发送FIN给Server,Server并没有调用close函数发送FIN,那么Server进入了CLOSE_WAIT状态。 | ||
| 解决 方案 | 修改TCP/IP配置参数 | tcp_keepalive_time | CLOSE_WAIT状态的时间,默认2h(改小) |
| tcp_keepalive_probes | 发从探测的次数(改小) | ||
| tcp_keepalive_intvl | 重新发送探测的频度(改小) | ||
| 代码需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,也断开连接 | |||
| 心跳检测:定期向socket发送指定格式的心跳数据包,如果接收到对方的RST报文,说明对方已经关闭了socket,那么也关闭这个socket。 | |||
| 背景 | 在使用一些协议通讯的时候,比如Talnet,会有一个字节一个字节发送数据的场景,(但是,每次发送一个字节的有用数据,却需要产生20个字节的IP头/20个字节的TCP头),这就导致了发送一个字节的数据需要携带至少40个字节的协议头。当发送频率很高的时候,会有很多小包没得到确认,无疑会产生很大的浪费,造成网络上充斥着很多small packet时,会造成网络拥塞。 |
| 思想 | Nagle算法思想 |
| 发送方发送数据后,在没有收到确认ACK前,不能继续发送其他数据(它保证了TCP连接上最多只能有一个未被确认的发送数据) | |
| 目的 | Nagle算法的目的是:避免网络中出现大量的Small packet,但与此同时,因为只能一个小包得到ack后,才可以发送下一个小包,所以会造成网络传输速度下降(但是Nagle算法不会那么傻,它会将小包合并,一起发送) |
| 实验 | 通过实验来看Nagle算法对发送的优化 |
| 实验设计:发送方每次发送一个字节的数据给接收方 | |
| 应用层调用send连续5次发送数据,由于Nagle算法,会导致①多个数据合并成一个数据,一起发送,这样大大减少了small packet的数量,(增加了TCP传输的效率)。②数据不会连续被发出,发送第一个数据后,会等待接收到确认ACK后,才发送第二个数据包,(这样会导致实时性不强) | |
| 场景 | Nagle算法默认是开启的,该算法比较适用的场景是:发送方发送大批量的小数据,且接收方会及时的做出回应。 |
| 选项 | setsockopt(client_fd, SOL_TCP, TCP_NODELAY,(int[]){1}, sizeof(int)); |
| 原理 | ① A给B发送数据后,B不会直接给A发送应答(Delay ACK);当且仅当B有数据发送给A后,顺便将应答放在数据包中,一起发送给A。 ② 如果等了一段时间(约40ms),没有数据从B发送给A,就回复一个“纯”确认ACK给A。 【总结】显然,上面过程,不是直接应答,中间发生了延迟确认。 |
| 这样做的好处是:相当于一个司机拉客人,他将客人拉到目的地后,不直接空车返回,而是等接到返程的客人,一起返回。 很明显,这样的方式是存在一定收益的!都是为了提高TCP性能 |
| 分析 | Nagle算法本身的立意是好的,避免网络充斥着过多的小包,提高网络传输的效率。与此同时,Delay ACK也是为了提高TCP的性能,不过二者遇到了,就比较悲剧了。 |
| 如果同时开启了Nagle算法和Delay ACK,对于write(header)-wirte(body)-read(response)场景,会产生极大的副作用。 | |
| 原因 |
|
| 分析 | 显然,发送方每次都要等待Delay ACK超时的应答到来后,才可以继续发送body数据,这会产生巨大的延迟。 |
| 这个问题的产生,主要是Nagle、Delay ACK副作用,以及write-write-read程序造成的,一般写程序的时候,不推荐这样的写法。 | |
| 如何 解决 | 方案1:禁止掉Nagle算法。但是这样做,会导致网络中充斥着大量的small packet(数据header),降低效率。 方案2:常规的解决方案,都是要避免应用程序出现write-write-read的写法 |

与TCP相比
| 可以看到,UDP与TCP相比,没有bind/connect函数。 S端:它在bind后,就可以直接调用sendto/recvfrom函数收发数据;不需要listen/accept C端:直接就可以调用sendto/recvfrom函数收发数据;不需要connect? |
|
????????connect(client_fd,?(struct?sockaddr*)&server_addr,?socklen)
| 先说明 | UDP中的connect与TCP中的connect有着本质上的区别:
|
| 【补充】采用connect的UDP发送接受报文可以调用send,write和recv,read操作.当然也可以调用sendto,recvfrom |
采用connect方式的UDP发送两个报文内核如下处理:#1:建立连结#2:发送报文#3:发送报文另外一点, ?每次发送报文内核都由可能要做路由查询???????
Q1: UDP是否可以多次调用connect?
A1:?UDP可以多次调用connect,而TCP只能调用一次connect
Q2:????????UDP多次调用connect的目的?
A2:?断开和之前的ip,port的连结,与新的ip,port连接
Q1: TCP和UDP的区别
A1:
| TCP | UDP |
| 面向连接(可靠) | 面向非连接(不可靠) |
| 流协议(引起粘包问题) | 报文(有边界) |
| UDP处理/传输速度更快 | |
| UDP不具有TCP那样的流量控制,它是尽自己最大可能的速度传输数据 | |
| TCP维持数据的可靠有序发送,成本更高,消耗更大 | |
| 数据流协议,包大小不受限制 | 包大小不超过MTU(1400),超过会报错or丢包 |
| TCP头大小20 | UDP头大小8 |
Q2: 既然TCP可靠,为什么很多项目仍然选择UDP?
A2:
物联网中的应用:长连接耗电
音视频通话:TCP延迟大
Q3:?TCP是可靠的,为什么通信会丢包? 怎么解决?
A3:?
????????例如服务器给客户端发大量数据,Send的频率很高,那么就有可能在Send时发生错误(原因可能是又多种,可能是程序处理逻辑问题,多线程同步问题,缓冲区溢出问题等等),如果没有对Send失败做处理重发数据,那么客户端收到的数据就会比理论应该收到的少,就会造成丢数据,丢包的现象。
????????这种现象,其实本质上来说不是丢包,也不是丢数据,只是因为程序处理有错误,导致有些数据没有成功地被socket发送出去。
????????常用的解决方法如下:拆包、加包头、发送,组合包,如果客户端、服务端掉线,常采用心跳测试。
Q4: KCP
A4: 可靠的UDP
Q5: 数据发送的整个过程
A5: 参考链接

Q6: 数据传输的过程中,哪些情况会丢包?
A6:?

|
|
| 网络协议 最新文章 |
| 使用Easyswoole 搭建简单的Websoket服务 |
| 常见的数据通信方式有哪些? |
| Openssl 1024bit RSA算法---公私钥获取和处 |
| HTTPS协议的密钥交换流程 |
| 《小白WEB安全入门》03. 漏洞篇 |
| HttpRunner4.x 安装与使用 |
| 2021-07-04 |
| 手写RPC学习笔记 |
| K8S高可用版本部署 |
| mySQL计算IP地址范围 |
|
|
| 上一篇文章 下一篇文章 查看所有文章 |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| 360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年12日历 | -2025/12/5 5:07:28- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |