(二十)(高级篇)自定义编解码

时间:2023-01-13 08:56:00

一,代码

1.1,客户端类

 

 1 package bhz.netty.custom.client;
2
3 import bhz.netty.custom.codec.NettyMessageDecoder;
4 import bhz.netty.custom.codec.NettyMessageEncoder;
5 import bhz.netty.custom.struct.Header;
6 import bhz.netty.custom.struct.NettyMessage;
7 import io.netty.bootstrap.Bootstrap;
8 import io.netty.channel.Channel;
9 import io.netty.channel.ChannelFuture;
10 import io.netty.channel.ChannelInitializer;
11 import io.netty.channel.EventLoopGroup;
12 import io.netty.channel.nio.NioEventLoopGroup;
13 import io.netty.channel.socket.SocketChannel;
14 import io.netty.channel.socket.nio.NioSocketChannel;
15
16 public class Client {
17
18 public static void main(String[] args) throws Exception {
19 //ONE:
20 //1 线程工作组
21 EventLoopGroup work = new NioEventLoopGroup();
22
23 //TWO:
24 //3 辅助类。用于帮助我们创建NETTY服务
25 Bootstrap b = new Bootstrap();
26 b.group(work) //绑定工作线程组
27 .channel(NioSocketChannel.class) //设置NIO的模式
28 // 初始化绑定服务通道
29 .handler(new ChannelInitializer<SocketChannel>() {
30 @Override
31 protected void initChannel(SocketChannel sc) throws Exception {
32
33 sc.pipeline().addLast(new NettyMessageDecoder(1024*1024*5, 4, 4));
34 sc.pipeline().addLast(new NettyMessageEncoder());
35 sc.pipeline().addLast(new ClientHandler());
36 }
37 });
38
39 ChannelFuture cf = b.connect("127.0.0.1", 8765).syncUninterruptibly();
40
41 System.err.println("client start....");
42
43 Channel c = cf.channel();
44
45 for(int i = 0; i < 50; i ++){
46 NettyMessage message = new NettyMessage();
47 Header header = new Header();
48 header.setSessionID(1001L);
49 header.setPriority((byte)1);
50 header.setType((byte)1);
51 message.setHeader(header);
52 message.setBody("我是请求数据" + i);
53 c.writeAndFlush(message);
54 }
55
56 //释放连接
57 cf.channel().closeFuture().sync();
58 work.shutdownGracefully();
59 }
60 }

 

1.2,客户端助手类

 1 package bhz.netty.custom.client;
2
3 import bhz.netty.custom.struct.NettyMessage;
4 import io.netty.channel.ChannelHandlerContext;
5 import io.netty.channel.ChannelInboundHandlerAdapter;
6 import io.netty.util.ReferenceCountUtil;
7
8 public class ClientHandler extends ChannelInboundHandlerAdapter {
9
10
11 @Override
12 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
13 try {
14
15 NettyMessage message = (NettyMessage)msg;
16 System.err.println("Client: " + message.getBody());
17
18 } finally {
19 ReferenceCountUtil.release(msg);
20 }
21 }
22
23 @Override
24 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
25 throws Exception {
26 System.err.println("----------客户端数据读异常-----------");
27 ctx.close();
28 }
29
30 }

 

1.3,协议头类

 1 package bhz.netty.custom.struct;
2
3 public class Header {
4
5 private int crcCode = 0xadaf0105; //唯一的通信标志
6
7 private int length; //总消息的长度 header + body
8
9 private long sessionID; //会话ID
10
11 private byte type ; //消息的类型
12
13 private byte priority; //消息的优先级
14
15 //...
16
17 public int getCrcCode() {
18 return crcCode;
19 }
20
21 public void setCrcCode(int crcCode) {
22 this.crcCode = crcCode;
23 }
24
25 public int getLength() {
26 return length;
27 }
28
29 public void setLength(int length) {
30 this.length = length;
31 }
32
33 public long getSessionID() {
34 return sessionID;
35 }
36
37 public void setSessionID(long sessionID) {
38 this.sessionID = sessionID;
39 }
40
41 public byte getType() {
42 return type;
43 }
44
45 public void setType(byte type) {
46 this.type = type;
47 }
48
49 public byte getPriority() {
50 return priority;
51 }
52
53 public void setPriority(byte priority) {
54 this.priority = priority;
55 }
56
57
58
59
60
61 }

 红字是一个安全协议,并不算头的一部分,就相当于html协议中,头里面不能有安全的东西。

