目录
一、传输层
1、再谈端口号
在TCP/IP协议中,用源IP,源端口号,目的IP, 目的端口号,协议号这样一个五元组来标识一个通信(可以通过netstat-n 查看)
2、端口号范围
0-1023:知名端口号,HTTP, FTP,SSH等这些为使用的应用层协议,他们的端口号都是固定的
1024-65535:操作系统动态分配的端口号,客户端程序端口号,就是操作系统从这个范围分配的
3、UDP协议

什么是协议
客户端和服务端约定的结构化字段

端口号为什么是16位
内核认为端口号为16位 报头和有效载荷分离如何做到 报头长度是固定的
为什么说UDP是面向数据报的
(1)对报文的重新理解
OS内部一定存在大量的的报文,不同层也一定存在多个报文,我们需要先描述,在组织
(2)系统中的报文


(3)封装
data -= (sizeof(tcphdr/其他报头))
(struct udphdr *) data -> XX = YY
(4)解封
(mac*) (sk_buff->data) -> XXX;
sk_buffer-> data+= sizeof(IP)
4、文件系统和socket的关系


5、TCP协议

系统中的存在形式

如何分离报头和有效载荷
报头固定长度位20
如何分用
有目的端口号
4位首部长度
表示报头+选项的总长度,单位是:4字节(范围20到60,选项最多40)
如何确保TCP的可靠性(解决丢包)
确认应答性质

TCP常规的通信模式

无论那个朝向的人给我发送一个消息,我都给他发送一个应答,互相保证两个朝向的可靠性,
这种方式十分的低下,在上一个信息得到应答之前我们不可以做任何的事
TCP通信的一般模式

我们发送一堆的信息,在返回一堆的应答,但是发送顺序和接受的顺序并不是一致的,收到的消息可能是乱序的,所以为了保证可靠性,我们报文中有一个序号,将发送的信息进行编号,保证不是乱序的,但是应答我们无法确定是谁的,所以我们需要报文中的确认序号
确认序号
ask_seq表明:该数字之前的报文我已经全部收到,下此发送,请从ack_seq发送
不是对一个报文确认,是对历史所有报文的确认
互相发的是什么
TCP报文
为什么要有两个序号
互相发送的报文可能存在捎带应答(即发送信息又是对历史消息的应答)
16位窗口大小
在我们发送的时候,有时候会有大量的数据,导致对端主机来不及接受,所以我们需要让发送方,知道对端的接受能力(16位窗口表示自己的缓冲区剩余空间的大小)
细节一:流量控制是双向的(如果单行发送,就只做单向流量控制)
细节二:窗口填写的是自己接受剩余空间的大小
标志位

什么是标志位
结构体位端中的比特位,0(无效)1(有效)
为什么有标志位
区分报文的类型
ACK
置1表示该报文是一个确认报文
SYN
置1表示请求建立链接,我们把携带SYN标识的称为同步报文段
FIN
通知对方,本端要关闭l,我们称携带FIN标识的为结束报文段
PSH
提示接收端应用程序立刻从TCP缓冲区把数据带走
细节一:PSH标志位,即便对方还有空间也可以被设置
细节二:应用程序,怎么取数据,应该是应用层自己决定的,所以PSH的功能类似于唤醒进程
RST
用来处理异常的时候,让对方进行链接重置
例如:TCP建立链接是三次握手,不一定100%建立链接成功,如果发送端认为链接已经建立成功但是链接实际没有建立成功,发送端发送消息,对端会发送RST,让链接重置
URG
TCP保证了协议的按序到达,如果有紧急数据,依旧需要排队,所以URG置1表示:内部存在紧急数据由16位紧急指针来指向,当前报文有效载荷部分的一个偏移量,紧急数据一共有1个字节
一种可靠性和效率提高的策略
理解TCP缓冲区
在逻辑上我们可以吧TCP的接受缓冲区,想象成线性结构

这样确认序号和序号就可以自动转化为数组下标
但是物理上不是一个线性结构他是不连续的,实际的缓冲区如下:

