从网卡开始到TCP/IP协议栈的数据流转
网络请求达到应用程序的处理过程
一个数据包通过网线传输过来,按照网络模型划分,依次需要经过五层处理
当到达网卡之后
- 数据首先到达网卡,此时是光信号(光纤)或电信号(网线)
网卡首先将其还原成电信号
根据FCS(帧校验序列,Frame check Squence)校验数据,丢弃失真的数据
校验mac地址,不是发送给自己的则丢弃
发送中断信号通知CPU或者DMA
- CPU/DMA通过网卡驱动参与数据接收
CPU/DMA收到网卡的中断信号后,选择时机进入中断,通过网卡驱动读取拷贝网卡数据到内核缓存区
根据mac头部的以太类型字段判断协议种类并调用处理该协议的协议栈,这里使用TCP/IP协议栈举例
IP模块判断数据包是否发送给自己,不是则丢且
判断数据包是否分片(以太网中,一个包的最大长度不能超过1500字节),如果分片则缓存起来等待分片全部到达再还原成数据包
根据IP头部的协议号字段转发给TCP模块处理
- TCP模块解析数据
TCP模块根据TCP协议还原数据包
-
TCP 根据 标志位FLAGS 识别不同的报文,多个标志位可能同时为1,如:ACK与SYN,表示服务端收到建立连接请求之后的响应
FLAGS字段标识位 含义 SYN=1 请求建立连接 FIN=1 关闭连接 ACK=1 响应 PSH=1 有DATA传输 RST=1 连接被重置 URG=1 有紧急数据 如果是请求连接的包,首先检查接收方端口号,然后检查有没有与该端口号相同且处于等待连接状态的socket,如果有,则为这个套接字复制一个副本,将发送反IP,端口等必要信息写入套接字,同时分配用于发送缓冲区和接收缓冲区的内存空间
如果是正常数据包,TCP模块数据检查该包对应的socket,然后提取出数据,存放到缓冲区,如果此时socket缓冲区满了,则TCP数据传输阻塞.此时如果应用程序来调用socket的read(),那么数据就可以转交给应用程序,否则数据会一直保存在缓冲区中
-
通过Socket与应用程序交换数据
应用程序不能直接与内核进行IO通信,网络IO通过socket,文件IO通过文件句柄
CPU/DMA将数据拷贝到socket缓冲区之后,应用程序通过调用socket的read()方法将数据copy到应用程序进程内存中
如果应用程序是JAVA且使用堆内内存操作,那么还需要将数据从堆外copy到堆内,因为JVM建立了自己的内存模型JMM,不能直接识别堆外内存的数据
TCP三次握手
什么是连接?
为了数据的可靠传输,客户端与服务端通信之前,确认对方是否可通信,所做的通信前检查工作.
当检查完成后,客户端与服务端都会维护一个可通信的状态.
这就是建立连接的目的,即三次握手需要达成的目标
RFC 793对连接的定义如下:
Connections:
The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream.
The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.
用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合包括socket,序列号,窗口大小
客户端主动发起连接,发送syn包,进入syn-send状态
服务端检查可建立连接之后响应ack,并同时响应syn,要求客户端确认是否可建立连接,进入syn-rcvd状态
客户端收到ack报文,然后进入estalished状态,初始化socket,分配缓冲区内存
服务端收到ack,进入estalished状态,初始化socket,分配缓冲区内存
-
为什么是三次握手,不是两次
正常情况下,会按照如上步骤走完,但通常情况下,任何一个步骤都可能会异常
-
节省不必要的资源浪费
客户端需要确认服务端可连接后,再创建socket并分配内存空间
服务端需要确认客户端响应了最新的可连接后报文后,再创建socket并分配空间
-
避免历史连接
当客户端发送syn报文后,超时没有收到ack报文,那么会再次发送syn报文.旧的报文先与新的报文到达
客户端收到服务器对旧的syn响应的ack后,根据序列号和超时时间判断,这是服务端响应的历史报文,则发送RST终止连接
如果是两次握手,那么不能判断是否是历史连接
-
同步双方初始序列号
序列号是可靠传输的关键因素
对于接收方的作用
去除重复的数据
根据序列号按序接收
标识发送出去的数据包中,哪些是已接收的
为什么不是四次呢?
TCP建立连接的目的是确认双方建立连接的可靠性检查,三次握手已经达到目的
-
-
建立连接过程中服务器做了什么处理
服务器用两个队列来维护连接
一个是syn队列,保存未完成建立连接的队列
一个是accept队列,保存连接完成的队列
服务器TCP维护SYN与ACCEPT队列.png
当应用程序调用accept()时,从accept队列中获取一个已建立好连接的socket
也可以通过不同的内核参数,控制syn队列和accept队列的大小,达到优化程序的目的
-
socket底层流转
socket TCP 编程(1).png
服务端:
* 创建一个元socket,调用bind()绑定端口
* 调用listen() 告知内核监听此端口是否有连接到来,内核创建syn和accept两个队列。当有连接到来时,内核完成三次握手
* 调用accept阻塞获取完成连接的accept队列新的socket
TCP四次挥手
TCP是全双工的通信协议,客户端与服务端都可以主动断开连接,区分为主动关闭连接的一方,被动关闭连接的一方
客户端发送FIN报文,进入fin-wait状态
-
服务端响应ack,进入close-wait状态
客户端收到ack报文,进入 fin-wait2状态
服务器处理完数据,发送fin报文,进入last-ack状态
-
客户端收到fin报文,响应ack,进入time-wait状态
服务器收到ack报文,连接关闭,进入close状态
客户端在经过2MSL(maximum segment lifetime 报文最大生存空间) 一段时间后,自动进入close状态
在四次挥手过程中,当服务端接收到客户端断开连接的fin报文后,需要等待数据处理的完成,所以先回复ack,然后再发送fin报文。因此需要四次
为什么客户端收到fin返回ack不直接进入close状态,而是进入time-wait状态呢?
为了帮助被被动关闭的一方能够正确的关闭。如果ack报文在网络中丢失,那么服务端会重发fin报文,客户端处于time-wait状态,仍然可以响应ack
防止收到旧的四元组的包,如果客户端直接关闭连接,使用相同的四元组新建连接,那么可能收到旧连接的数据报文
TCP数据传输
TCP是一个可靠传输协议,通过序列号,确认应答,重发控制,连接管理以及窗口控制等机制实现可靠性传输
-
重传机制
发送方发送一个数据,当接收方接收到后,会回应一个ack,表示报文已经收到。
TCP使用SACK和D-SACK的方式,首先发送方发送一个完整数据序号列表,接收方接收数据时,根据数据列表确定哪些没有收到,需要客户端再次发送
-
滑动窗口
TCP如果每次只发送一个数据包,那么会导致效率非常低,因此使用一个滑动窗口来发送数据
窗口指无需等待确认应答,而可以继续发送数据的最大值
-
流量控制
发送方与接收方的速率并不相同,因此,需要协商一个双方都能接收的速度
TCP提供一种机制可以让发送方和接收方的时机接收能力控制发送的数据量,这就是流量控制
具体是指:当发送端发送一个滑动窗口的数据,接收方根据处理能力,调整滑动窗口的大小,返回给发送端
-
拥塞控制
当数据传输受网络带宽等其它影响,接收方不能及时的返回应答
因此发送方根据超时量去主动调整滑动窗口的大小,这就是拥塞控制
UDP
UDP不提供复杂的控制机制,利用ip提供无连接的通信服务
UDP传输时,只管发送,并不考虑接收端是否已经接收到,因此存在丢包的可能
应用场景:直播,DNS服务