1.4,协议头和实体封装类

 1 package bhz.netty.custom.struct;
2
3 public final class NettyMessage {
4
5 private Header header;
6
7 private Object body;
8
9 public final Header getHeader() {
10 return header;
11 }
12
13 public final void setHeader(Header header) {
14 this.header = header;
15 }
16
17 public final Object getBody() {
18 return body;
19 }
20
21 public final void setBody(Object body) {
22 this.body = body;
23 }
24
25
26 }

 

1.5,编码类

 1 package bhz.netty.custom.codec;
2
3 import java.io.IOException;
4
5 import bhz.netty.custom.struct.NettyMessage;
6 import io.netty.buffer.ByteBuf;
7 import io.netty.channel.ChannelHandlerContext;
8 import io.netty.handler.codec.MessageToByteEncoder;
9
10 public class NettyMessageEncoder extends MessageToByteEncoder<NettyMessage> {
11
12 private MarshallingEncoder marshallingEncoder;
13
14 public NettyMessageEncoder() throws IOException {
15 this.marshallingEncoder = new MarshallingEncoder();
16 }
17
18
19 @Override
20 protected void encode(ChannelHandlerContext ctx, NettyMessage message, ByteBuf sendBuf) throws Exception {
21 if(message == null || message.getHeader() == null){
22 throw new Exception("编码失败,没有数据信息!");
23 }
24
25 //Head:
26 sendBuf.writeInt(message.getHeader().getCrcCode());
27 sendBuf.writeInt(message.getHeader().getLength());
28 sendBuf.writeLong(message.getHeader().getSessionID());
29 sendBuf.writeByte(message.getHeader().getType());
30 sendBuf.writeByte(message.getHeader().getPriority());
31
32 //Body:
33 Object body = message.getBody();
34 //如果不为空 说明: 有数据
35 if(body != null){
36 //使用MarshallingEncoder
37 this.marshallingEncoder.encode(body, sendBuf);
38 } else {
39 //如果没有数据 则进行补位 为了方便后续的 decoder操作
40 sendBuf.writeInt(0);
41 }
42 //最后我们要获取整个数据包的总长度 也就是 header + body 进行对 header length的设置
43 // TODO: 解释: 在这里必须要-8个字节,4是起始的位置
44 sendBuf.setInt(4, sendBuf.readableBytes() - 8);
45 }
46
47 }

 4是起始的位置,因为开始设置了一个

private int crcCode = 0xadaf0105; //唯一的通信标志

这个字段,是不不要包含在Buff里面的,所以从4开始

而-8记住它就是一套自己的规则,先不花费精力去研究

那么8是怎么计算出来的呢

就是长度前面的字节数+长度本身的字节数就是这个

比如,

private int crcCode = 0xadaf0105; //唯一的通信标志

private int length; //总消息的长度 header + body

private long sessionID; //会话ID

private byte type ; //消息的类型

private byte priority; //消息的优先级

在这里面length字段前面有一个int的类型,所以占4个字节

而length本身是一个字节所以也占4个类型

所以是8个字节

假如改成

private int crcCode = 0xadaf0105; //唯一的通信标志

private long sessionID; //会话ID

private byte type ; //消息的类型

private byte priority; //消息的优先级
private int length; //总消息的长度 header + body

这种格式的话就是length前面一个int 4个字节

一个long8个字节

两个byte总共2个字节

