📌 个人主页: 孙同学_
🔧 文章专栏: Liunx
💡 关注我,分享经验,助你少走弯路!
文章目录
传输层协议UDP
传输层
传输层负责把数据从一台主机发送到另一台主机。
我们之前的write和read接口并不是直接把数据发送到网络中,write接口只是把缓冲区中的数据拷贝到UDP的缓冲区中,至于数据怎么发发多少由操作系统自己决定。
端口号
端口号标识了一个主机上进行通信的不同应用程序。
当我们的底层一旦收到了报文,ip地址表明了我要把报文发送给那台主机,端口号用来把报文交付给上层的哪一个应用。
在TCP/UDP协议中用"源ip",源端口号","目的ip","目的端口号","协议号"这样五元组来表示一个通信(可以通过netstat -n查看)
端口号的划分范围
认识知名端口号

协议本质是结构体

UDP协议的格式

UDP的特点
udp的传输过程类似于寄信。
- 无连接:知道对端的ip和端口号就能进行传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制。如果因为网络故障该段无法发送给对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能灵活的控制读写数据的次数和数量。
面向数据报
应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并。
用UDP传输100字节数据:
如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用一次recvfrom,接受100个字节,而不能一直循环的调用10次recvfrom,每次接受10字节。
UDP的缓冲区
- UDP没有真正意义上的发送缓冲区(因为没必要),调用sendto就直接发给内核,由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有接受缓冲区,但这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达缓冲区的数据就会被丢弃。
UDP的socket既能收,也能写,这个概念叫做 全双工。
UDP使用注意事项
我们注意到,UDP协议的首部中有一个16位的最大长度,也就是说一个UDP能传输的最大数据长度是64k(包含UDP首部)。
然而64k在当前互联网时代是一个非常小的数字。
如果我们需要传输的数据超过了64k,就需要在应用层手动的分包,多次发送,并在接收端手动拼装。
基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:虚名解析协议
在操作系统内部,一定可能会同时存在大量的报文,所以操作系统就要管理这些报文。
如何管理呢?
先描述,再组织
下图为描述报文的结构体对象sk_buff


传输层协议TCP
TCP协议
"TCP"全称为传输控制协议,要对数据的传输进行一个详细的控制。
TCP协议的格式

1.标准问题
a, 如何将报头和有效载荷进行分离?
TCP报头的长度是20字节,一般而言TCP报头也是定长报头。TCP的报头是20字节的定长报头再加上选项,选项一般为0.
问题是TCP把20个字节读完后到底有没有选项是怎么知道的,选项是多少字节又是怎么知道的?
所以在TCP的标准报头20字节里面,存在一个4为首部长度的字段(4个比特位),4位首部长度也就是报头长度,包含了选项。
规定(约定):4位首部在计算式的基本单位是4字节。如果4位首部长度是0,那么报头长度就是0*4字节。如果4位首部长度是15,那么报头长度就是15*4 = 60字节。
因为标准报头是20字节,所以4位首部长度的最小值就是20字节,所以4位首部长度最小为5。
结论:因为TCP的报头长度的基本单位是4字节,所以就决定了不管TCP的选项有多少,TCP的报头肯定是能整除4字节的。
报头和有效载荷是如何分离的呢?
答案是先无脑的读取前20字节,因为20个字节是标准的必须得有,在提取4位有效长度,提取完4位有效程度后乘以4,再减去20就是选项的长度,剩下的就是有效载荷了。
这里怎么只有报头大小,没有报文大小?
TCP是面向字节流的,在操作系统内部,即便你发了多个TCP数据段,他也区分不清楚是不是一个完整的报文,所以对于TCP来讲,不需要来设置一个报文的字段。
b. 如何将自己的有效载荷交付给上一层?
TCP的报头里面有16位的源端口号,16位的目的端口号,当在传输层中收到了一个TCP报文它肯定也是一个sk_buff,当收到报文时提取目的端口号,根据目的端口号查进程,就能把报文交给对应的进程了
可靠性的本质
1.具有应答,可以保证历史消息的可靠性。
2.在通信的过程中,最新的报文,永远没有应答,最新的可靠性无法保证。
如果保证可靠性,在TCP中的机制叫做:确认应答机制。
客户端给服务器发消息,服务器收到消息后要应答。
服务器给客户端发消息,客户端要给服务器做应答
不对应答做应答
在TCP中如果客户端连续的发送几次请求,那么服务端也需要有连续的应答,当服务端对于某个请求没有应答时,那么是怎么解决那么请求丢失了呢?
答案是32位序号 和32位确认序号
32位序号:是我们发送报文时对报文带的序号,那么应答回复的序号是(确认序号)发送序号数+1。
发送请求时的32位序号是100,应答的确认序号是101,代表着101之前的报文全部收到
指定报文的序号之前的所有信息,已经全部收到。下一次发送,从该信号开始
报文将来按指定序号发,但是不一定按指定顺序收,这叫做乱序问题,是不可靠的一种问题。所以在今天我们的TCP不会有乱序问题,因为有序号,我们可以按照序号的大小进行排序,解决乱序问题。
现在我们就清楚了为什么TCP中要有32位的序号:
- 可以确认应答。
- 可以解决乱序问题。
客户端和服务器进行数据交互的时候交互的不是箭头,而是报文。要么是裸的报头,要么是报文+数据

