目录
1. 开发中常见的数据组织格式
1.1 XML
1.2 JSON
1.3 Protobuf
2. 端口号
3. UDP协议
4. TCP协议
4.1 特点
4.2 TCP报文格式
4.3 TCP可靠性机制
4.3.1 确认应答机制
4.3.2 超时重传机制
4.3.2.1 丢包的两种情况
4.3.2.2 重传时间
4.3.3 连接管理机制
4.3.3.1 三次握手建立连接
4.3.3.2 四次挥手释放连接
4.3.3.3 建立连接与释放连接的总过程
4.4 TCP效率提高机制
4.4.1 滑动窗口协议
4.4.1.1 数据传输示意图
4.4.1.2 滑动窗口
4.4.1.3 超时重传机制
4.4.1.3.1 第一种情况:ACK丢失
4.4.1.3.2 第二种情况:数据包丢失
4.4.2 流量控制
4.4.3 拥塞控制
4.4.4 延时应答
4.4.5 捎带应答
4.5 粘包问题
4.5.1 粘包问题描述
4.5.2 粘包问题的解决方法
4.6 异常情况的处理
4.6.1 进程崩溃
4.6.2 主机关机(正常流程)
4.6.3 主机掉电(非正常)
4.6.4 网线断开
5. TCP与UDP的对比
1. 开发中常见的数据组织格式
1.1 XML
1. XML属于较早时期组织数据的格式,通过标签来组织数据;
2. 可以认为HTML是XML的变种,HTML的标签是规定好的,不允许程序员自创;
而XML的标签都是程序员自定义的。
以某请求为例:
<request><userId> 1000 </userId><position> 100,30 </position>
</request>
3. XML的优势:可读性强;
XML的劣势:标签编写非常繁琐,传输时也会占用更多网络带宽;
4. 目前阶段,在maven项目中会使用XML管理项目配置。
1.2 JSON
1. JSON是当前最流行的一种数据组织格式。
2. JSON是一种键值对结构,用一个{ }包裹所有键值对,如:
{userId:"1000",position:"100, 30"
}
其中,键值对之间用逗号分割,键与值之间用冒号分割,键为String类型(键的双引号可以省略),值可以为数字、字符串、JSON、数组等等;
2. JSON的优势:可读性强,比XML更简洁;
JSON的劣势:在网络传输中会消耗额外的带宽(key也需传输);
1.3 Protobuf
1. 相比与json与xml,Protobuf(pb)是使用二进制的方式来组织数据的;
2. pb相当于把要传递的信息按照二进制形式压缩了,故而可以保证带宽占用最低;
3. pb的优势:占用带宽最低,传输效率最高,非常适合对于性能要求较高的场景;
pb的劣势:可读性较差,一定程度上影响开发的效率;
2. 端口号
在编写一个服务器时,必须手动指定一个端口号,以区分当前主机上的不同应用程序;
在编写一个客户端时,系统会在客户端通信时自动分配一个端口号;
端口号长度固定为2字节,能表示的数据范围为0~65535,一般来说0是不使用的,其中:
(1)1~1023:熟知端口号,给一些知名服务器预留的端口号
如:22:SSH服务器(远程登录主机); 80:HTTP服务器; 443:HTTPS服务器
(2)1024~49151:登记端口号,要使用这类端口号必须在IANA按照规定的手续登记;
(3)49152~65535:短暂端口号,客户进程运行时进行动态选择,客户进程时临时使用;
3. UDP协议
1. 特点:无连接,不可靠,面向数据报,全双工;
2. 报文格式:
(1)UDP报文长度字段为16位,能表示的数据范围为0~65535,即64Kb,故而使用UDP时很难表示一个很大的数据报;,当传输较大数据时会发生截断;
(2)常用的校验方法有:CRC冗余校验、MD5、SHA1算法等;
3. 基于UDP的应用层协议有:
(1)NFS:网络文件系统;
(2)TFTP:简单文件传输协议;
(3)DHCP:动态主机配置协议;
(4)BOOTP:启动协议(用于无盘设备启动);
(5)DNS:域名解析协议;
4. TCP协议
4.1 特点
1. 有连接,
2. 面向字节流,
3. 全双工:
体现在代码中:
4. 可靠传输:(TCP最核心的特性)
TCP保证可靠传输是以确认应答为核心,借助其他机制辅助,最终完成可靠传输;
TCP实现可靠传输的核心机制有:
(1)确认应答:
发送方发送数据后,接收方收到数据则返回一个应答报文(acknowledge,ack);
发送方收到应答报文就知道数据是否发送成功。
(2)超时重传:
在TCP传输过程中,丢包可能出现传输数据丢失和返回的ack丢失两种情况,
但在发送方端是无法辨别的,但无论是出现了哪种情况,发送方都会进行重新传输。
重传操作大幅度提升了数据传输成功的概率,是重要的丢包补救措施。
(3)连接管理:
连接管理即建立连接(三次握手)和释放连接(四次挥手);
4.2 TCP报文格式
(1)TCP数据报=首部(报头)+数据载荷。其中报头长度不固定:
当选项部分不存在时,报头最短,为20字节;
当选项选中且长度最长(40字节)时,报头最长,为60字节。
(2)首部(4位),表示的范围为0~15,单位为4字节;
即当首部为0xF(15)时,表示首部长度为15×4字节=60字节,达到首部最大长度;
4.3 TCP可靠性机制
4.3.1 确认应答机制
注:(1)在TCP报文首部中,有一个ACK字段值为1时,该数据报中的“确认序号字段”有效;
当ACK字段为0时,表示当前数据报中的“确认序号字段”无效;
(2)TCP是按照字节进行编号(序列号)的:
4.3.2 超时重传机制
4.3.2.1 丢包的两种情况
对于第二种ACK丢失的情况,站在主机B的角度收到了2条数据,会不会导致bug是需要思考的问题。
TCP设置了一个接收缓冲区,在该内存空间中保存已经收到的数据及数据的序号。
如果接收方发现当前发送方发来的数据已经在缓冲区中存在,则判定为重复发送,直接将其丢弃。确保应用程序read时只能读到一条数据。
且接受缓冲区除去重外,还可进行重新排队。保证发送的顺序和应用程序读取的顺序是一致的。
4.3.2.2 重传时间
发送方发送数据后会进行等待,如果在等待时间内收到对方的ACK,则视为数据成功送达;
如果超过了等待时间,还没有收到ACK,就会触发重传机制。
(1)超时时间的设定并不简单,如果超时时间设的太长,会影响整体的重传效率;如果超时时间设的太短,有可能会频繁发送重复的包;
(2)初始等待时间是可以进行配置的,不同系统上的等待时间也不一定相同,可以通过修改内核参数来进行修改;
(3)等待的时间也会动态变化,超时次数越多,下一次等待时间会变长。但也不是无限变长,重传若干次则视为不可达,触发TCP的重置连接,即放弃此条连接。
(4)在Linux和Windows中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。比如,重发一次后仍然没有得到应答,就会等待2*500ms后再进行重传。累积到一定次数后,TCP认为网络或对端主机出现异常,强制关闭连接。
4.3.3 连接管理机制
4.3.3.1 三次握手建立连接
1. A还要发送第三次确认的意义在于:
可以防止已失效的连接请求报文段突然又传送到了B,因而产生错误。
2. 三次握手的核心作用:
(1)确认当前网络是否通畅;
(2)发送方和接收方确认自己发送能力与接受能力正常;
(3)让通信双方在握手过程中针对一些重要参数进行协商,如通信数据的开始序号;
4.3.3.2 四次挥手释放连接
1. 四次挥手中间的两次交互不一定能合二为一,这与三次握手建立连接不同。
(1)对于三次握手:
ACK与第二个SYN都是内核触发的,同一个时机可以合并;
(2)对于四次挥手:
ACK与第二个FIN的触发时机是不同的:
ACK是内核触发的,B收到FIN就会立刻返回ACK;
第二个FIN是应用程序的代码触发,B方调用close方法才会触发FIN;
从服务器收到FIN并发送ACK后,再到执行close发起FIN中间要经历多少时间是不确定的。
4.3.3.3 建立连接与释放连接的总过程
注:TIME_WAIT状态需要等待2MSL(最长报文段寿命)存在的意义:
(1)假如没有设置TIME_WAIT状态而令A直接进入关闭状态,如果最后一个ACK丢失了,B会超时重传FIN,而A已经释放连接,则重传的FIN就无法被A收到。
而设置了TIME_WAIT状态后,如果对方重传了FIN,A就可以继续返回ACK了。
即:保证最后一个ACK能够到达对方。
(2)防止已失效的连接请求报文段出现在本连接中,A发送完最后一个ACK后再经过2MSL,就可以使本连接持续时间内所产生的所有报文段都从网络中消失。
4.4 TCP效率提高机制
4.4.1 滑动窗口协议
已知TCP的可靠传输会影响传输的效率,故而提出了滑动窗口协议用于尽可能降低可靠传输对性能的影响;
对于一发一收传输方式:
这种传输方式的等待时间是较长的;
批量传输方式:
4.4.1.1 数据传输示意图
无需等待ACK返回直接发送下一个数据。设置一个批量发送数据的最大限度,达到上限后再同意等待ACK。将这个数据量上限称为窗口大小。
4.4.1.2 滑动窗口
(1)当前A向B批量发送了4份数据,达到发送窗口大小,停止发送,等到B的ACK;
(2)B收到A的4份数据后,向A发送4个ACK;
(3)当A收到B对第一份数据的ACK后,就将窗口滑动,继续发送第五份数据;
4.4.1.3 超时重传机制
4.4.1.3.1 第一种情况:ACK丢失
此种情况无需重传。
确认序号表示当前序号之前的数据已经全部接收到了,下一个希望收到当前序号的数据包。
故而如果1001的ACK丢失了,而2001的ACK成功返回则表示2001号之前的数据都已经传输成功了,包括了1001ACK的含义。称为累积确认。
4.4.1.3.2 第二种情况:数据包丢失
(1)当主机B向A发送1001号ACK时,表示下一个希望收到1001及以后得数据包;
(2)主机A发送给主机B的1001~2000号数据包丢失,故而主机B未收到期待序号的数据包。主机B无论主机A继续发送什么序号的数据包都不予确认,而是连续发送三个重复确认ACK,向主机A索要1001号数据包;
(3)当主机A收到主机B发来的连续重复确认后,就开始重传1001~2000号数据包。
(4)当主机B成功接收1001~2000号数据包后,对这段时间主机A所发送的多条数据包进行累积确认,即发送7001号ACK;
注:
(1)上述重传操作中,并没有额外的冗余操作,哪个数据丢失就重传哪个,没有丢失的无需重传,整个过程比较迅速,称为快速重传,是滑动窗口协议下的对超时重传机制的变种。
(2)如果通信双方传输数据量较小,也不频繁,就仍然是普通的确认应答和普通的超时重传;
如果通信双方传输数据量较大,也较频繁,就会进入滑动窗口模式,进行快速重传的方式处理。
4.4.2 流量控制
流量控制即:根据接收方的接受速率,控制发送方的发送速率。
避免出现发送方发送太快导致接收方来不及接收的情况。
(1)主机A向主机B发送数据后,数据先到达B的系统内核中。TCP socket对象上带有接收缓冲区,A发送给B的数据就会先到达B的接收缓冲区;
(2)B的应用程序会调用read这样的方法,把数据从接收缓冲区中读出来进行进一步的处理。
一旦数据被read了,就可以从缓冲区删除了;
注:(1)对应TCP报文格式,其中的“16位窗口大小”用于表示接收缓冲区剩余空间大小。但此处空间最大值并非64K,在TCP报头中的选项部分有一项叫做“窗口扩展因子”,可以对窗口大小进行左移位,从而表示更大的范围;
(2)TCP规定,即使接收窗口为0,也必须接收窗口探测报文、确认报文段以及携带紧急数据的报文段;
4.4.3 拥塞控制
1. 拥塞控制即:防止过多的数据注入到网络中,使网络中的路由器或链路不至于过载,是一个全局性的问题;
而流量控制往往是指点对点通信量的控制,是个端到端的问题。
注:(1)在TCP建立连接和网络出现超时时,采用慢开始和拥塞避免算法,当发送方接收到冗余ACK时,采用快重传和快恢复算法。
(2)流量控制和拥塞控制都是在限制发送方窗口的大小,最终发送的窗口大小为流量控制与拥塞控制窗口中的较小值;
4.4.4 延时应答
1. 正常情况下,A把数据传输给B,B就会立即返回ACK给A;
但有的时候A传输给B,B等待一段时间后再向A返回ACK,此时就是延时应答;
2. 比如在流量控制中,可以使用延时返回ACK,以给接收方更多的时间来读取接收缓冲区的数据,缓冲区的空间会更大,使得可以返回的窗口大小也增大了。
4.4.5 捎带应答
由于TCP延时应答的存在,有可能存在以下情况:
(1)ACK是内核立即返回的,response是应用程序代码返回的,二者时机是不同的;
(2)TCP引入延时应答后,B对A的ACK可能不是立即返回,在其延时返回的过程中,可能B就计算好response,此时就可以将业务数据response捎带ACK进行返回:
(3)将两个数据包合二为一个数据包进行发送,可以实现更高效地传输;
4.5 粘包问题
4.5.1 粘包问题描述
由于TCP面向字节流的特性,在数据传输过程中,可能出现如上图所示,在接收缓冲区中,三个应用层数据包的数据以字节的形式紧紧挨在一起,B的应用程序就无法区分出缓冲区的完整数据包。
相比UDP这样面向数据报的通信方式,就不存在粘包问题:
4.5.2 粘包问题的解决方法
解决粘包问题的核心思路是:通过定义好应用层协议,明确应用层数据包之间的边界;
常用方法有:
1. 引入分隔符:
约定 \n 为分隔符后,实际上传输的数据形式为:
当接收方应用程序读取数据时,就可以已知持续读数据,直到读到\n为止;
回想之前的TCP实现网络通信的程序,使用scanner.next()读,使用println写,均以\n为分隔符;
TCP实现网络编程文章链接如下:
【JavaEE】_基于TCP实现网络通信-CSDN博客
2. 引入长度:
假定:现约定在每个完整的数据包业务数据前增加2字节的长度信息,实际传输方式为:
当接收方应用程序读取数据时,就可以先读2个字节得到数据包的长度,再根据该长度读取对应的字节数;
注:对于本文开头提及到的应用层协议格式(XML,JSON,ProtoBuf等),本身就是明确包的边界的。
4.6 异常情况的处理
4.6.1 进程崩溃
进程结束,异常也终止了,文件描述符表也释放了,相当于调用了socket.close();
此时就会触发FIN,进行4次挥手释放连接;
注:TCP的连接可以独立于进程存在,即进程结束但连接并不一定断开。
4.6.2 主机关机(正常流程)
进行关机时,先会触发强制终止进程操作,同1一样,触发FIN进行4次挥手释放连接;
注:但此时不仅是进程结束了,整个系统也关闭了。
如果在系统关闭前对端返回的ACK与FIN成功到达,则系统还可以返回ACK,进行正常的四次挥手;
如果在ACK与FIN返回前系统已经关闭了,则无法进行后续ACK的响应。
站在对端的角度,会误以为FIN丢包,会触发重传,待重传几次没有响应后自然就会放弃连接,把持有的对端信息删掉了。
4.6.3 主机掉电(非正常)
主机掉电是瞬间的,来不及结束进程,也来不及发送FIN,主机直接停机了。
如果对端在发送数据(接收方掉电),发送的数据就会一直等待ACK,触发超时重传,触发TCP连接重置功能,发起“复位报文段”(将TCP报文段首部的RST字段置1),如果复位报文段发送后也没有收到任何响应,就会释放连接了;
如果对端在接收数据(发送方掉电),对端未发送消息和没有收到消息是无法区分的。但接收方会周期性地给发送方发一个不携带业务数据的特殊数据包(心跳包),并期望对方返回一个应答。如果对方没有应答,并且重复多次后仍未应答,就视为对方无法正常通信,就可以单方面释放连接了。
4.6.4 网线断开
网线断开与主机掉电是非常相似的。
如果A正向B发送数据,一旦网线断开,则过程为:
B未应答—>触发A超时重传—>连接重置—>单方面释放连接;
B触发心跳包—>A未应答—>单方面释放连接。
注:类似于TCP,心跳机制在分布式系统重非常常见,但一般用到的心跳是在应用程序中自助实现的,TCP的心跳机制周期较长,往往会在应用程序中使用秒级或毫秒级的检测对端是否存活。
5. TCP与UDP的对比
有无连接,是否可靠,传输单位,传输方式的区别此处不再赘述;
TCP的主要优势在于可靠,故而TCP适用于绝大部分场景;
UDP的主要优势是效率高,故而UDP更适合对于可靠性不敏感而性能敏感的场景;
1. 如在一个局域网内部(如同一个机房)的主机之间的通信,由于网络结构简单,带宽充足,交换机/路由器网络设备负载程度也不是很高,出现丢包的概率也不大,使用UDP是完全可以的;
2. 如果要传输比较大的数据包,TCP更优先;(UDP有64KB的限制)
3. 如果要进行广播传输,使用UDP(TCP不支持广播),如在同一局域网下使用投屏功能,手机就会在局域网内发起广播数据包寻找投屏目标设备;
注:如何基于UDP实现可靠传输?
在应用程序中建立可靠传输机制,仿照TCP建立如确认应答,引入序号、确认序号,超时重传,滑动窗口等功能。