加载一起就是14个字节

而length本身4个字节

就是18了。

先这样理解吧

1.6,编码对象类

 1 package bhz.netty.custom.codec;
2
3 import java.io.IOException;
4
5 import org.jboss.marshalling.Marshaller;
6
7 import io.netty.buffer.ByteBuf;
8
9 public class MarshallingEncoder {
10
11 //空白占位: 用于预留设置 body的数据包长度
12 private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
13
14 private Marshaller marshaller;
15
16 public MarshallingEncoder() throws IOException {
17 this.marshaller = MarshallingCodeCFactory.buildMarshalling();
18 }
19
20 public void encode(Object body, ByteBuf out) throws IOException {
21 try {
22 //必须要知道当前的数据位置是哪: 起始数据位置
23 int pos = out.writerIndex();
24 //占位写操作:
25 out.writeBytes(LENGTH_PLACEHOLDER);
26 ChannelBufferByteOutput output = new ChannelBufferByteOutput(out);
27 marshaller.start(output);
28 marshaller.writeObject(body);
29 marshaller.finish();
30 //总长度(结束位置) - 初始化长度(起始位置) - 预留的长度 = body数据长度
31 //pos就是body的起始的位置
32 out.setInt(pos, out.writerIndex() - pos - 4);
33
34 } finally {
35 marshaller.close();
36 }
37
38
39 }
40
41 }

 用这种方式计算出body实体的长度,先设计一个常量固定的长度,然后去除pos读到的长度,然后在去除设置的长度,最后得到的就是body实体的长度。

1.7,编码转化类,对象转化成字节流用来传输

 1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package bhz.netty.custom.codec;
17
18 import io.netty.buffer.ByteBuf;
19 import org.jboss.marshalling.ByteOutput;
20
21 import java.io.IOException;
22
23 /**
24 * {@link ByteOutput} implementation which writes the data to a {@link ByteBuf}
25 *
26 *
27 */
28 class ChannelBufferByteOutput implements ByteOutput {
29
30 private final ByteBuf buffer;
31
32 /**
33 * Create a new instance which use the given {@link ByteBuf}
34 */
35 public ChannelBufferByteOutput(ByteBuf buffer) {
36 this.buffer = buffer;
37 }
38
39 @Override
40 public void close() throws IOException {
41 // Nothing to do
42 }
43
44 @Override
45 public void flush() throws IOException {
46 // nothing to do
47 }
48
49 @Override
50 public void write(int b) throws IOException {
51 buffer.writeByte(b);
52 }
53
54 @Override
55 public void write(byte[] bytes) throws IOException {
56 buffer.writeBytes(bytes);
57 }
58
59 @Override
60 public void write(byte[] bytes, int srcIndex, int length) throws IOException {
61 buffer.writeBytes(bytes, srcIndex, length);
62 }
63
64 /**
65 * Return the {@link ByteBuf} which contains the written content
66 *
67 */
68 ByteBuf getBuffer() {
69 return buffer;
70 }
71 }

 

1.8,编解码工具类

 1 package bhz.netty.custom.codec;
2
3 import java.io.IOException;
4
5 import org.jboss.marshalling.Marshaller;
6 import org.jboss.marshalling.MarshallerFactory;
7 import org.jboss.marshalling.Marshalling;
8 import org.jboss.marshalling.MarshallingConfiguration;
9 import org.jboss.marshalling.Unmarshaller;
10
11 /**
12 * Marshalling工厂
13 * @author(alienware)
14 * @since 2014-12-16
15 */
16 public final class MarshallingCodeCFactory {
17
18 /**
19 * 创建Jboss Marshalling解码器MarshallingDecoder
20 * @return MarshallingDecoder
21 * @throws IOException
22 */
23 public static Marshaller buildMarshalling() throws IOException {
24 //首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
25 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
26 //创建了MarshallingConfiguration对象,配置了版本号为5
27 final MarshallingConfiguration configuration = new MarshallingConfiguration();
28 configuration.setVersion(5);
29 Marshaller marshaller = marshallerFactory.createMarshaller(configuration);
30 return marshaller;
31 }
32
33 /**
34 * 创建Jboss Marshalling编码器MarshallingEncoder
35 * @return MarshallingEncoder
36 * @throws IOException
37 */
38 public static Unmarshaller buildUnMarshalling() throws IOException {
39 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
40 final MarshallingConfiguration configuration = new MarshallingConfiguration();
41 configuration.setVersion(5);
42 Unmarshaller unmarshaller = marshallerFactory.createUnmarshaller(configuration);
43 return unmarshaller;
44 }
45 }

 