为什么要有两个序号?
服务器不仅仅做应答,还会捎带应答。既需要给对方报文做确认,同时自己的报文也要有序号!!!
16位窗口大小 接受缓冲区剩余空间的大小
发送端如何尽早知道接收端的接受能力呢?
一台主机的接受能力由对方的接受缓冲区决定,由于对方的接受缓冲区中也可能有数据,所以对端的接受能力由对方接收缓冲区剩余空间大小决定。
我们要保证给对方发送数据,按量发送和按需发送,我们就必须得知道对方剩余缓冲区的大小。
流量控制问题:我们把按照对方的接受能力来动态调整自己发送数据速度的机制叫做流量控制。
流量控制主要解决的是效率问题。
TCP报头当中的标志位问题
TCP的报头里面有保留的6个位,除了保留的6个位之外,还有6个标志位。

💡三次握手
SYN(你要做我女朋友吗)SYn+ACK(好啊,什么时候开始呢) ACK(就现在)
前两次握手不能携带数据,因为3次握手没有完成(SYN,SYN+ACK),只有报头。
三次握手已经可以进行双方接受能力的协商了。
SYN标志位: 同步标志位,通常表示建立连接,握手过程的标志位。
表明自己的报文类型是一个建立连接相关的报文
ACK标志位: 确认号是否有效。说人话就是表明报文是一个应答报文。
ACK标志位几乎常被设置为1的,因为报文大部分是应答报文,或者报文+数据(捎带应答)。
💡四次挥手
四次挥手,客户端要和服务器断开连接,服务器也要向客户端断开连接。本质是两个互相断开连接的请求被对方可靠收到了,加上应答是4次,所以叫4次挥手,因为断开连接要经过双方的同意。本质是关闭全双工。
FIN标志位: 通知对方,本段要关闭了,我们称携带FIN标识的为结束报文段, 表明这是一个断开连接的报文。
PSH标志位: 提示接收端应用程序立刻从TCP缓冲区中把数据读。即要求接收端把接收缓冲区的数据尽快向上交付。
RST标志位: 通信的过程中,双方连接出现了任何问题,都可以对链接进行重置。
TCP是保证可靠性的,通信之前要先建立连接,而建立连接一定会成功吗?
答案是不一定
客户端发出ACK才算三次握手,而服务端收到ACK才算三次握手,所以这之间存在一定的时间差,如果客户端发出ACK,ACK丢了的话,客户端会认为自己的建立连接是成功的,而服务端会认为三次握手都没建立成功,所以就造成了客户端和服务器在建立连接是否成功不一致的问题。
客户端认为自己的连接建立好了就有可能给服务器发送数据,然而服务器端的连接并没有建立好,所以服务端就向客户端发送了RST。
URG标志位: 紧急指针是否有效。
要配合TCP报头中的16位紧急指针,URG标志位如果置为0,代表16位紧急指针无效。URG标志为1,标识16为紧急指针有效。
紧急数据并不属于常规数据,叫做带外数据。
16位紧急指针表示的是再当前报文的有效载荷中,特定偏移量处,有紧急数据。
紧急数据只有一个字节,用来设置各种状态码。
确认应答机制(ACK)机制