6、TCP超时重传机制
如何看待丢包问题
- 我发的报文,你有没有收到 我知道吗? 知道,如果收到应答则对方一定收到了
- 我发的报文,丢失了,我知道吗?不知道,没有收到报文有两个情况:我发的报文丢失了,对方的确认应答丢失了
超时重传
如果我们很长时间都没有接受到应答,我们不可能一直等待下去,所以我们需要约定一个时间,确认应答如果超时了,我们判定超时(不叫判定丢包)重发
超时时间
因为网络的畅通程度时浮动的 ,超时时间一定也必须是浮动的,如果太长会导致发送效率降低,而太短会导致误判,重传机制可能被高频触发
在 Linux、BSD Unix 及 Windows 系统中,TCP 超时重传机制以 500ms 为固定基础单位,超时时间始终是该单位的整数倍,具体规则如下:
- 首次重传后若未收到应答,等待 2×500ms 进行第二次重传;
- 若第二次重传仍无应答,超时时间按指数级递增,下一次等待 4×500ms 再重传,后续以此类推(即每次超时时间翻倍);
- 当重传次数累计达到阈值时,TCP 会判定网络链路或对端主机出现异常,进而强制关闭当前连接。
7、TCP链接的管理机制

三次握手
TCP进行链接的时候需要进行三次握手,代码中由connect发起双方OS自主完成

注意:accept并不参加三次握手
互相发送的是TCP报文
TCP三次握手,为什么是三次
1、验证全双工
验证双方都可以收发消息,网络是通畅的
2、建立双方要通信的共识
三次握手的本质其实是四次握手,因为捎带应答,变成了三次,一发一答确立的双方要通信的共识
互发消息

write本质是拷贝,将信息拷贝到发送缓冲区中。
四次挥手
TCP关闭链接需要进行四次挥手

为什么是四次挥手
双方建立要结束链接的共识
可以三次挥手吗
FIN表达的意思是我要发的数据已经发完了,我要关闭一个通道了(逻辑上),可能客户端关闭链接,但是服务端还有信息发送给客户端
CLOSE_WAIT
一端主动断开链接,另一端未断开链接,另一端会进入ClOSE_WAIT状态,存在fd泄漏问题
TIME_WAIT
主动断开链接的一方,4次挥手已经完成,自己不能立即退出,而是要在等待一段时间(2MSL报文生存的最大生存时间),让自己处于TIME_WAIT状态
TIME_WAIT的作用
理由一:防止陈旧报文对新连接产生干扰,让陈旧报文从网络中消散, 强制要求更换端口号
(但是服务端不可以随意的更改端口号,所以需要设置服务端即使在time_wait状态也可以立即重启)

理由二:保证四次挥手的正确结束
如何保证陈旧报文对新连接产生干扰
1、建立链接握手的时候,交换随机报文起始序号
8、滑动窗口
什么是滑动窗口
指的是暂时无需等待确认应答而可以继续发送数据的最大值
滑动窗口在哪里
发送方的发送缓冲区中
如何滑动

滑动窗口的存在,把发送缓冲区分成了三个部分
左边:已发送,已确认的数据
中间:直接发,暂时不用应答
右边:待发送和空位置
滑动窗口滑动的方向:从左到右
滑动窗口的本质:下标增多,就是滑动
滑动窗口的大小
start = 确认序号
end = start + win
滑动窗口只能向右滑动吗, 能不能向左
不能,因为未发送的数据在右边
滑动窗口的范围大小会变吗
变小:给对方发送数据,但是对方不取数据,接收缓冲区变小,滑动窗口变小
变大:给对方发送数据,对方将所有未取的数据一次性取走了,接收缓冲区变大了,滑动窗口变大
不变:给对方发送数据,对方取等量的数据走,接收不变,滑动窗口不变
9、报文丢失
确认应答表示该数字之前的报文我已经全部收到了
如下如果一个报文丢失了,那么他会应答都是历史已经收到的报文,收到三个同样的确认应答时会启动快重传,快重传提高速率上限,超时重传用来兜底

如果左侧报文丢失(1001~2000)