MarshallerFactory 注意这个类和之前用的
Marshalling的编解码是不一样的
因为原来的是整个对象的编解码
而这个类是可以对整个对象的局部的对象的编解码的
比如这里面有一个body就是局部的对象
是一个一对多额关系

1.9,解码类

 1 package bhz.netty.custom.codec;
2
3 import java.io.IOException;
4
5 import bhz.netty.custom.struct.Header;
6 import bhz.netty.custom.struct.NettyMessage;
7 import io.netty.buffer.ByteBuf;
8 import io.netty.channel.ChannelHandlerContext;
9 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
10
11 /**
12 * LengthFieldBasedFrameDecoder 是为了解决 拆包粘包等问题的
13 * @author Alienware
14 *
15 */
16 public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
17
18 private MarshallingDecoder marshallingDecoder;
19
20 /**
21 * 那减8应该是因为要把CRC和长度本身占的剪掉了。我猜错没
22 * @param maxFrameLength 第一个参数代表最大的长度 1024*1024*5
23 * @param lengthFieldOffset 代表长度属性的偏移量 简单来说就是message中 总长度的起始位置 4
24 * @param lengthFieldLength 代表长度属性的长度 整个属性占多长 4
25 * @throws IOException
26 */
27 public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) throws IOException {
28 super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
29 this.marshallingDecoder = new MarshallingDecoder();
30 }
31
32 @Override
33 protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
34 //1 调用父类(LengthFieldBasedFrameDecoder)方法:
35 ByteBuf frame = (ByteBuf)super.decode(ctx, in);
36
37 if(frame == null){
38 return null;
39 }
40
41 NettyMessage message = new NettyMessage();
42 Header header = new Header();
43 header.setCrcCode(frame.readInt()); //crcCode ----> 添加通信标记认证逻辑
44 header.setLength(frame.readInt()); //length
45 header.setSessionID(frame.readLong()); //sessionID
46 header.setType(frame.readByte()); //type
47 header.setPriority(frame.readByte()); //priority
48
49 message.setHeader(header);
50
51 if(frame.readableBytes() > 4) {
52 message.setBody(marshallingDecoder.decode(frame));
53 }
54 return message;
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68 }

 

1.10,解码对象类

 1 package bhz.netty.custom.codec;
2
3 import java.io.IOException;
4
5 import org.jboss.marshalling.Unmarshaller;
6
7 import io.netty.buffer.ByteBuf;
8
9 public class MarshallingDecoder {
10
11 private Unmarshaller unmarshaller;
12
13 public MarshallingDecoder() throws IOException{
14 this.unmarshaller = MarshallingCodeCFactory.buildUnMarshalling();
15 }
16
17 public Object decode(ByteBuf in) throws Exception {
18 try {
19 //1 首先读取4个长度(实际body内容长度),因为在编码的时候设置了一个4个字节的站位付,所以这个时候要加上一个4个字节的站位符作为重新计算长度的。
20 int bodySize = in.readInt();
21 //2 获取实际body的缓冲内容,因为这个readerIndex的索引,是因为前面的
22 /* header.setCrcCode(frame.readInt()); //crcCode ----> 添加通信标记认证逻辑
23 header.setLength(frame.readInt()); //length
24 header.setSessionID(frame.readLong()); //sessionID
25 header.setType(frame.readByte()); //type
26 header.setPriority(frame.readByte()); //priority
27 这些已经把游标给移动了,所以这个时候就会是现在的索引值.*/
28 //slice这个方法
29 ByteBuf buf = in.slice(in.readerIndex(), bodySize);
30 //3 转换
31 ChannelBufferByteInput input = new ChannelBufferByteInput(buf);
32 //4 读取操作:
33 this.unmarshaller.start(input);
34 Object ret = this.unmarshaller.readObject();
35 this.unmarshaller.finish();
36 //5 读取完毕以后, 更新当前读取起始位置: 因为slice的方法只会读取内容,而不会修改索引值的,所以读完之后要修改索引值的。
37 in.readerIndex(in.readerIndex() + bodySize);
38
39 return ret;
40
41 } finally {
42 this.unmarshaller.close();
43 }
44 }
45
46 }

 

