netty系列之:protobuf在UDP协议中的使用

netty系列之:protobuf在UDP协议中的使用,第1张

netty中提供的protobuf编码解码器可以让我们直接在netty中传递protobuf对象。同时netty也提供了支持UDP协议的channel叫做NioDatagramChannel。如果直接使用NioDatagramChannel,那么我们可以直接从channel中读写UDP对象:DatagramPacket。

但是DatagramPacket中封装的是ByteBuf对象,如果我们想要向UDP channel中写入对象,那么需要一个森空宴将对象转换成为ByteBuf的方法,很明显netty提供的protobuf编码解码器就是一个这样的方法。

那么可不可以将NioDatagramChannel和ProtobufDecoder,ProtobufEncoder相结合呢?

NioDatagramChannel中channel读写的对象都是DatagramPacket。而ProtobufDecoder与ProtobufEncoder是将protoBuf对象MessageLiteOrBuilder跟ByteBuf进行转换,所以两者是不能直接结合使用的。

怎么才能在UDP中使用protobuf呢?今天要向大家介绍netty专门为UDP创建的编码解码器DatagramPacketEncoder和DatagramPacketDecoder。

UDP的数据包在netty中是怎么表示呢?

netty提供了一个类DatagramPacket来表示UDP的数据包。此银netty中的UDP channel就是使用DatagramPacket来进行数据的传递。先看下DatagramPacket的定义:

DatagramPacket继承自DefaultAddressedEnvelope,并且实现了ByteBufHolder接口。

其中的ByteBuf是数据包中需要传输的数据,InetSocketAddress是数据包需要发送到的地址。

而这个DefaultAddressedEnvelope又是继承自AddressedEnvelope:

DefaultAddressedEnvelopee中有三个属性,分别是message,sender和recipient:

这三个属性分别代表了要发送的消息,发送方的地址和接收方的地址。

DatagramPacketEncoder是一个DatagramPacket的编码器,所以要编码的对象就是DatagramPacket。上一节我们也提到了DatagramPacket实际上继承自AddressedEnvelope。所有的DatagramPacket都是一个亏慎AddressedEnvelope对象,所以为了通用起见,DatagramPacketEncoder接受的要编码的对象是AddressedEnvelope。

我们先来看下DatagramPacketEncoder的定义:

DatagramPacketEncoder是一个MessageToMessageEncoder,它接受一个AddressedEnvelope的泛型,也就是我们要encoder的对象类型。

那么DatagramPacketEncoder会将AddressedEnvelope编码成什么呢?

DatagramPacketEncoder中定义了一个encoder,这个encoder可以在DatagramPacketEncoder初始化的时候传入:

实际上DatagramPacketEncoder中实现的encode方法,底层就是调用encoder的encode方法,我们来看下他的实现:

上面的逻辑就是从AddressedEnvelope中调用 msg.content() 方法拿到AddressedEnvelope中的内容,然后调用encoder的encode方法将其编码并写入到out中。

最后调用out的get方法拿出编码之后的内容,再封装到DatagramPacket中去。

所以不管encoder最后返回的是什么对象,最后都会被封装到DatagramPacket中,并返回。

总结一下,DatagramPacketEncoder传入一个AddressedEnvelope对象,调用encoder将AddressedEnvelope的内容进行编码,最后封装成为一个DatagramPacket并返回。

鉴于protoBuf的优异对象序列化能力,我们可以将ProtobufEncoder传入到DatagramPacketEncoder中,做为真实的encoder:

这样就把ProtobufEncoder和DatagramPacketEncoder结合起来了。

DatagramPacketDecoder是和DatagramPacketEncoder相反的 *** 作,它是将接受到的DatagramPacket对象进行解码,至于解码成为什么对象,也是由传入其中的decoder属性来决定的:

DatagramPacketDecoder要解码的对象是DatagramPacket,而传入的decoder要解码的对象是ByteBuf。

所以我们需要一个能够解码ByteBuf的decoder实现,而和protoBuf对应的就是ProtobufDecoder。

先来看下DatagramPacketDecoder的decoder方法是怎么实现的:

可以看到DatagramPacketDecoder的decoder方法很简单,就是从DatagramPacket中拿到content内容,然后交由decoder去decode。

如果使用ProtobufDecoder作为内置的decoder,则可以将ByteBuf对象decode成为ProtoBuf对象,刚好和之前讲过的encode相呼应。

将ProtobufDecoder传入DatagramPacketDecoder也非常简单,我们可以这样做:

这样一个DatagramPacketDecoder就完成了。

可以看到,如果直接使用DatagramPacketEncoder和DatagramPacketDecoder加上ProtoBufEncoder和ProtoBufDecoder,那么实现的是DatagramPacket和ByteBuf直接的互相转换。

当然这里的ProtoBufEncoder和ProtoBufDecoder可以按照用户的需要被替换成为不同的编码解码器。

可以自由组合编码解码方式,就是netty编码器的最大魅力。

public void run(int port)throws Exception{

2 EventLoopGroup group = new NioEventLoopGroup()

3 try {

4 Bootstrap b = new Bootstrap()

5 b.group(group).channel(NioDatagramChannel.class)

6 .option(ChannelOption.SO_BROADCAST,true)

7 .handler(new UdpServerHandler())

8

9 b.bind(port).sync().channel().closeFuture().await()

10 }

11 finally {

12 group.shutdownGracefully()

13 }

14 }

1 @Override