TCP每个字节的数据都进行了编号,即为序号。

每一个ACK都带有对应的确认序号。意思是告诉发送者,我已经收到了哪些数据,下一次发送数据的时候应该从哪处发。
超时重传机制

如何理解丢包问题?
丢包的情况就两种:
情况一:主机A给主机B发送数据,主机A携带的报文真的丢了。
情况二:主机A给主机B发送消息,数据主机B真的收到了,但是应答丢了。
重新理解应答报文
发送方没有收到应答ACK,意味着什么?
不能意味着丢包,他只能意味着数据可能丢失了,对方可能没收到。
意味着我们无法百分之百确定接收端收到报文,也就是无法保证可靠性。
没有收到ACK,要么数据丢,要么应答丢。
客户端收不到对应的应答,只能能带特定的时间间隔,如果在特定的时间间隔,收不到应答,就判定报文丢失。
收不到应答并且超时我们才判定它丢包了。
等待特定时间间隔本质是在等应答。
这里会衍生出一个问题:主机B收到的报文重复了
主机B可以甄别出来报文时重复的,因为我们的报文是有序号的。
所以序号的作用:(1)确认应答 (2)去重
这个时间间隔是多长呢?
因为网络的网速是变化的,所以这个时间间隔也应该是变化的。
TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。
- Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍。
如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。
如果仍然得不到应答, 等待 4 * 500ms 进行重传. 依次类推, 以指数形式递增。
累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接。
再谈三次握手
当我们想要通信的时候,我们首先必须得先进行三次握手
connect:发起三次握手!!三次握手的具体过程由客户端操作系统自己完成。
accept:不参与三次握手,三次握手由server os和client os自动完成。
❗️为什么要有三次握手???
- 三次握手是以最短的方式验证全双工。
三次握手真的是三次握手吗?
- 以最小成本百分之百确认双方通信意愿。
再谈四次挥手
断开连接的本质:建立双方断开连接的共识。
为什么是四次挥手?
因为四次挥手是在全双工下,以最短次数建立双方断开连接的请求。
如果客户端把连接关掉了,服务器还能给客户端发消息吗?
- 如果客户端已经退出或者关闭,服务端就是不管,那么服务端就会一直处在一个
close_wait的状态,而close_wait会依旧占用文件描述符,连接也没有释放- 我们自己作为服务器端,我们把文件描述符用完了就必须得关掉,如果不关,服务端有可能会处在一个close_wait的状态,close_wait就会一直在占用文件描述符,如果我们的服务器大量的代码处理了文件描述符就是不关,服务器一直处于close_wait,文件描述符一直被占用,建立的连接依旧都没有被释放,如果一个东西是有用的并且是有限的,那么他就是资源,比如内存,文件描述符本质是当前服务器进程的数组下标,如果我们把文件描述符不关,最后就会导致可用的文件描述符越来越少,这个过程我们叫做文件描述符泄漏问题.
当客户端收到FIN发出ACK就算客户端的四次挥手完成了,而客户端理论上四次挥手一旦完成.客户端的连接就应该直接关闭,可事实上客户端的连接并没有直接关闭(close),也就是说主动断开连接的一方把最后一个ACK发送完成不能立马进入close状态,它必须得等待一定的时间,过后然后把自己才能处于close的状态.
主动断开连接的一方,要进入一个状态,
TIME_WAIT状态,即便是四次挥手完成。等待两个MSL(最大报文的存活时间)时间后才能回到CLOSED状态。
为什么是2倍的MSL ?
MSL 是 TCP 报文的最大生存时间, 因此 TIME_WAIT 持续存在 2MSL 的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK);
为什么要TIME_WAIT ?
等待网络消散。
避免短时间内源端口目的端口的复用,导致历史报文被发送。
保证最后一个ACK被对方收到。
哪一方发出ACK就进入TIME_WAIT了,在TIME_WAIT期间,如果这个ACK丢了,发送方要再次FIN(没得到应答,超时重传)。在TIME_WAIT期间没有再次收到FIN,就认为对方收到ACK了。
如果我们今天既要服务器进入TIME_WAIT,又要服务器重启呢?用
setsocketpot,可以设置对套接字所对应的操作选项。使用 setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端口号相同但 IP 地址不同的多个 socket 描述符.
重连之后如何避免收到历史报文?
- 序号。当建立新连接后,旧的报文的序号和新的报文的序号不一定配得上,如果data所对应的序号和我们期望收到的序号不匹配的时候,这个报文是可以被丢弃的。
滑动窗口
我们已经了解了确认应答的策略,对每一个发送的数据段,都要有一个ACK来确认应答。收到ACK后再发送下一个数据段。这样做有一个缺点就是性能比较差,尤其是数据的往返时间比较长的时候。