slice方法只是读取数据,并不会改变索引值的,所以改完之后也需要把索引这改到读到的对应的索引值的位置

1.11,解码转化类,字节流转化成对象,用来读取

 1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package bhz.netty.custom.codec;
17
18 import io.netty.buffer.ByteBuf;
19 import org.jboss.marshalling.ByteInput;
20
21 import java.io.IOException;
22
23 /**
24 * {@link ByteInput} implementation which reads its data from a {@link ByteBuf}
25 */
26 class ChannelBufferByteInput implements ByteInput {
27
28 private final ByteBuf buffer;
29
30 public ChannelBufferByteInput(ByteBuf buffer) {
31 this.buffer = buffer;
32 }
33
34 @Override
35 public void close() throws IOException {
36 // nothing to do
37 }
38
39 @Override
40 public int available() throws IOException {
41 return buffer.readableBytes();
42 }
43
44 @Override
45 public int read() throws IOException {
46 if (buffer.isReadable()) {
47 return buffer.readByte() & 0xff;
48 }
49 return -1;
50 }
51
52 @Override
53 public int read(byte[] array) throws IOException {
54 return read(array, 0, array.length);
55 }
56
57 @Override
58 public int read(byte[] dst, int dstIndex, int length) throws IOException {
59 int available = available();
60 if (available == 0) {
61 return -1;
62 }
63
64 length = Math.min(available, length);
65 buffer.readBytes(dst, dstIndex, length);
66 return length;
67 }
68
69 @Override
70 public long skip(long bytes) throws IOException {
71 int readable = buffer.readableBytes();
72 if (readable < bytes) {
73 bytes = readable;
74 }
75 buffer.readerIndex((int) (buffer.readerIndex() + bytes));
76 return bytes;
77 }
78
79 }

 

1.12,服务端类

 1 package bhz.netty.custom.server;
