目录
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实现网络编程文章链接如下:
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建立如确认应答,引入序号、确认序号,超时重传,滑动窗口等功能。