作者
:学Java的冬瓜
博客主页
:☀冬瓜的主页
专栏
:【JavaEE】
主要内容
:应用层HTTP协议、DNS域名解析系统、传输层UDP协议,TCP协议。TCP协议的工作机制:确认应答、超时重传、连接管理、滑动窗口、流量控制、拥塞控制、延时应答、捎带应答、面向字节流地粘包问题、客户端服务器异常断电等的理解。
自定义协议要做的事情就是两件事:
1.明确协议数据要传输哪些数据
。(根据需求分析获得)
2.明确数据组织格式
。比如可以使用纯文本,或者xml标签形式,json方式,或者protobuffer(二进制)
HTTP协议可以传输多种类型的文件,包括HTML页面、图像、音频、视频以及其他文本文件。
HTTP协议是浏览器和服务器之间进行数据传输的协议。客户端通过浏览器发送HTTP请求,服务器通过HTTP响应返回数据给浏览器。
HTTP是一种无状态的协议,每个请求和响应之间相互独立,不会记录之前的请求和响应。
DNS域名解析系统:
DNS全称域名解析系统,就是我们使用的地址栏上的地址。对服务器来说,要访问的其实是服务器的IP地址,但是IP地址记起来很麻烦,于是就用一些简单的单词构成字符串表示这个IP地址。每个域名都对应了一个/N个IP地址。
那么怎么将IP和域名对应呢?
最开始使用的是一个hosts文件,像哈希表一样,建立了IP和域名之间的映射关系。
后来网站越来越多,仅靠hosts文件难以维护,所以有了DNS服务器。当你访问某个域名时,就会自动请求DNS服务器,DNS服务器就去查表,然后返回给你要访问的具体的IP地址,你就可以根据IP地址访问你要访问的服务器。
域名不能重复,如何保证?
使用一级域名、二级域名、三级域名…
一级域名比如:.com
、.org
、.cn
…
二级域名比如:baidu
因为域名分级了,所以DNS服务器也分级了。
且IPv4对应一套域名解析系统,而IPv6又对应另一套域名解析系统。IPv4的DNS服务器大部分在美国,所以我们才大力发展IPv6对应的DNS服务器。
传输层是操作系统内核实现好了的,但是程序员可以通过系统提供的 Scket API完成网络编程。 传输层最著名的协议就是UDP和TCP。
端口:
端口范围范围是0~65535(2^16-1),0 ~ 1023
叫做知名端口,分配个给广泛使用的应用程序。
程序员需要记住的数字:
1个字节:8个bit位,0~255
(0 ~ 2^8 - 1),或者-128 ~ +127
( -2^7 ~ 2^7-1)
2个字节:16个bit位,0~65535
(0 ~ 2^16 - 1),或者-32768 ~ +32767
( -2^15 ~ 2^15-1)
4个字节:32个bit位,0~ 42九千多万
(0 ~ 2^32 - 1),或者-21亿~ +21亿
( -2^31 ~ 2^31-1)
在网络传输数据中,生成校验和的知名算法有:
CRC
: 将设定的初始值和数据进行合并,然后做除法运算,得到新的值,重复操作,最后得到结果。
MD5
: MD5算法很强大,应用广泛,1>生成校验和;2>作为计算hash值的方式;3>加密。这源于MD5的特性:1>定长;2>冲突概率小;3>不可逆
SHA1
: 它也有定长特性,可以用于hash值的计算。
4位首部长度:
一个TCP报头,长度是可变的。不像UDP报头 固定8个字节。首部长度就描述了TCP报头的长度,不包含数据部分。4位首部长度,即4个bit位,可以表示0~15的数。
这个首部长度的值的单位是4字节(比如值为0001就代表4个字节),我们看TCP报头可以发现,选项以上的内容为20字节,如果选项的部分为0,那么首部长度就应该是5(5x4=20)。
40字节选项:
选项部分用于TCP功能或数据的扩展,提高性能。
如果首部长度是15,那么选项=首部长度x4-20。所以,选项此时就是40字节。
6位保留位:
保留位置,便于以后方便扩展。
6位标志位:
URG:紧急指针是否有效
ACK
:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN
:请求建立连接;我们把携带SYN标识的称为同步报文段
FIN
:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含TCP首部,也包含TCP数据部分。和UDP的校验和一样。
16位紧急指针:标识哪部分数据是紧急数据。
TCP对数据提供的管控机制,主要体现在两个方面:
安全和效率
接下来我们来理解TCP内部的工作机制,这其中就有ACK,SYN,FIN,窗口,序号,确认序号等信息,帮助我们全方面的理解。
确认应答:TCP传输数据时,主机A作为发送方给主机B发送数据,B收到数据后,给A返回一个 ACK,确认B已经收到A发送的数据。
在网络传输中,如果不做特殊处理,可能会存在数据后发先至的问题
比如A给B发送数据本来的情况是:A发B:今天周六,你写代码没?B回A:有。A发B:你找到女朋友没?B回A:没有。
引入编号后,即使出现后发先至的情况(程序上,用户看到的界面则不会发生), 也不会有上述信息歧义的问题。如下图的情况:
在TCP协议中,因为是面向字节流,所以序号并不是按照一条消息,两条消息来编号,而是按照字节的方式来编号的,如下图:
假设第一条数据是1000字节,那么数据第一个字节序号就是1,第二个是2…由于1-1000的数据都属于同一个TCP数据报,所以此时报头中序号是1。接下来假设第二个数据报也是1000字节,第二份数据的报头中序号就为1001,然后…。TCP知道TCP数据报第一个节点的序号,再根据TCP数据报的长度,就可以很容易得到了TCP数据报中的字节序号。
注意:
1>这个字节序号不一定从0开始,是在客户端和服务器建立连接关系时协商确定。
2>确认序号的取值是收到的最后一个数据的序号加1(表示这个序号之前的数据都已经收到,接下来就索要以这个序号开头的数据报)。
在上面确认应答机制中,我们讨论了通过编号解决后发先至,通过确认应答保证TCP传输的可靠性,都是数据可以正常发送接收的情况,但是如果丢包了,那就又会出现问题!
在TCP中,是这样解决丢包的:规定一个超时时间,如果对方还没响应,就重新发一次。
如果是丢失数据,那重传一次完全没问题,但是如果是丢失ACK,那么接收方就受到了两份一样的数据,这是否产生问题?
在TCP中,进一步解决丢失ACK,重传后,接收方收到两份数据的问题:TCP传输,在接收方存在去重操作。
具体情况接着往下看,在说之前我们先来搞清楚什么是缓冲区:在TCP里的每个 Socket对象,都有一个接收缓冲区(也有一个发送缓冲区),主机B接收到主机A发送的数据时,其实是B的网卡读到数据了,然后把这个数据放在对应的Socket缓冲区中,然后后续应用层通过inputStream流,进一步通过read读取缓冲区的数据。缓冲区可以看成是一个阻塞队列(有序的),在阻塞队列中的数据等待应用层的读取。
解决:
如果A给B发数据,B回ACK时ACK丢失,那么A给B重新发送这份数据,这时候,这两份相同的数据都会在 B的接收缓冲区中,B通过TCP的传输时的字节序号对比就可以很容易发现这两份数据重复,从而将后一份数据之间丢弃。因此就保证了应用程序调用read读到的数据,一定是不重复的数据。
同时,TCP使用这个接收缓冲区,对收到的数据进行重新排序,从而让应用程序 read的数据是保证有序的!即在客户这里显示的信息就是有序的!
注意:由于由于去重和重新排序机制的存在,发送方只要发现丢包(包括丢数据或丢ACK),就会重新发送数据,即使数据重复,数据顺序乱了,在缓冲区中都可以解决问题!
解决了丢包一次的问题,那我们想象丢包多次的问题:在TCP中,如果丢包多次,那么就会重传多次,达到一定次数时,会尝试重置连接,如果重置连接还是失败,就彻底断开连接了。通过这样的方式达到"止损"的目的。
而且,重传时间会随着重传次数的增加 超时时间也会延长,从而降低重传频率,节省资源。
注意:TCP可靠性是通过 确认应答+超时重传 的机制实现的。
其中:确认应答描述的是数据顺利传输的情况,超时重传描述的是数据传输失败的处理,二者相互配合,共同支撑TCP的可靠性
客户端和服务器通过三次握手建立连接,通过四次挥手断开连接。
三次握手的作用:
1> 保存通信双方之间的信息,彼此之间相互认同
2> 验证双方的发送能力和接收能力是否正常
3> 在握手的过程中,通信双方来协商一些重要参数(比如传输的TCP数据的序号是否从1开始)
接下来我们来理解理解三次握手作用1和作用2:
作用1:建立彼此之间相互认同。这个建立连接的过程,就像两个人恋爱建立关系的场景一样,如下图:
“三次握手” 本质上是 “四次交互” 。通信双方,各自给对方发送"建立连接"
的请求,同时,再各自返还给对方一个ack,这里是有四次信息交互,但是中间两次交互合并变成一次交互,所以变成三次握手。
作用2:验证双方的发送能力和接收能力 还是上述例子:再加上男女双方都是程序员这个条件。
当女方收到男方发来的表白时(第一次握手):女方是知道男方的数据发送功能完好,并且知道自己接收数据的功能完好;男方什么都未知。
当男方收到女方发来的表白时(第二次握手):男方知道自己的消息已经被女方收到,且自己够接收到女方发来的数据,所以男方的发送和接收功能完好,女方的接收和发送功能也完好。所以此时男方知道了双方的发送河接收信息的功能都完好;但此时女方还不知道自己发送功能是否完好,男方接收功能是否完好。
女生收到男生发的好啊(第三次握手):女生也可以确认双方通信完好了。
为什么不使用2次握手建立连接?或者4次握手?
如果使用2次握手,那上述例子的最终结果是:男方知道双方 发送和接收信息能力完好,但是女方不知道自己发送功能是否完好,男方接收功能是否完好。
如果使用四次握手,那中间的两次发送是两次封装分用,比一次封装分用成本更高!
三次握手图:ACK=1是标志位,Ack=123457是确认应答数据。
可以简化为下图:
断开TCP的连接采用四次挥手的方式。
四次挥手的场景和情侣分手的场景很像,如下图:
为什么中间两步不能合并成一步操作呢?四次挥手断开连接的图可以简化为下图:
在建立连接时,使用的是三次握手,将中间两步和并成一步解决(为减少成本),但是断开连接这里却不能将中间两步合并,因为中间两步中ACK和FIN的时机是不一样的!!!
LISTEN
服务器的状态,表示服务器已经就绪,客户端可以随时来连接ESTABLISHED
表示自己已经和对方建立连接,客户端服务器都有,就表示建立连接完成!CLOSE_WAIT
出现在被动发起断开连接的一方,如果客户端主动发起,CLOSE_WAIT出现在服务器,如果服务器主动发起,CLOSE_WAIT出现在客户端。 等待关闭,应用程序中调用Socket的close,销毁Socket。TIME_WAIT
出现在主动发起断开连接的一方。假设是客户端发起的断开连接,如下图:在TIME_WAIT前,已经完成了第四次挥手(客户端给服务器发送ACK),但是ACK还没有到服务器,所以客户端等待一会。如果客户端在等待的过程中,其实这个ACK已经丢包,无法到达服务器了,服务器根据超时重传的机制,会重新发送FIN 给客户端,因为客户端处于TIME_WAIT状态,此时客户端就可以再次发送ACK给服务器。注意:确认应答、超时重传、连接管理为TCP传输的可靠性提供了支持
TCP协议相比于UDP引入了可靠性,但是也因此降低了效率,而滑动窗口就是为了相对地提高效率,为可靠性造成的效率低做的补救措施。
滑动窗口本质上是降低了确认应答,等待ACK消耗的时间。
(进行IO操作的时候有两种情况:1>等待 2>传输数据)
滑动窗口使用批量发送一组数据的方式,用等待一份ACK等待的时间等待多份ACK,这种是数据传输正常的情况,如果出现丢包,那怎么办?
1> 数据丢了 :发送发收到连续3次相同的ACK,就重发这份ACK对应的数据。在下图中1000-2000的数据丢了,接下来2001-3000到达主机B后,B给A返回的确认序号仍为1001(即向A索要1001-2000的数据),A收到B返回的3次1001ACK后,A就知道B未收到1001-2000的数据,就将这一份数据重发给B了。此时B已经收到1000-7000的数据,所下一个ACK就B向A从7001开始索要数据了。
2> ACK丢了:不需要做任何处理,因为一组中包含多个ACK,即使丢了一两个,只要不是全丢,由于滑动窗口的滑动和 ACK确认序号的含义(这个ACK序号之前的数据已经到达),仍可以继续往后执行。
比如A给B发送数据1-5000,窗口大小是6000,那么如果是B返回给A的1001这个ACK丢了,但是只要2001(或3001…6001)ACK返回给A,那么就代表2001之前的数据B已经收到,所以A不需要超时重传。
- 快速重传:发送数据是丢失数据,如果此时传输数据比较密集,是按照滑动窗口的方式传输,此时按照 “快速重传” 来处理数据丢了。
超时重传:发送数据时丢包(包括数据丢和ack丢),如果此时传输数据稀疏,不再按照滑动窗口的方式传输了,那就按照超时重传的方式解决丢包问题!
在滑动窗口的基础上,做的操作。
滑动窗口,窗口大小越大,传输效率越高(一份时间,等待的ACK越多),但是窗口效率还得看接收方的最大处理能力来控制,发送方的发送速度,不能超过接收方的处理能力。
流量控制:根据接收方的处理能力,来协调发送方的发送速率
接收方的处理能力:用接收缓冲区剩余的空间来表示接收方的处理能力。
当接收方窗口大小较大时,发送方会增大发送速率;接收方窗口大小较小时,发送发会减慢发送速率。
如果接收方缓冲区无剩余空间了,窗口大小就为0,那么发送方暂时不再发数据,而是定期给接收方发送窗口探测,等接收方把缓冲区的数据消耗了,空出位置后,发送方发送窗口探测后,接收方返回一个ACK,发送方就再根据ACK设定窗口大小。
在滑动窗口的基础上做的操作。
流量控制和拥塞控制共同决定发送发的窗口大小,取二者中较小的。
流量控制描述的是接收方的处理能力对发送发窗口大小的控制;
拥塞控制则是网络中中间节点的处理能力(类比木桶原理)
拥塞控制解析:拥塞窗口(尝试以多大的窗口进行发送),随着传输轮次的改变,窗口大小在不断改变。不丢包时,窗口大小在增长,当丢包时,窗口大小瞬间变小,然后再试探,最后达到动态平衡的情况。
在滑动窗口的基础上做的操作。
在滑动窗口的基础上,接收方收到数据时,延时返回ACK,在延时过程中,接收方把接收缓冲区的数据先消耗一些,这样接收方的剩余空间就大,返回给发送方的ACK中的信息中窗口大小信息就大,发送发接收到ACK后,指定的窗口大小就大,那么发送方的发送速率就快。
在延时应答的基础之上做的操作。
三次握手是一定会合并的,这里的延时应答和四次挥手(中间两步合并的)的情况相似,在四次挥手中,如果应用程序执行到close和B返回给A一个ACK的时间间隔很小很小,也是有可能合并ACK+FIN的。而这里是因为B延时应答,所以为中间两步合并提高了更大的概率。
捎带应答的原则是能合并则合并。
面向字节流存在"粘包问题":由于TCP是面向字节流的,所以读数据时的个数是随机的,这样就可能导致发送的两份数据连在一起,导致一次读到的可能是半份应用层数据报,或者各半份应用层数据报。
解决办法:
1> 在每份应用层数据报后加\n作为标识符(标识符得是数据中不存在的);
2> 在应用层结构中约定好每个包的长度
传输过程中因不可抗的因素,出现了异常。
1.进程崩溃
2.主机关机(正常关机)
3.主机断电
4.网线断开
注意点1:1出现时,进程对应的PCB没了,PCB中的文件描述符表就没了,相当于Socket调用close关闭了,因此会正常执行四次挥手,只是此时接收方不是返回FIN给发送方,而是返回RST给发送方,从而发送方不需要进入TIME_WAIT状态,而是收到RST立即释放资源并尝试重新建立连接。2出现时,关机前先关进程,也是会四次挥手。
注意点2:3和4出现时,就无法完4次挥手。
如果接收方断电了,发送方在发送数据,发送发无法收到接收方发来的ACK,然后超时重传,仍然无法收到ACK,重传几次尝试重置TCP连接(复位报文段RST),连接不上A就彻底放弃和B连接了。
如果发送方断电了,接收方会隔一段时间给发送发发送一个消息"心跳包"
,来确定发送方是否还在工作。如果发送方无反应了,接收方就会断开连接。
TCP:保证数据可靠传输
UDP:性能高
UDP应用场景:
1>同一个机房内部的服务器之间通信(丢包概率小,且即使丢了也没啥事)
2>UDP天然支持广播。
IP地址中有一种特殊的地址叫做"广播IP",通过UDP往
广播IP上发送数据报,此时这个IP的局域网内的所有设备都能收到广播!
游戏需要的传输层协议:在游戏上既需要可靠,有需要效率,但是UDP牺牲了可靠换来了效率,TCP牺牲了效率换来了可靠,因此都不符合游戏的应用场景,在游戏中,传输层协议需要一个效率和可靠性都兼备居中情况。
网络层主要做两件事:
1>地址管理 2>路由选择
网络层最知名的协议就是IP协议。
4位版本号:只有两个取值4和6,代表IPv4和IPv6。
4位首部长度:和TCP的4位首部长度一样,描述了IP报头长度,是可变的,且用法相同。
8位服务类型(TOS):说是8位,只有4位有效,这四位中只有一位可以是1,其它位是0。4位就表示IP协议中的四种工作模式。四种工作模式分别为:最大延时、最大吞吐量、最高可靠性、最小:成本,只能选四者中的一个。
16位总长度(字节数):描述了一个IP数据报的长度,报头+载荷。可以知道因为16位的限制,IP数据报最长是64kb。那如果超过64kb,如何处理? IP协议支持对数据报的自主 拆分和组装。 传输层协议UDP则需要程序员手动在代码中完成拆分和组装。
实际的IP数据报的长度并不一定达到64kb,往往会更小,这取决于数据链路层。
16位标识,3位标志,13位片偏移:这几个字段都是辅助拆包,组装提供的信息。同一个数据拆成的多个包的16位标识符是一样的,3位标志位描述了结束标志(就是在最后一个包中加上结束标志),13位片偏移标识了同一个数据拆成的多个包的先后顺序。
8位生存时间(TTL):一个数据报在网络上能够传输的最大时间(这个时间指的不是"秒"而是"次数"),这个数据报构造出来就有一个初始值,在传输中每经过一个路由器转发,TTL-1,如果TTL=0了数据还没到达,这个数据就丢包了,且这个包不可能到达对面。
8位协议:描述了载荷部分内容是属于哪一个传输层协议的,如UDP,TCP…
16位头部校验:这里只对IP报头首部校验,载荷已经在之前由传输层校验了。
32位源IP,32位目的IP
:根据源IP和目的IP规划路径。
接下来我们进一步思考和理解:
IPv4的IP只有32位,表示的数只有0-42亿。为了解决IP不够用的场景就有了一些列的方法:
1> 动态分配地址。
2> NAT网络地址转换。
3> 使用IPv6从根源上解决问题。
使用NAT后,IP可以不用指一个设备,而是代指一批设备。比如你的校友一同使用你的学校这个IP,而每个同学之间使用端口号区分开。
在NAT的背景下,把IP分成了两大类:
内网IP(私网IP):(10.* ) (172.16.* -172.31*)(192.168.*)
外网IP(公网IP):剩下的为公网IP
NAT要求,公网IP是唯一的,私网IP在不同的局域网中可以重复出现。
如果私网设备想要访问公网设备,就需要对应的NAT设备(路由器),把IP地址进行映射(用路由器的IP替换要访问公网的私网IP),从而完成网络访问。(见下图),同时,此时服务器这边的响应数据的目的ip就变成运营商路由器了,运营商路由器收到响应后,再根据私网ip对应的端口转发回响应。
具体操作是:路由器会维护一张NAT表,记录私网IP和端口对应的公网IP和端口,以便于在路由器收到响应后,能正确响应给相应的私网IP和端口。
反之公网的IP不能访问私网IP,私网IP之间也不能相互访问。
IPv4使用4个字节(32位)表示IP地址,表示范围是0~42亿9千万。
IPv6则使用16个字节(128位)表示IP地址,表示范围并不是IPv4的4倍,而是42亿 * 42亿 * 42亿 * 42亿。
注意:IPv6和IPv4不兼容
网络号
:标识网段(局域网),保证相互连接的两个局域网具有不同的标识(要保证相邻的两个局域网的网络号不同)。
主机号
:标识主机,同一个局域网内,主机之间具有相同的网络号,但是必须有不同的主机号。
注意:划分出网络号和主机号的目的就是为了组网。
子网掩码:一个IP地址,怎么区分哪些是网络号,哪些是IP号? 通过子网掩码
这个图中前三个字节的每一个比特位(24个比特位)都是1,代表前面的这三个字节表示网络号,后一个字节的8位比特位为0,表示后一个字节表示主机号。
但注意:网络号的表示并不一定就是前3个字节!具体要看子网掩码!
路由选择就是规划路径。
路由选择,核心思路就是问路!
每一个路由器都会保存周围一定设备的信息(发送方的IP和端口,以及接收方的IP和端口,使用哈希表建立关系)
每次有一个IP 数据包经过路由器,它就会查找哈希表以确定下一步数据包发向哪里,如果哈希表中没有目标IP地址的匹配项,路由器将使用默认路由将数据包转发到预定的下一个路由器。即去一个地方时,如果找不到具体的路,就根据大体方向走过去再问路。
这个数据包每经过一个路由器,TTL(8位表示,最高2^8-1)TTL-1,如果数据包的TTL-0了,那就永远也到不了接收方那里!就被丢弃了。
考虑两个相邻节点数据的传输。(通过网线/光纤/无线直接连接的两个设备。 数据链路层最知名的协议就是 “以太网”。
以太网数据帧=帧头+载荷+帧尾。(0800类型载荷为完整的IP数据报)
注意:此处目的地址和源地址并非IP地址而是一个mac地址,mac地址和IP地址完全独立,是另一套地址体系。
mac地址使用6字节表示,数量比IPv4大很多(有两百八十亿万多),因此每个设备都能分配到一个mac地址,这个地址不是动态分配的,是网卡 出场时就被设置好的。
IP地址和mac地址是相互配合,共同完成数据传输的。
IP地址是网络层协议,关注的是源发送方的IP和目的接收方的IP,从而规划路径;
而mac地址是数据链路层以太网协议的地址,从而考虑相邻节点数据的传输,是当前转发设备的mac和传输到下一个节点的 mac地址。
数据链路层中的另外的协议:ARP协议,通过这个协议,可以在路由器/交换机 里创建一个关于IP和mac的哈希表,从而在当前设备根据IP地址找到mac地址。
下图为以太网数据帧,载荷的长度即为MTU,载荷具体多长,和物理介质有很大关系,也和数据链路层使用的协议有很大的关系。
IP的分包组包通常不是使用IP的最大报文长度64kb来分开的,而是通过数据链路层的MTU。因为MTU一般都比64kb小。