2
3
4 import bhz.netty.custom.codec.NettyMessageDecoder;
5 import bhz.netty.custom.codec.NettyMessageEncoder;
6 import io.netty.bootstrap.ServerBootstrap;
7 import io.netty.channel.ChannelFuture;
8 import io.netty.channel.ChannelInitializer;
9 import io.netty.channel.ChannelOption;
10 import io.netty.channel.EventLoopGroup;
11 import io.netty.channel.nio.NioEventLoopGroup;
12 import io.netty.channel.socket.SocketChannel;
13 import io.netty.channel.socket.nio.NioServerSocketChannel;
14
15 public class Server {
16
17
18 public static void main(String[] args) throws Exception {
19 //ONE:
20 //1 用于接受客户端连接的线程工作组
21 EventLoopGroup boss = new NioEventLoopGroup();
22 //2 用于对接受客户端连接读写操作的线程工作组
23 EventLoopGroup work = new NioEventLoopGroup();
24
25 //TWO:
26 //3 辅助类。用于帮助我们创建NETTY服务
27 ServerBootstrap b = new ServerBootstrap();
28 b.group(boss, work) //绑定两个工作线程组
29 .channel(NioServerSocketChannel.class) //设置NIO的模式
30 .option(ChannelOption.SO_BACKLOG, 1024) //设置TCP缓冲区
31 //.option(ChannelOption.SO_SNDBUF, 32*1024) // 设置发送数据的缓存大小
32 .option(ChannelOption.SO_RCVBUF, 32*1024) // 设置接受数据的缓存大小
33 .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE) // 设置保持连接
34 .childOption(ChannelOption.SO_SNDBUF, 32*1024)
35 // 初始化绑定服务通道
36 .childHandler(new ChannelInitializer<SocketChannel>() {
37 @Override
38 protected void initChannel(SocketChannel sc) throws Exception {
39 sc.pipeline().addLast(new NettyMessageDecoder(1024*1024*5, 4, 4));
40 sc.pipeline().addLast(new NettyMessageEncoder());
41 sc.pipeline().addLast(new ServerHandler());
42 }
43 });
44
45 ChannelFuture cf = b.bind(8765).sync();
46
47 System.err.println("Server start... ");
48
49 //释放连接
50 cf.channel().closeFuture().sync();
51 work.shutdownGracefully();
52 boss.shutdownGracefully();
53 }
54 }

 

1.13,服务端工具

 1 package bhz.netty.custom.server;
2
3 import bhz.netty.custom.struct.Header;
4 import bhz.netty.custom.struct.NettyMessage;
5 import io.netty.buffer.ByteBuf;
6 import io.netty.buffer.Unpooled;
7 import io.netty.channel.ChannelHandlerContext;
8 import io.netty.channel.ChannelInboundHandlerAdapter;
9
10 public class ServerHandler extends ChannelInboundHandlerAdapter {
11
12 /**
13 * 当我们通道进行激活的时候 触发的监听方法
14 */
15 @Override
16 public void channelActive(ChannelHandlerContext ctx) throws Exception {
17
18 System.err.println("--------通道激活------------");
19 }
20
21 /**
22 * 当我们的通道里有数据进行读取的时候 触发的监听方法
23 */
24 @Override
25 public void channelRead(ChannelHandlerContext ctx /*NETTY服务上下文*/, Object msg /*实际的传输数据*/) throws Exception {
26
27 NettyMessage requestMessage = (NettyMessage)msg;
28
29 System.err.println("Server: " + requestMessage.getBody());
30
31 NettyMessage responseMessage = new NettyMessage();
32 Header header = new Header();
33 header.setSessionID(2002L);
34 header.setPriority((byte)2);
35 header.setType((byte)2);
36 responseMessage.setHeader(header);
37 responseMessage.setBody("我是响应数据: " + requestMessage.getBody());
38 ctx.writeAndFlush(responseMessage);
39
40 }
41
42 @Override
43 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
44 System.err.println("--------数据读取完毕----------");
45 }
46
47 @Override
48 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
49 throws Exception {
50 System.err.println("--------服务器数据读异常----------: ");
51 cause.printStackTrace();
52 ctx.close();
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67 }

 

1.14,结果显示

客户端

 1 client start....