既然这样一发一收的效率比较低,那么我们一次发送多条数据,就可以大大提高性能了。

发送方一次向对方发送多少数据,由什么决定?
答案是:滑动窗口
滑动窗口是:一次主机A向主机B发送数据的最大值。
滑动窗口是TCP发送缓冲区的一部分

滑动窗口将我们的缓冲区分成了三部分

已发送,已确认之后,不就是这部分数据无效了,这部分空间可以被利用了!!
所以对于网络通信而言,我们对于已发送,已确认这部分的缓冲区不用刻意的去清空缓冲区,而只需要将对应的数据无效即可,只要它在滑动窗口的左侧,就认为它是无效的了。
序号在发送的轮次当中,数字是一次增大的,那么也就意味着宏观上滑动窗口整体未来要向右滑动。
所以滑动窗口的本质:让start和end下标增加
细节一:滑动窗口的大小由谁决定?
答案是:对方的接收能力(收到的报文中的win大小)
滑动窗口的本质:是流量控制的具体实现方法
如何调整滑动窗口的大小?
start = 报文确认序号
end = 报文确认序号 + win(窗口大小)
问题:
1.滑动窗口可以向左滑动吗?
不会
2.滑动窗口,可以变大吗?可以变小吗?可以为0吗?
都可以,所有的这些变化都取决于对方的接收能力
3.如果滑动窗口太大,丢包了怎么办?滑动窗口,会不会跳过报文,进行应答?
a.最左侧数据段丢失
b.中间报文丢失
c.最右侧报文丢失
实际丢包的情况肯定是这三种的自由组合
最左侧丢失:
①情况一:
(1)最左侧报文数据真的丢了,滑动窗口最左侧不变。

- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的 ACK, 就像是在提醒发送端 "我想要的是 1001" 一样;
- 如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的 ACK 就是 7001 了(因为 2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
这种机制被称为 "高速重发控制"(也叫 "快重传").
快重传 vs 超时重传
快重传的条件:必须收到3个同样的确认应答 超时重传的条件:超时了
如果我们在TCP中收不到3个同样的确认应答呢?
比如说就只发送了2个报文,其中有一个报文丢了,那么就收不到3个同样的确认应答,此时就需要超时重传了
- 所以真正的重传机制是快重传和超时重传之间进行互相配合,超时重传是给快重传进行兜底的。
tcp发出,暂时没有应答的报文的时候,必须让对应的报文保存起来,以方便后续的重传!!
保存在哪里?如何理解保存
在滑动窗口中,保存其实就是窗口不要移动,当收到了相应报文的应答时,滑动窗口向右移动,就相当于丢弃数据。
所以超时重传和快重传的底层支持是滑动窗口
(2)最左侧报文对应的应答丢了,滑动窗口正常工作
中间报文丢失,依据确认序号的规定,所以中间报文丢失也就变成了最左侧报文丢失。
最右侧报恩丢失,也会转化成最左侧丢失。
所以滑动窗口不会跳过报文进行应答,因为确认序号的定义决定的。
问题:如果没有收到应答,滑动窗口的位置会不会更新?
不会
4.滑动窗口一直向右,会不会溢出?
不会,可以把缓冲区理解成为一个char类型的数组,可以把char类型的数组想象成一个环形区域。
流量控制
如果主机A给主机B发送消息,接收端处理数据的速度是有限的,如果发送端发送数据的速率比较快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送数据,就有可能出现丢包,继而引起丢包重传等一系列的连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制叫做流量控制(Flow Control)。
- 接收端将自己可以接收缓冲区剩余空间的大小,放入TCP首部中的"窗口大小"字段,通过ACK端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高
- 接收端一旦发现自己的缓冲区满了,就会将窗口大小设置成一个更小的值,发送给发送端。
- 发送端接收到这个窗口之后,就会减慢自己的发送速度。
- 如果接收端的缓冲区满了,就会将窗口设置为0,这时发送方不再发送数据,但是需要定期发送一个窗口,探测数据段。使接收端把窗口大小告诉发送端。

那么问题来了, 16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?
实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口字段的值左移 M 位;
拥塞控制
同样是丢包,但是丢包多和丢包少对应的结论是不同的。
TCP保证可靠性不仅仅保证了双方主机的问题,还考虑了网络本身的问题。
大面积的丢包,发送端判定网络拥塞问题!!
如果是大量报文,判定拥塞了,不敢立即重发。
如果立即重发,那么势必会增加网络的压力负载,会让网络变得更加拥堵。
多个c,多个s,都要走同一个网络。如果发生了网络拥堵,在不清楚网络状态的情况下,大量的客户端继续再往网络里面发送数据,就会造成网络拥堵的加剧。
所以就要拥塞控制,拥塞控制会让发送端的多台主机,都采用这种策略。
TCP引入慢启动机制
采用指数级增长的方式,先发送一个报文,如果收到了应答,再发送两个,再发送四个,以此类推。

像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速度非常快。
那么问题来了,发送多少数据不是由滑动窗口决定吗?
滑动窗口 = 对方的接收能力
为了支持拥塞控制算法,我们提出一个新的概念,这个概念叫做拥塞窗口
拥塞窗口: 一个临界值,值以内,网络较大概率不拥塞,值以上,网络可能拥塞。
我们把拥塞窗口就看作一个整形变量,如果我们发送的数据量,超过了拥塞窗口的大小,那么就可能会导致网络拥塞。如果发送数据量小于拥塞窗口的大小,我们就会认为有较大的概率,不会导致网络拥塞。
网络是变化的,就决定这个拥塞窗口是变化的。
所以我们此时要对滑动窗口有一个新的认识:滑动窗口 = min( 对方win ,拥塞窗口 )
谁小谁是主要矛盾,既考虑了网络拥堵问题,又考虑了对方接收能力的问题
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍。
此处引入一个叫做慢启动的阈值。
当 拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增
长。

首次的ssthresh值规定为16
所以我们在实际通信的时候,拥塞窗口在增加,我们发送的数据量一定在增加吗?
答案是不一定
所以当我们的拥塞窗口和真实的网络通信时:
- 正常发送数据,基于对方的接收能力进行流量控制
- 在我们正常发送消息的同时,我们的拥塞窗口的数字,也一直在更新。
加法增大的本质就是给我们探测新的拥塞窗口的值,本质是为了探测。
在发送数据的时候,如果突然发生了网络拥塞。
- 我们要将指数变为1,重新开始慢启动(除了支持慢启动,本质上也是重新开始探测网络健康)
- 在此处发生拥塞了,本质不就是探测出来了当前网络的拥塞窗口。
新的ssthresh值规定为:上一次网络拥塞时的数字除以2,这也叫做乘法减小
拥塞窗口在线性探测的过程中,会一直增大吗?
逻辑上讲,它就该一直增大,表明我在网络通信的过程中,我的网络特别的好。
但是理论上这个数字不会一直增大。
单位时间内,我们究竟能发多少数据量本质是由带宽硬件决定的,即便网再好,我们的带宽决定了我们的上限。
所以拥塞窗口这个数字再怎么增大,它增大到一定的值就不会增大了。
- 当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值;
- 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
当 TCP 通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
延迟应答
接收方等一会,就有可能给发送方,通告一个更大的窗口。就有较大概率更新出一个滑动窗口。下次发送消息时,就能发出更多的消息。
这种通过延迟给对方通告更大窗口,从而在较大概率上提高我们发送效率的机制,叫做延迟应答。
所以延迟应答是解决TCP效率问题。
捎带应答
捎带应答就是接收方将确认信息(ACK) 顺便搭载在即将要发送给对方的业务数据包中,实现"一包两用",从而减少网络包数量并提升传输效率。
面向字节流
创建一个 TCP 的 socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
- 调用 write 时, 数据会先写入发送缓冲区中。
- 如果发送的字节数太长, 会被拆分成多个 TCP 的数据包发出。
- 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去。
- 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区。
- 然后应用程序可以调用 read 从接收缓冲区拿数据。
- 另一方面, TCP 的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工。
粘包问题
问题:
当我们在读取报文的时候。我们并不清楚我们读到的是不是一个完整的报文,有可能读到第一次请求里面的后半部分报文和第二次请求中的前半部分报文,这叫做TCP中的粘包问题。
那么如何避免粘包问题呢?归根揭底就是一句话,明确两个包之间的边界
如何明确?
协议 + 序列和反序列化
- 对于定长的包,保证每次都按固定大小读取即可。
- 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道的了包的结束位置。
- 对于变长的包,在包与包之间使用明确的分隔符(应用层协议是程序员自己定义的,只要保证分隔符不和正文冲突即可)
TCP连接异常情况
- 进程终止: 进程终止会释放文件描述符,仍然可以发送
FIN,和正常关闭没有什么区别。 - 机器重启: 和进程终止的情况相同。
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行
reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放。
TCP小结:
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传计时器,保活定时器,TIME_WAIT定时器等)
TCP vs UDP
我们说了 TCP 是可靠连接, 那么是不是 TCP 一定就优于 UDP 呢? TCP 和 UDP 之间的优点和缺点, 不能简单, 绝对的进行比较。
- TCP 用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景。
- UDP 用于对高速传输和实时性要求较高的通信领域, 例如, 早期的 QQ, 视频传输等. 另外 UDP 可以用于广播。
归根结底, TCP 和 UDP 都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定。
用 UDP 实现可靠传输(经典面试题)
- 引入序列号,保证数据顺序
- 引入确认应答,确保对端收到了数据
- 引入超时重传,如果隔一段时间没有应答,就重新发送数据。
- 引入去重机制,接收方根据序列号判断,如果收到已存在的ID,则丢弃,解决重复 问题。
...
源码分析:
sock中有struct sk_buff_hand sk_reader_queue和 sk_buff_hand sk_reader_queue sk_write_queue这就是tcp中的发送缓冲区和接受缓冲区,UDP是直接读取一个一个的reader_queue的,这也是UDP是面向数据报的原因
struck inet_sock里面有原ip目的ip,源端口,目的端口等信息
inet_connection_sock里面又包含了request_sock_queue请求队列,把请求建立成功后的连接结构体放在这里面,
struct tcp_sock里面包含了inet_connection_sock里面又包含了inet_sock里面又包含了sock,sock就是未来指针指向的。未来建立三次连接,只需要创建一个tcp_sock结构就可以了,一旦创建了tcp_sock结构,后面的结构体就全都有了。当前的指针指向的是sock,如果我们想要访问inet_sock,只需要把该指针(sock)强转为inet_sick,其他的也类似。用c语言进行struct的嵌套,就能实现c语言版本的多态了。
udp_sock中保存的是inet_sock,里面保存的是ip地址端口号,也就是我们的网络信息,而inet_sock里包含了sock,而sock里面有对应的接受和发送队列(sk_receive_queue,sk_write_queue)而sock将来被struct_file指向
而udp中没有inet_connection_sock即没有连接相关的东西
👍 如果对你有帮助,欢迎:
- 点赞 ⭐️
- 收藏 📌
- 关注 🔔




