文章目录
一、前言
本系列虽说本意是作为 《Netty4 核心原理》一书的读书笔记,但在实际阅读记录过程中加入了大量个人阅读的理解和内容,因此对书中内容存在大量删改。
本篇涉及内容 :第一章 网络通信原理
本系列内容基于 Netty 4.1.73.Final 版本,如下:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.73.Final</version>
</dependency>
系列文章目录:
【Netty4核心原理】【全系列文章目录】
使用 Socket 的目的就是实现 C/S 软件架构的服务端与客户端之间的网络通信。
二、C/S 信息传输流程
完成一次网络通信,大致要经过以下5个步骤:
- 客户端产生数据,存放于客户端应用的内存中,然后调用接口将自己内存中的数据 发送/拷贝 给操作系统内存。
- 客户端操作系统收到数据后,按照客户端应用指定的规则(即协议),调用网卡并发送数据。
- 网络传输数据。
- 服务端应用调用系统接口,想要将数据从操作系统内存拷贝到自己的内存中。
- 服务端操作系统收到指令后,使用与客户端相同的规则(即协议)从网卡读取数据,然后拷贝给服务端应用。
三、TCP/IP 五层模型
按照功能不同,人们将互联网协议从不同维度分为 OSI 七层、TCP/IP 五层或者 TCP/IP 四层,如下图所示:
1. 物理层
物理层主要是基于电器特性发送高低电平信号,即高压代表 1 ,低压代表 0。
2. 数据链路层
单纯的 0和1 没有意义,因此在实际应用中会将电平信号进行分组处理,多少位一组,每组什么意思,这样数据才具有意义。数据链路层的功能就是定义电平信号的分组方式。
2.1 以太网协议
数据链路层使用的以太网协议进行数据传输,基于 MAC 地址的广播方式实现数据传输,只能在局域网内广播,后业界对这个数据分组方式进行了统一,即 以太网协议。
2.2 以太网 Ethernet
由一组电平信号构成一个数据包,叫做“帧”,每一数据帧由报头 Head 和 Data 两部分组成。数据包的具体内容格式为 :Head 长度 + Data 长度 = 最短 64 字节,最长 1518 字节(超过最大限制就分片发送)。
2.3 MAC 地址
Head 中包含的源地址和目标地址,Ethernet 规定接入 Internet 的设备必须配有网卡,发送端恶化接收端的地址便是指网卡的地址,即 MAC地址。MAC 时一个长度为 48 位的二进制数,通常用 12位十六进制数表示(前6位厂商编号,后六位流水线号),每块王凯出厂时都被印上世界上唯一的MAC 地址。
2.4 Broadcast广播
有了 MAC地址,同一网络内的两台主机就可以通信了(一台主机通过ARP 协议获取另外一台主机的 MAC 地址)。
Ethernet 采用了广播的形式进行通信,即在一个局域网下,所有在这个局域网下的计算机都会收到以太网发来的消息。
3. 网络层
上面介绍了Ethernet 是以广播方式发送数据包,这种方式显然不能应用于互联网中。
全世界的网络是一个个彼此隔离的局域网组成,以太网包只能在一个局域网内发送,一个局域网是一个广播域,跨 广播域通信只能通过路由转发。因此必须要找到一种方式来区分哪些计算机是属于同一广播域。在同一广播域下就可以通过广播的方式发送,否则就通过路由方式发送。而 MAC 地址是无法区分广播域的,因此引入了网络层来解决这个问题:网络层引入了一套新的地址来区分不同的广播域/子域,这套地址就叫做网络地址。
3.1 IP 地址
规定网络地址的协议叫做 IP 协议,他定义的地址成为 IP 地址,以 IPV4 为例,一个 IP 地址由两部分组成:网络部分(用来标识子网)和主机部分(用来标识主机)
IP 的作用主要有两个: 一是为每台计算机分配IP地址,另一个是确定哪些地址在同一个子网络中。
单纯的 IP 地址段只是标识了 IP 地址的种类,从网络部分或主机部分都无法辨识一个 IP地址所处的子网。
3.2 子网掩码
子网掩码表示子网络特征的一个参数,他在形式上等同于 IP 地址,也是一个 32位二进制数字,他的网络部分全为 1,主机部分全为 0,通过子网掩码就能判断任意两个 IP 地址是否处于同一个子网络。方法是将两个 IP 地址与子网掩码分别进行 & 运算(两个数位都为 1 则为1,否则为 0),然后比较结果是否相同,如果相同则表明他们在同一个子网络中,否则不在。
如下,以 172.16.10.1 和172.16.10.2 为例,假设他们的子网掩码都是 255.255.255.0,如下计算过程可以得到他们的计算结果都是 172.16.10.0,因此他们处于同一个子网络中。计算如下:
3.3 ARP(Address Resolution Protocol 地址解析协议)
ARP 用于实现从 IP 地址到 MAC 地址的映射,即询问目标 IP 地址对应的 MAC 地址,以广播的形式发送数据包,获取目标主机的 MAC 地址。计算机每发出一包数据,都需要填写 数据链路层的源 MAC、目标MAC 网络层的源 IP 和 目标 IP,源 MAC一般是本机地址,目标MAC 由ARP 协议获取,源IP 和 目标IP 由用户指定。而如下图:
以下面的案例为例
- 主机 A 的 IP 地址是 10.1.20.64,MAC 地址为 00:08:ca:xx:xx:xx
- 主机 B 的 IP 地址是 10.1.20.109,MAC 地址为 44:6b:57:xx:xx:xx
当主机 A 要与 主机 B 通信时,ARP 可以将主机 B 的 IP 地址解析成主机 B 的 MAC 地址,以下为工作流程。
- 主机 A 检查本地ARP缓存:主机需要和目标IP通信时,首先检查本地ARP缓存表,如果缓存表中有对应的IP-MAC映射且未过期,则直接使用,如果缓存表中没有对应记录或已过期,则需要发起ARP请求。
- 如果没有ARP缓存,主机A 会先通过子网掩码判断目标IP在同一子网。
- 如果主机A,B 属于同一子网,则主机 A根据 ARP 协议构建请求包,请求包的内容包括 源 mac(本机mac地址)、目标 mac(因为未知在网络层用 00:00:00:00:00:00 占位,数据链路层则用 ff:ff:ff:ff:ff:ff 代表)、源 ip(本机ip)、目标 ip(目标机器ip)。同一子网下的所有机器都会收到主机 A 的 ARP 的请求,只有与 目标 IP 匹配的主机 B 才会回复消息(这里的回复不再是广播,而是单播),回复的消息内容包括自身的 MAC 地址。
- 主机 A 收到主机 B的单播回复后,会将其缓存到自己本地 ARP 缓存中(本地 ARP缓存是有生存期的,生存期结束后,会再次重复上面的过程)。
- 主机 B 的 MAC 地址一旦确定,主机 A 就可以向主机 B 发送 IP 地址了。
- 如果主机 A,B 不属于同一子网,主机A 仍会通过 ARP 协议发送消息,不过目标 MAC 会变成 默认网关的 MAC 地址(如果主机 A不知道 默认网关的 MAC 地址,也会发起类似上述的 ARP 请求询问以获得默认网关的 MAC 地址)。
- 默认网关收到主机 A 的消息后,会检查目标 IP 是否在其路由表中,如果在,则网关会响应自己的 MAC 地址。
- 如果目标 IP 不在默认网关的路由表中,则网关会转发 ARP 请求:默认网关会查找路由表确定下一跳,并向目标网段发送新的 ARP 请求并等待目标主机的ARP。获得目标MAC后,网关会修改数据包(源MAC 修改为 网关 MAC,目标 MAC 修改为 目标主机 MAC),随后根据路由表信息选择出接口并转发数据包。
4. 传输层
通过上面的描述可以得知 :网络层 的 IP 地址可以用来区分子网,以太层的 MAC地址可以找到主机,但我们需要找到对应的应用程序,还需要端口来标识。端口就是应用程序与网卡关联的编号。
主机端口的取值范围是 0-65535,其中 0-1023 为系统保留端口的取值范围,也叫 BSD 保留端口,用户可注册的端口范围是 1024-49152,还有随机动态端口的范围是 49152-65535。
因为 TCP 协议的头部留给存储端口的空间只有 2 字节,最大值就是65535,因此端口的最大数就是65535。
4.1 TCP (传输控制协议)和 UDP(用户数据报协议)
TCP 协议是一种可靠传输协议。TCP 数据包没有长度限制,理论上可以无限长,但是为了保证网络效率,通常 TCP 数据包的长度不会超过 IP 数据包的长度,以确保单个 TCP 数据包不必再分割。而UDP 协议是一种不可靠传输协议。报头部分总狗友 8字节,总长度不超过 65535字节。
TCP 数据结构如下图:
4.2 TCP 协议和 UDP 协议的区别
- TCP是面向连接的,UDP是面向无连接的:TCP 传输前需要通过三次握手建立连接,UDP则不需要,直接发送
- TCP是可靠的,UDP是不可靠的:TCP 会建立连接后发送,并且会确定消息收发状态;UDP则不建立连接并且不关心对方是否收到了消息。并且 UDP 没有拥塞控制,会一直以一定速度发送数据,即使网络环境不好,也不会对发送速率做调整,在网络不好时就会产生丢包。而在视频会议等这种实时要求性较高的场景下,使用UDP较为合适,因为就算丢了部分包,也不影响视频的大部分内容。
- 在 TCP 协议中使用了接收确认和重传机制,确定了每个消息都能到达,是可靠的
- 在 UDP 协议中则没有应答和重传机制,UDP只是将消息发送出去,对方收不收到也不进行应答,所以是不可靠的
- TCP 是面向字节流的,UDP是面向报文的 :
- TCP 基于流的传输表示 TCP 不认为消息是一条一条的,是无保护消息边界的,所以 TCP 会存在粘包。
(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。 - UDP 面向报文,是有保护消息边界的,接收方一次只能接受一条独立的消息。所以UDP不存在粘包
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕,那么就会造成接收方一次会接收不止一条消息,这就是粘包。
- TCP 基于流的传输表示 TCP 不认为消息是一条一条的,是无保护消息边界的,所以 TCP 会存在粘包。
- TCP 只有一对一传输方式, UDP 可以一对一,一对多,多对多
- UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。
- TCP不能一对多的原因是:TCP通信前要跟一台主机进行三次握手连接,因此TCP不能对多。
- TCP 头部开销比 UDP大 : UDP 头部开销只有8个字节, TCP 至少20个字节
- TCP 会产生粘包问题,UDP会产生丢包问题
- 粘包:TCP产生粘包问题的主要原因是:TCP是面向连接的,所以在TCP看来,并没有把消息看成一条条的,而是全部消息在TCP眼里都是字节流,因此A、B消息混在一起后,TCP就分不清了。粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对先接收包体长度,依据包体长度来接收包体
- UDP 丢包问题:
- 主要丢包原因:接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。
- 发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。
- 发送的包较大,超过接受者缓存导致丢包:包超过mtu size(mtu表示最大传输单元)数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。
- 发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。
4.2 TCP 的三次握手和四次挥手
-
三次握手
第一次握手:建立连接时,客户端发送SYN包(syn=1)到服务器,并进入 SYN_SEND 状态
第二次握手:服务器收到 SYN 包,必须确认客户端的 SYN 包(ack = x +1),同事自己也发送一个 SYN 包(syn=1),即 SYN + ACK 包,此时服务器进入 SYN_RECV 状态。
第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 AC(ack=y+1),此包发送完毕,客户端和服务端进入了 ESTABLISHED(TCP连接成功)状态,完成三次握手。 -
四次挥手
第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。
补充:
- 为什么三次握手和四次挥手?
三次握手时,服务器同时把ACK和SYN放在一起发送到了客户端那里
四次挥手时,当收到对方的 FIN 报文时, 仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送。- 为什么客户端最后还要等待2MSL?
客户端需要保证最后一次发送的ACK报文到服务器,如果服务器未收到,可以请求客户端重发,这样客户端还有时间再发,重启2MSL计时。
5. 应用层
在日常操作中,用户使用的都是应用程序,应用程序都工作在应用层。应用层的功能就是规定应用程序的数据格式。如下图是应用层协议的基本组成结构示意。
四、网络通信实现原理
要想实现网络通信,每台主机需要具备四要素:本机IP地址、子网掩码、网关IP地址和 DNS IP 地址。获取这四要素可以通过手动配置的静态获取或者通过 DHCP(动态主机配置协议)获取。
当一台计算机加入网络后,会经过如下流程得知自身的四要素信息。
如下是网络通信数据结构示意图:
具体图解如下:
- 以太网头部:设置发出方(本机)的MAC地址和接收方(DHCP 服务器)的 MAC 地址,前者就是本机网卡的MAC 地址,后者此时并不知道,就填入一个广播地址 FF-FF-FF-FF-FF-FF。
- IP 头部:设置发出方的IP地址和接收方的 IP 地址。此时对于这两者本机都不知道,于是发出方的 IP 地址被设置为 0.0.0.0,接收方的 IP 地址设置为 255.255.255.255。
- UDP头部:设置发出方的端口和接收方的端口,这部分是 DHCP规定好的,发出方是 68 端口,接收方是 67 端口。
这个数据包构造完成后,就可以发出了。以太网是广播发送,所以同一个子网下的每台计算机都收到了这个数据包。因为接收方的 MAC 地址是 FF-FF-FF-FF-FF-FF,看不出是发给谁的,所以每台收到这个数据包的计算机都需要分析这个数据包的 IP 地址,才能确定是不是发送给自己的。看到发出方IP地址是 0.0.0.0,接收方IP地址是 255.255.255.255,于是 DHCP 服务器知道这个数据包是发送给自身的,其他计算机就可以丢弃这个包。
随后 DHCP 服务器读出这个包的数据内容,分配好 IP 地址,发送回去一个 “DHCP 响应” 的数据包。这个响应包的结构也是类似的,以太网头部的MAC 地址是双方的网卡地址,IP 头部的 IP 地址是 DHCP 服务器的 IP地址(发出方)和 255.255.255.255(接收方),UDP 头部的端口是 67(发送方)和 68(接收方),分配给请求端的 IP 地址和本网络的具体参数包含在Data部分。
新加入的计算机收到这个响应包,就得知了自己的IP地址、子网掩码、网关地址、DNS服务器等参数。
五、Socket
两个进程进行通信的一个最基本的前提是能够唯一地标识一个进程。在本地进程通信中,我们可以使用PID 来唯一标识一个进程,但是 PID 仅在本地唯一,网络中两个进程 PID 冲突的概率很大,这时候就不能使用PID了。IP 层的 IP 地址可以标识唯一主机,而 TCP 层的 协议和端口可以唯一标识主机的一个进程,因此可以使用 IP 地址 + 协议 + 端口号唯一标识网络中的一个进程。能够唯一标识网络中的进程后,他们之间就可以通过 Socket 进行通信了。
Socket 被翻译为 套接字,是应用层和传输层之间的一个抽象层,他把TCP/IP 层负责的操作抽象为几个简单的接口供应用层调用,以实现进程在网络中的通信,具体结构如下:
Socket 起源于 UNIX,在 UNIX “一切皆文件” 的哲学思想下,Socket 是一种从打开到完成读写操作最后关闭的模式,服务端和客户端各自维护一个 “文件”,在建立连接打开文件后,可以向自己的文件写入内容供对方读取或读取对方的内容,通信结束时关闭文件。
补充 :
-
Socket是一种长连接, 通常情况下Socket 连接就是 TCP 连接,因此 Socket 连接一旦建立,通讯双方开始互发数据内容,直到双方断开连接。在实际应用中,由于网络节点过多,在传输过程中,会被节点断开连接,因此要通过轮询高速网络,确保该节点处于活跃状态。很多情况下,都是需要服务器端向客户端主动推送数据,保持客户端与服务端的实时同步。
-
Websocket 和 Socket 的区别(内容摘录 webSocket和Socket,详参原文):
总结来说 :socket 是一套抽象接口,把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。而 Websocket 是基于 TCP 的一种新的网络协议,属于应用层的一种协议(Netty 就是基于 websocket 协议实现的)。- socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信;WebSocket协议是基于TCP的一种新的网络协议,和http协议一样属于应用层协议,是一种让客户端和服务器之间能进行双向实时通信的技术。
- Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口(不是协议,为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口)。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面。利用TCP/IP协议建立TCP连接。(TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。)WebSocket则是一个典型的应用层协议,包含一套标准的 API 。
- Socket 是传输控制层的接口。用户可以通过 Socket 来操作底层 TCP/IP 协议族通信。网络中的 Socket 并不是什么协议,而是为了使用 TCP,UDP 而抽象出来的一层 API,它是位于应用层和传输层之间的一个抽象层。Socket 是对 TCP/IP 的封装;HTTP 是轿车,提供了封装或者显示数据的具体形式;Socket 是发动机,提供了网络通信的能力。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次 HTTP 握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
六、参考
- 豆包
- 《Netty4 核心原理》
- webSocket和Socket