2 Client: 我是响应数据: 我是请求数据0
3 Client: 我是响应数据: 我是请求数据1
4 Client: 我是响应数据: 我是请求数据2
5 Client: 我是响应数据: 我是请求数据3
6 Client: 我是响应数据: 我是请求数据4
7 Client: 我是响应数据: 我是请求数据5
8 Client: 我是响应数据: 我是请求数据6
9 Client: 我是响应数据: 我是请求数据7
10 Client: 我是响应数据: 我是请求数据8
11 Client: 我是响应数据: 我是请求数据9
12 Client: 我是响应数据: 我是请求数据10
13 Client: 我是响应数据: 我是请求数据11
14 Client: 我是响应数据: 我是请求数据12
15 Client: 我是响应数据: 我是请求数据13
16 Client: 我是响应数据: 我是请求数据14
17 Client: 我是响应数据: 我是请求数据15
18 Client: 我是响应数据: 我是请求数据16
19 Client: 我是响应数据: 我是请求数据17
20 Client: 我是响应数据: 我是请求数据18
21 Client: 我是响应数据: 我是请求数据19
22 Client: 我是响应数据: 我是请求数据20
23 Client: 我是响应数据: 我是请求数据21
24 Client: 我是响应数据: 我是请求数据22
25 Client: 我是响应数据: 我是请求数据23
26 Client: 我是响应数据: 我是请求数据24
27 Client: 我是响应数据: 我是请求数据25
28 Client: 我是响应数据: 我是请求数据26
29 Client: 我是响应数据: 我是请求数据27
30 Client: 我是响应数据: 我是请求数据28
31 Client: 我是响应数据: 我是请求数据29
32 Client: 我是响应数据: 我是请求数据30
33 Client: 我是响应数据: 我是请求数据31
34 Client: 我是响应数据: 我是请求数据32
35 Client: 我是响应数据: 我是请求数据33
36 Client: 我是响应数据: 我是请求数据34
37 Client: 我是响应数据: 我是请求数据35
38 Client: 我是响应数据: 我是请求数据36
39 Client: 我是响应数据: 我是请求数据37
40 Client: 我是响应数据: 我是请求数据38
41 Client: 我是响应数据: 我是请求数据39
42 Client: 我是响应数据: 我是请求数据40
43 Client: 我是响应数据: 我是请求数据41
44 Client: 我是响应数据: 我是请求数据42
45 Client: 我是响应数据: 我是请求数据43
46 Client: 我是响应数据: 我是请求数据44
47 Client: 我是响应数据: 我是请求数据45
48 Client: 我是响应数据: 我是请求数据46
49 Client: 我是响应数据: 我是请求数据47
50 Client: 我是响应数据: 我是请求数据48
51 Client: 我是响应数据: 我是请求数据49

 

(二十)(高级篇)自定义编解码

 

服务端

 1 Server start... 
2 --------通道激活------------
3 Server: 我是请求数据0
4 Server: 我是请求数据1
5 Server: 我是请求数据2
6 Server: 我是请求数据3
7 Server: 我是请求数据4
8 Server: 我是请求数据5
9 Server: 我是请求数据6
10 Server: 我是请求数据7
11 Server: 我是请求数据8
12 Server: 我是请求数据9
13 Server: 我是请求数据10
14 Server: 我是请求数据11
15 Server: 我是请求数据12
16 Server: 我是请求数据13
17 Server: 我是请求数据14
18 Server: 我是请求数据15
19 Server: 我是请求数据16
20 Server: 我是请求数据17
21 Server: 我是请求数据18
22 Server: 我是请求数据19
23 Server: 我是请求数据20
24 Server: 我是请求数据21
25 Server: 我是请求数据22
26 Server: 我是请求数据23
27 Server: 我是请求数据24
28 Server: 我是请求数据25
29 Server: 我是请求数据26
30 Server: 我是请求数据27
31 Server: 我是请求数据28
32 Server: 我是请求数据29
33 Server: 我是请求数据30
34 Server: 我是请求数据31
35 Server: 我是请求数据32
36 Server: 我是请求数据33
37 Server: 我是请求数据34
38 Server: 我是请求数据35
39 Server: 我是请求数据36
40 Server: 我是请求数据37
41 Server: 我是请求数据38
42 Server: 我是请求数据39
43 Server: 我是请求数据40
44 Server: 我是请求数据41
45 Server: 我是请求数据42
46 Server: 我是请求数据43
47 Server: 我是请求数据44
48 Server: 我是请求数据45
49 Server: 我是请求数据46
50 Server: 我是请求数据47
51 Server: 我是请求数据48
52 Server: 我是请求数据49
53 --------数据读取完毕----------

(二十)(高级篇)自定义编解码