那么后面所有的确认应答都是1001,导致滑动窗口左侧不会移动(数据传输过程,丢报了,该数据不能从滑动窗口中删除)
如果中间报文丢失(3001~4000)
那么后面所有的确认应答都是2001,滑动窗口的左侧会移动到3001不动,将中间报文丢失传化成左侧报文丢失
如果最右侧报文丢失(4001~5000)
同理会变成左侧报文丢失
10、应答丢失

这种情况下,部分ACK丢了不要紧,可以通过后续的ACK进行确认
11、流量控制
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制。
接收端会把自身可接收的缓冲区剩余空间大小,填入 TCP 首部的 "窗口大小" 字段,通过 ACK 报文告知发送端;
- 窗口大小字段的数值越大,意味着网络的吞吐量越高;
- 一旦接收端察觉到自己的缓冲区即将填满,就会把窗口大小设置为更小的值并通知发送端;
- 发送端收到这个窗口信息后,就会降低自身的发送速度;
- 要是接收端的缓冲区已经满了,就会将窗口值设为 0;此时发送端不再发送数据,但需要定期发送一个窗口探测数据段,让接收端把当前的窗口大小告知自己。

接收端如何把窗口大小告诉发送端

TCP报文中有一个16位窗口字段
缓冲区大小是否只有65535
不是,TCP报文中选项字段有包含一个窗口扩大因子M,表示窗口字段的值左移M位
刚开始是如何知道对方的窗口大小
三次握手报文交换彼此窗口大小
12、拥塞控制
TCP的滑动窗口机制堪称高效可靠传输大量数据的"大杀器",但即便如此,若在连接刚建立的初始阶段就盲目发送海量数据,依然可能引发网络问题。
究其原因,网络中存在着众多计算机设备,当前的网络状态本身就可能处于拥堵状态。而在尚未摸清网络实时状况的情况下,贸然发送大量数据,无疑会让原本就拥堵的网络"雪上加霜",进一步加剧传输困境。
因此,TCP专门引入了"慢启动"机制:在连接初期,先发送少量数据作为"探路包",通过这些数据的传输反馈来摸清当前的网络拥堵情况,之后再根据探测到的网络状态,动态决定后续的数据传输速率,从而实现更平稳、高效的传输。

此处引入一个概念称为拥塞窗口
- 发送开始的时候,定义拥塞窗口大小为1;拥塞窗口是一个int,用来衡量网络的拥塞程度的
- 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;end = start + min (win ,拥塞窗口)
- 慢启动最开始是指数正常的,指数函数前期慢(进行探测)增长快(探测后确定不怎么拥堵后尽快恢复正常通信)
- 为了不让它一直快速增长,引入慢启动的阈值,当拥塞窗口超过这个阈值按照线性方式增长
- 当TCP开始启动的时候,慢启动阈值等于窗口最大值(最大值= 带宽*RTT)

- 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回为1
- 少量丢包,我们仅仅是触发超时重传,大量丢包,我们认为网络拥塞(一般1%~1.5%正常)
13、延迟应答
接收方在接收到信息的时候,不立即应答,而是等一等,概率上接收端的上层回取走一些,应答的时候可以更新一个更大的滑动窗口提升效率
等一等的策略:
- 小于最大报文生存时间
- 数量限制:每隔N个包就应答一次(N一般为2)
- 时间限制:超过最大延迟时间就应答一次(时间一般取200ms)
14、捎带应答
在应答的同时可以携带传递给对端的信息
面对字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区:
- 调用write时, 数据会先写入发送缓冲区中;
- 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
- 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
- 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
- 然后应用程序可以调用read从接收缓冲区拿数据;
- 另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工
- 由于缓冲区的存在, TCP程序的读和写不需要一一匹配,
- 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节; • 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
15、如何解决粘包问题
- 固定包的大小
- 包的头部加上包的总长度
- 包和包之间使用分隔符
16、TCP异常情况
进程终止:进程终止会释放文件描述符,仍然可以发送FIN。和正常关闭没有什么区别。
机器重启:和进程终止的情况相同。
机器掉电/网线断开:发送端网线掉了,回触发硬件中断自动断开链接,接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。发现对方释放sockfd,系统回自动发送信号,杀死进程。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。
另外,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接。