参考:Netty工作原理
Rpc通信,无非就是通过网络层的一些可靠协议进行数据传输的!这里我们的协议是tcp协议,要对tcp的握手挥手知识做一个了解,也要涉及一些多路复用、事件驱动内容!知其然,也要知其所以然。比如为什么选用这种技术就能保证我们的业务与效率呢!
Tcp/Ip参考模型:
模型看起来很抽象,换个方式看的话会更方便一些!首先还是要先了解一下tcp/ip参考模型:应用层、传输层、网络层、链路层。单纯看名称大概也能知道各层是干什么用的,但是如何用却显得比较鸡肋!
可以借助一些工具,分析一下tcp或者http传包过程中的数据,因为在各层用途,肯定有一些各层需要使用的数据!
随便抓了个http的二进制包,按照分层模型可以很明显看出都是用来做什么的。
Frame:物理层;
Ethernet II:数据链路层的头部结构,14字节。包括,源mac地址与目标mac
地址,各占6个字节,还有两个字节表示互联网协议,本次分析的是ipv4的协议;
Internet Protocol Version:网络协议,IP包头部结构,头部信息20字节!包括, 版本、差分区 域、总长度、标志、源ip、目标ip等;;
Transmission Control Protocol:传输控制层,20字节!包括了源端口、目标端口、 序号、确认序号、tcp 的6种报文段等;
Hypertext Transfer Protocol:超文本传输协议。
总之,从抓到的包中可以很轻松容易的看出,各个字段的意义,且很容易理解!
直接上图分析传输控制部分:
同时注意!虽然在工具上是按照顺序排列下来的,但在码流当中,并不一定是如此排列的,不过同一层的内容肯定是紧密排列的。
而主体数据,不管是公有协议http还是私有rpc都是一个header头与一个body体的结合。像私有栈的rpc属于自定义内容信息。
三次握手、四次挥手:
要了解tcp,必须要先了解tcp的报文格式!事实上根据以上的具体报文数据段,我们已经知道了这个报文格式!
本来想自己画一下,但是发现自己画的并不好看,而网上的报文格式似乎都是用的同一张图片,感觉不错,直接贴过来:
关于tcp连接,必须要把握好6个标记位所表示的内容!URG:紧急报文段;ACK:确认报文段;PSH:推数据报文段;RST:重置报文段;SYN:同步报文段;FIN:结束报文段。所谓三次握手,四次挥手也主要是针对这几个报文段标记位的建立连接、断开连接的操作!
假设握手、挥手过程中:任意一端主动方为client端!这里同时还会涉及到client与server的各种状态!
三次握手:
1)、执行触发Client端,随机设置序号seq为x,同时设置syn报文段 = 1,将此报文段发送给server端,此时client端处于SYN_SEND状态;
2)、server端接收到数据,根据client.syn知道要建立连接,ack报文段、syn报文段 = 1,同时ack seq = client.seq+1,同时产生一个seq序号 = y,将此数据发送给client,此时server为SYN_RECV状态;
3)、client接收到数据,验证server.ack = 1 && ack seq报文段 = y + 1,是,则置ack = 1, 发送给server端;server端检验ack = 1 && ack seq = y + 1,是,则成功建立。Client、server先后进入ESTABLISHED状态。
四次挥手:
1)、执行触发,client端将fin报文段 = 1、seq=x,发送给server端,此时client端处于FIN_WAIT_1的状态;
2)、server端收到数据,发现fin = 1,则将ack=1,ack seq = x + 1 = y发送给client,此时server进入CLOSE_WAIT阶段;
3)、server端再次发送一个fin报文段,关闭与client端的数据传输,server状态变为LAST_ACK;
4)、client端收到fin报文段之后,先进入TIME_WAIT阶段,再根据上述规律发送一个ack seq = y + 1 给server端,server端收到数据变为CLOSED状态。
具体可以通过该图,流的分析验证。如果要查看tcp的各个状态,使用netstat -apn查看:
可以看出此机器的运行正常!
至于为什么要进行四次挥手,主要是因为当server端接收到fin之后,client不发送了,但是server端还可以接收并发送,所以要处理掉server接收发送的情况。
假如是email时代,两个热恋的情侣:1、女方发消息 ‘end’;男方收到email后,2、首先一点需要停止 ‘发送消息’,3、也要告诉女方“了解,即将关闭”;女方确认之后,4告诉男方,‘好的’,这样才就结束了!不然的话,如果男方是一个话痨,女方发送就关闭消息了,那么男方可能不知道女方是不是接收到了最后的消息,也就不能及时断开!
那么如果真的出现了女方发送了消息就不再进行回复的情况呢?也就是比如被动关闭的情况,女方无法再发送ack确认报文了怎么办!?这个时候就有了2MSL(最大报文生存时间),这样的定义。也就是如果超过了2倍的发送、接收时间,这可以保证女方(client端)没有再发送过来数据,不会出现丢包的情况!
长短连接:
常见的http一般是采用短连接的,也可以通过keepAlive设置长连接,这里因为rpc模块是用在内网访问的,为了避免频繁创建关闭连接,可以创建一个tcp的长连接!
全连接半连接:
有一个参数allowHalfOpen!allowHalfOpen为false,socket 将发送一个 FIN 数据包,一旦写出,它的等待写入队列就销毁它的文件描述符。当然,如果allowHalfOpen 为 true,socket 就不会自动结束;它的写入端,用户可以写入任意数量的数据!
所谓半连接,也就是在第二次握手之后,出现的状态!Client发送了syn报文段给server端,client端为同步发送状态;server端收到信息,并发送了ack+syn报文段给client端,此时处于同步接收状态,此时server端即为半连接状态。为什么要有这么一个参数呢?那是因为,如果在同步接收状态,任意client端可以伪造ip地址请求,对server发送SYN报文段,由于ip不存在,tcp机制会不断重试重发,最终导致端口占用,网络阻塞,也就是syn攻击的一种。如果存在allowHalfOpen这个参数为false,就可以及时释放资源。
全双工:
通信方式不能是单方向的,所以必须是client、server都能进行发送与接收数据!
noDelay:
涉及到一种传输方式与nagle算法。Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段(摘自百度百科)。简单来说就是在mtu等造成的分片影响下,通过该算法,确保在网络中只有一个报文段,也就是当psh数据后,在未收到ack确认报文段之前不发送数据!这种算法毫无疑问会是效率低下,但保证信息可靠性提升了网络吞吐率。
backlog队列:
这是底层对接收发送数据处理的一个队列!事实上这也是对半连接的区分,如果看netty源码,也会发现会调用java底层对应这个参数的值!Linux2.X哪个忘了,版本之后,采用此方式!实际上是有两个队列,一个是syn backlog、另一个是listen backlog(accept队列),这其实也是nio处理的方式,异步的处理!如果accept队列满了后,会如何?这时候,底层不处理,也就是不返回ack,client端会认为是消息丢失,不断进行重试,直到处理完成!
拆包:
在提升tcp网络利用率的情况下,发送的数据与接收的数据的先后顺序不能保证、mtu造成的ip分片、最大报文长度切分等!所以就出现了粘包、拆包的概念!常见的拆包方式有:定义分隔符、定长、定义消息头消息体(比如http)。而我们是采用了定义分隔符的方式。再底层的数据合并已经由操作系统帮我们处理了!!!
事件驱动与libev:
其实就是利用了nio的多路复用,也跟操作系统相关!
网友评论