转载请注明出处,原文地址: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 {@Overrideprotected void encode(ChannelHandlerContext ctx, Object obj, ByteBuf buf) throws Exception {MessagePack msgPack = new MessagePack();//序列化操作byte[] bytes = msgPack.write(obj);//netty操作,将对象序列化数组传入ByteBufbuf.writeBytes(bytes);}}   

         解码类,与编码对应,也使用MessagePack进行解包,就是收到包裹后,拆开包裹取出衣服的过程

public class MessagePackDecoder extends MessageToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf buf, List objs) throws Exception {final byte[] bytes;final int length = buf.readableBytes();bytes = new byte[length];// 从数据包buf中获取要操作的byte数组buf.getBytes(buf.readerIndex(), bytes, 0, length);// 将bytes反序列化成对象,并添加到解码列表中MessagePack msgpack = new MessagePack();objs.add(msgpack.read(bytes));}}   

         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 out) throws Exception {String protocol = getBufStart(in);//System.out.println("ProtocolHandler protocol:" + protocol);if (protocol.startsWith(WEBSOCKET_PREFIX)) {// WebSocket协议处理,移除TcpSocket相关编解码器ctx.pipeline().remove("tcpFrameDecoder");ctx.pipeline().remove("tcpMsgPackDecoder");ctx.pipeline().remove("tcpFrameEncoder");ctx.pipeline().remove("tcpMsgPackEncoder");// 将对应的管道标记为ws协议ChannelWraper channelWraper = NettyServer.CLIENTS.get(ctx.channel().id().asLongText());if (channelWraper != null) {channelWraper.setProtocol(ChannelWraper.PROTOCOL_WS);}} else {// TcpSocket协议处理,移除WebSocket相关编解码器ctx.pipeline().remove("httpCodec");ctx.pipeline().remove("httpAggregator");ctx.pipeline().remove("httpChunked");// 将对应的管道标记为tcp协议ChannelWraper channelWraper = NettyServer.CLIENTS.get(ctx.channel().id().asLongText());if (channelWraper != null) {channelWraper.setProtocol(ChannelWraper.PROTOCOL_TCP);}}// 重置index标记位in.resetReaderIndex();// 移除该协议处理器,该channel后续的处理由对应协议安排好的编解码器处理ctx.pipeline().remove(this.getClass());}/** * 获取buffer中指定长度的信息 *  * @param in * @return */private String getBufStart(ByteBuf in) {int length = in.readableBytes();if (length > PROTOCOL_LENGTH) {length = PROTOCOL_LENGTH;}// 标记读取位置in.markReaderIndex();byte[] content = new byte[length];in.readBytes(content);return new String(content);}}   

         还值得一提的就是Handler的处理,由于支持2中协议,TCP协议采用了MessagePack编解码,WebSocket使用JSON,所以对接受到的消息做了object类型的判断,根据不同类型使用不同的方法处理:

public class ServerChannelHandler extends SimpleChannelInboundHandler {......// 接收到消息@Overrideprotected void channelRead0(ChannelHandlerContext ctx, final Object object) throws Exception {// 管道读取到消息,先判断消息对象是什么类型,然后做不同处理try {if (object instanceof ArrayValue) {// 这是经过MessagePack解码完成后的对象handleValue(ctx, (ArrayValue) object);} else if (object instanceof FullHttpRequest) {// HTTP请求对象handleHttpRequest(ctx, (FullHttpRequest) object);} else if (object instanceof WebSocketFrame) {// WebSocket消息对象handleWebSocketFrame(ctx, (WebSocketFrame) object);}} catch (Exception e) {e.printStackTrace();}}......   

        这里具体就不讲那么细了,要不然篇幅会很长,代码里有详细注释,大家稍微看看就明白。经过上面的处理,最后封装了一个方法来处理不同协议的处理逻辑,这个方法的目的就是通过2种协议传来的数据还原成对象后,交给统一的方法处理逻辑

public class ServerChannelHandler extends SimpleChannelInboundHandler {....../** * 统一处理Event,为了让TCP和Websocket的处理逻辑统一,封装了该方法,两种协议的对象采用不同序列化方案 *  * @param eventType * @param value * @param text * @throws IOException */private void handleEvent(EventType eventType, Value value, String text) throws IOException {switch (eventType) {// 对于测试类事件,转发给所有终端case TEST_EVENT:TestEvent testEvent = null;if (value != null) {// 如果是tcp协议,采用messagepack序列化testEvent = MessageConverter.converter(value, TestEvent.class);} else {// ws使用json序列化testEvent = JSON.parseObject(text, TestEvent.class);}System.out.println("收到新消息:" + testEvent);// 将该消息转发给所有终端sendEventToAll(testEvent);break;case OTHER_EVENT:// 其他事件,暂未处理System.out.println("未处理,主要用于测试判断不同类型事件");break;default:break;}}......   

客户端:

        讲完服务端后,感觉客户端已经没什么好说的,需要注意的就是使用的编解码器与服务器端对应,就是你发的那个衣服的快递,怎么包装的,买家那边也会怎么拆开包装,你包了两层,她就需要拆两层

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:如果发现代码中有写的不对、有更好的实现方法或者文章中有误的地方,还望各位指出,我及时修改

更多相关文章

  1. View事件分发机制
  2. android hander
  3. Android系统架构ABC
  4. android 多线程
  5. 你不知道的Runnable接口,深度解析Runnable接口
  6. Android消息处理机制
  7. 带着问题学习 Android(安卓)Handler 消息机制
  8. Android中使用WebView, WebChromeClient和WebViewClient加载网页
  9. Android(安卓)SDK23以上(包括23)无法使用httpclient的处理方法

随机推荐

  1. 将不同的Android项目连接到现有的Google-
  2. Linux上杀死eclipse进程
  3. 用vs2015开发Android靠谱吗
  4. Android笔记(9)---Fragment的生命周期以及
  5. Android4.X物理按键监听
  6. Android VR入门文章
  7. Android Handler处理机制 ( 一 )(图+源码
  8. (转)当Note2遇上NFC
  9. 美女开发者独立开发二十多款应用,月入上万
  10. 在截击中从响应中获得头球