2 public void messageReceived(ChannelHandlerContext channelHandlerContext,

3DatagramPacket datagramPacket) throws Exception {

4 // 因为明没运Netty对UDP进行了封装,所以接收到的是察山DatagramPacket对象。

5 String req = datagramPacket.content().toString(CharsetUtil.UTF_8)

6 System.out.println(req)

7

8 if("啪啪啪来拉!!!激梁".equals(req)){

9 channelHandlerContext.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(

10 "结果:",CharsetUtil.UTF_8),datagramPacket.sender()))

11 }

12 }

从数据结构上可以看出来,TCP比UDP要复杂的多。

我们上面说tcp是面向连接的,这是啥意思呢,简单的说,tcp要发送数据,首先得先建立连接,而udp不需要,直接发送数据就行了。

TCP是全双工的,即客户端在给服务器端发送信息的同时,服务器端也可以给客户端发送信息。

而半双工的意思是A可以给B发,B也可以给A发,但是A在给B发的时候,B不能给A发,即不同时,为半双工。

为什么采用三次握手?而不是二次?

是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误;比如有以下场景,当客户端发出第一个

连接请求1并没有丢失,而是在某些网络节点长时间滞留了,这时客户端会认为超时,会再次发送连接请求2,服务端在接收到连接请求2以后建立了正常的连接,这时候失效请求1又到达了服务端,服务端会误以为是客户端又发出的一个连接请求,于是会再次建立连接,假定不采用三次握手,那服务端发出确认,新的连接就建立了。但是由于客户端并没有发出新的建立连接的请求,因此客户端不会再新的连接上发送数据,而服务端却以为新的连接已经建立了,在一直等待客户端的数据,由此会导致服务端许多资源的浪费。采用了三次握手后,可以防止这个现象发生,在客户端收到服务端对于自己的无效连接的应答后,并不会向服务端发出确认,服务端由于收不到确认,就可以认为此次连接是无效的。

第二次袭搭挥手完成后,客户端到服务端的连接已经释放,B不会再接收数据,A也不会再发送数据。这个时候只是客户端不再发送数据,但是 B 可能还有未发送完的数据,所以需要等待服务端也主动关闭。

为什么是四次?

关闭连接时,当Server收到FIN报文时,很可能并不会立即关闭socket,因为Server端可能还有消息未发出,所有其只能先回复一个ACK报文,告诉Client端,你的关闭请求我收到了;当Server把锋唤所有的报文都发送完以后,Server才能给Client端发送FIN报文关闭连接,Client收到后应答ASK。所以需要四次握手

在四次握手后,Server端先进入TIME_WAIT状态,然后过2MS(最大报文生存时间)才能进入CLOSE状态。为啥?

因为网络是不可靠的,客户端在第四次握手的ACK可能会丢失,所以TIME_WAIT状态就是用来重发可能丢失的ACK报文

TCP是顺序性,是通过协议中的序号来保证,每个包都有一个序号ID,

在建立连接的时候会商定起始 序号ID 是什么,然后按照 序号ID 一个个发送。

tcp是通过应答/确认/ACK 以及 重传机制来保证消息可靠传输的。

即在消息包发送后,要进行确认,当然,这个确认不是一个一个来的,而是会确认某个之前的 ID,表示都收到了,这种模式成为累计应答或累计确认。

确认是通过报文头里面的确认序号来保证的。

为了记录所有发送的包和接收的包,TCP 需要在发送端和接收端分别来缓存这些记录,发送端的缓存里是按照包的 ID 一个个排列,根据处理的情况分成四个部分

流量控制指的是发送端不能无限的往接收端发送数据(UDP就可以),为啥呢?

因为在 TCP 里,接收端在发送 ACK 的时候会带上接收端缓冲区的窗口大小,叫 Advertised window,超过这个窗口,银禅凯接收端就接收不过来了,发送端就不能发送数据了。这个窗口大概等于上面的第二部分加上第三部分,即 发送未确认 + 未发送可发送。

流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。

tcp接收端缓冲区的大小是可以调试的,见 Netty高级功能(五):IoT百万长连接性能调优

TCP通过一个定时器(timer)采样了RTT并计算RTO,但是,**如果网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,然而重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这就导致了恶性循环,最终形成“网络风暴” —— TCP的拥塞控制机制就是用于应对这种情况。 **

拥塞控制的问题,也是通过窗口的大小来控制的,即为了在发送端调节所要发送的数据量,定义了一个“拥塞窗口”(Congestion Window),在发送数据时,将拥塞窗口的大小与接收端ack的窗口大小做比较,取较小者作为发送数据量的上限。

所以拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。

TCP 拥塞控制主要来避免两种现象,包丢失和超时重传,一旦出现了这些现象说明发送的太快了,要慢一点。

具体的方法就是发送端慢启动,比如倒水,刚开始倒的很慢,渐渐变快。然后设置一个阈值,当超过这个值的时候就要慢下来

通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。

第一层:物理层

第二层:数据链路层 802.2、802.3ATM、HDLC、FRAME RELAY

第三层:网络层 IP 、IPX、ARP、APPLETALK、ICMP

第四层:传输层 TCP、UDP 、SPX

第五层:会话层 RPC、SQL 、NFS 、X WINDOWS、ASP

第六层:表示层 ASCLL、PICT、TIFF、JPEG、 MIDI、MPEG

第七层:应用层 HTTP,FTP ,SNMP等


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12302348.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存