Netty多语言(Java、Android 、C#、WebSocket)通信实例Demo (二)Java端简介【附源码】
转载请注明出处,原文地址:https://blog.csdn.net/lucherr/article/details/88378235
Netty多语言(Java、Android 、C#、WebSocket)通信实例Demo (一)概述 【附源码】
Netty多语言(Java、Android 、C#、WebSocket)通信实例Demo (二)Java端简介【附源码】
Netty多语言(Java、Android 、C#、WebSocket)通信实例Demo (三)Android端简介【附源码】
Netty多语言(Java、Android 、C#、WebSocket)通信实例Demo (四)C#端简介【附源码】
该项目源码中,包含了Java服务端、Java客户端、WebSocket示例源码,对应的源码目录为NettyServer:
源码结构如下图:
服务端:
Netty的强大之处在于他的API使用非常简单、功能强大、扩展性很强,对于简单的一个Demo来说,几句代码就可以搞定,在本Demo中,由于同时支持了TCP和WebSocket协议,而且使用了自定义的编解码器,TCP使用MessagePack,WebSocket使用了Json,传递的事件对象也是加入了类的继承关系以及枚举,所以增加了Demo的复杂度,所以这个Demo并不是单纯的想要展示这几个端是怎么通信的,而是更加符合实际开发而加入了一些相对复杂的设定。
使用Netty开发我们主要关心的就是编解码类、消息处理类的实现,对于编解码Netty提供了一些简单的编解码器,也可以很方便的定制自己的编解码器,对于TCP粘包和拆包问题解决也是So easy,简单的说明一下我对编解码的理解,首先说说概念:编码可以理解为序列化:是指将对象转换为字节数组的过程。
解码可以理解为反序列化:是指将字节数组还原成原始对象的过程。
为什么需要编码?需要将变量或对象从内存中取出来进行存储或传输。
我觉得可以将Netty中的编解码用大家都熟悉的快递业务来对比说明,这样比较好理解,因为本例中使用了2种协议,所以举例的时候会将协议一起说明。
收发快递例子说明Netty编解码:
Netty就像是快递柜,你可以通过它收发快递,它支持N家快递公司,它的服务很专业
协议就像是这里的快递公司,用不同的快递公司将会用不同的包装将你的包裹包装起来
编码就像是要邮寄物品的时候,把物品打包成包裹的过程
解码就像是领取包裹后,拆开包裹的过程
来一个案例:现在你是一个小服装厂的老板(刚开始干),有个客户在网上购买了一件衣服需要试穿,你高兴的不得了,现在你要把衣服邮寄给她,你家楼下有个快递柜(也就是Netty,特别省事特别方便),客户那边说了只方便收顺丰的快递(也就是指定了协议,ps:顺丰没有给我广告费),于是你在邮寄的时候选择了顺丰,当然衣服你不能直接就塞快递柜里了,你还得给它细心的包装起来,你可以随便找个袋或者纸壳箱子把衣服塞进去用透明胶带给粘上,也可能你有专业人士给你设计的高大上包装盒,里面还放了一张五星好评返红包的卡片,再加个小礼物(把衣服用包装盒抱起来,里面加入小卡片和小礼物的这个过程就是编码本码没错了,好比Demo中的MessagePack打包方式),用包装盒打包完成包裹后,放进快递柜,顺丰的工作人员拿出包裹后,贴上了顺丰的快递单(相当于Demo中的TCP协议),然后把这个快递发出去了(你的数据包发出去了),第二天,客户收到快递包裹了,上面贴着顺丰的快递单,然后她开始拆包裹(也就是解码过程了,之前你怎么给它包起来的,她就怎么拆开,Demo中就是收到消息也得用MessagePack解包),然后取出了衣服,她看到这个包装盒真的很漂亮还看到了小礼物,觉得你太贴心了,感动的眼泪都差点出来了,于是赶紧把衣服拿回家试穿(这里就相当于是Handler做的事了),试完后发现稍微有点小,然后她决定换一件大点的,然后又把衣服打包发给你,依然选了TCP协议,进行编码,再发给你,你又解码,看看衣服有没有损坏......编不下去了哈哈,表达能力有待提升,就这个小例子都编了半天,相信你的理解能力。
下面来看看编码类,使用了MessagePack进行打包,也就是你把衣服装到包装盒里的过程,你得把你要发的消息打包起来,这样才能给快递公司寄出去,MessagePack的API可以查看官网:https://msgpack.org/
public class MessagePackEncoder extends MessageToByteEncoder
解码类,与编码对应,也使用MessagePack进行解包,就是收到包裹后,拆开包裹取出衣服的过程
public class MessagePackDecoder extends MessageToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf buf, List
Demo中比较麻烦的一个问题是同时支持TCP和WebSocket协议,做之前也调研过,最简单的办法就是监听2个端口,分别做不同的编解码,但是我还是想要在一个端口同时实现TCP和WebSocket协议的支持,后来也是借鉴了有个网友的方法,根据协议动态修改编解码器,判断的方法是获取协议签名的几个字符进行判断,如果是WebSocket的协议会有一个固定的协议信息,根据这个信息来判断是WebSocket还是TCP,由于目前就支持这两种协议,所以可以按照目前ProtocolDecoder中的判断方式,具体办法就是先将TCP和WebSocket使用的编解码器都加入,然后在ProtocolDecoder中进行判断,具体实现逻辑请查看Demo中的代码
public class NettyServer extends Thread {......@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();// 协议解码处理器,判断是什么协议(WebSocket还是TcpSocket),然后动态修改编解码器pipeline.addLast("protocolHandler", new ProtocolDecoder());/** TcpSocket协议需要使用的编解码器 */// Tcp粘包处理,添加一个LengthFieldBasedFrameDecoder解码器,它会在解码时按照消息头的长度来进行解码。pipeline.addLast("tcpFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4));// MessagePack解码器,消息进来后先由frameDecoder处理,再给msgPackDecoder处理pipeline.addLast("tcpMsgPackDecoder", new MessagePackDecoder());// Tcp粘包处理,添加一个// LengthFieldPrepender编码器,它会在ByteBuf之前增加4个字节的字段,用于记录消息长度。pipeline.addLast("tcpFrameEncoder", new LengthFieldPrepender(4));// MessagePack编码器,消息发出之前先由frameEncoder处理,再给msgPackEncoder处理pipeline.addLast("tcpMsgPackEncoder", new MessagePackEncoder());/** WebSocket协议需要使用的编解码器 */// websocket协议本身是基于http协议的,所以这边也要使用http解编码器pipeline.addLast("httpCodec", new HttpServerCodec());// netty是基于分段请求的,HttpObjectAggregator的作用是将请求分段再聚合,参数是聚合字节的最大长度pipeline.addLast("httpAggregator", new HttpObjectAggregator(65536));// 用于向客户端发送Html5文件,主要用于支持浏览器和服务端进行WebSocket通信pipeline.addLast("httpChunked", new ChunkedWriteHandler());// 管道消息处理pipeline.addLast("channelHandler", new ServerChannelHandler());}......
public class ProtocolDecoder extends ByteToMessageDecoder {/** * 请求行信息的长度,ws为:GET /ws HTTP/1.1, Http为:GET / HTTP/1.1 */private static final int PROTOCOL_LENGTH = 16;/** * WebSocket握手协议的前缀, 本例限定为:GET /ws ,在访问ws的时候,请求地址需要为如下格式 ws://ip:port/ws */private static final String WEBSOCKET_PREFIX = "GET /ws";@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List
还值得一提的就是Handler的处理,由于支持2中协议,TCP协议采用了MessagePack编解码,WebSocket使用JSON,所以对接受到的消息做了object类型的判断,根据不同类型使用不同的方法处理:
public class ServerChannelHandler extends SimpleChannelInboundHandler
这里具体就不讲那么细了,要不然篇幅会很长,代码里有详细注释,大家稍微看看就明白。经过上面的处理,最后封装了一个方法来处理不同协议的处理逻辑,这个方法的目的就是通过2种协议传来的数据还原成对象后,交给统一的方法处理逻辑
public class ServerChannelHandler extends SimpleChannelInboundHandler
客户端:
讲完服务端后,感觉客户端已经没什么好说的,需要注意的就是使用的编解码器与服务器端对应,就是你发的那个衣服的快递,怎么包装的,买家那边也会怎么拆开包装,你包了两层,她就需要拆两层
public class NettyClient extends Thread {......@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();// Tcp粘包处理,添加一个LengthFieldBasedFrameDecoder解码器,它会在解码时按照消息头的长度来进行解码。pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4));// MessagePack解码器,消息进来后先由frameDecoder处理,再给msgPackDecoder处理pipeline.addLast("msgPackDecoder", new MessagePackDecoder());// Tcp粘包处理,添加一个// LengthFieldPrepender编码器,它会在ByteBuf之前增加4个字节的字段,用于记录消息长度。pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));// MessagePack编码器,消息发出之前先由frameEncoder处理,再给msgPackEncoder处理pipeline.addLast("msgPackEncoder", new MessagePackEncoder());// 消息处理handlerpipeline.addLast("handler", new NettyClientHandler());}......
当然,你的IP和端口号需要与服务端对应:
public class RunClient {// Server端IP地址,根据实际情况进行修改static final String HOST = System.getProperty("host", "127.0.0.1");// Netty服务端监听端口号static final int PORT = Integer.parseInt(System.getProperty("port", "8888"));......
WebSocket端:
代码没什么难度,就一个html文件,直接用支持WebSocket的浏览器打开即可,里面也加入了注释,直接在之前基于WebSocket实现的Android和H5聊天通讯实例【附效果图附所有源码】这篇文章Demo的基础上修改的,代码就不粘这里占空间了,需要注意的仍然是IP和端口号要与服务端对应:
//参数就是与服务器连接的地址socket = new WebSocket("ws://127.0.0.1:8888/ws");//客户端收到服务器消息的时候就会执行这个回调方法socket.onmessage = function(event) {console.log("onmessage:"+event.data);var ta = document.getElementById("responseText");//解析jsonvar testEvent = JSON.parse(event.data);//将内容加入到文本框中ta.value = "【" +testEvent.time +","+testEvent.content +"】\n"+ta.value;}
源码地址:
CSDN下载 积分不受我的控制
Github地址
PS:如果发现代码中有写的不对、有更好的实现方法或者文章中有误的地方,还望各位指出,我及时修改