阅读本文建议从第一篇开始往后看
本系列文章
- Netty在Android开发中的应用实战系列(一)——— 搭建服务端与客户端
- Netty在Android开发中的应用实战系列(二)——— Encoder | Decoder | Handler 的使用
- Netty在Android开发中的应用实战系列(三)——— 心跳处理 | 断线重连
- Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理
- Netty在Android开发中的应用实战系列(五)——— 创建Web服务 | 作为HTTP服务器
一、Encoder的作用
将发送的数据进行编码成需要的数据格式,然后发送出去
二、Decoder的作用
将收到的数据根据数据协议进行解码,然后处理
三、Handler的作用
将解码好的数据进行处理
四、下面们通过一个简单的一个示例进行收发数据
- 定义一个传输的数据包格式
包头 | 命令字 | 数据长度 | 数据区 | 包尾 |
---|---|---|---|---|
0x2A | 一个字节 | 一个字节 | 数据字符串 | 0x2A |
- 根据上面定义的数据包,便可以生成对应的数据实体类;如下:
- 数据实体类
public class PkgDataBean {
//命令字
private byte cmd;
//数据长度
private byte dataLength;
//数据
private String data;
//省略get/set函数
}
- 客户端发送数据就可以直接写入
PkgDataBean
对象,然后在ClientEncoder
中编码 - 发送数据
//获取与服务端的连接通道
Channel channel = NettyClient.getChannel();
PkgDataBean bean = new PkgDataBean();
bean.setCmd((byte) 0x01);
bean.setData(etContent.getText().toString());
bean.setDataLength((byte) bean.getData().getBytes().length);
//写入数据
channel.writeAndFlush(bean);
- 对数据进行编码,将对象转成字节数组。可以注意到这里我们将泛型直接使用的是定义的实体类
public class ClientEncoder extends MessageToByteEncoder<PkgDataBean> {
private static final String TAG = "ClientEncoder";
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, PkgDataBean data, ByteBuf byteBuf) throws Exception {
//根据数据包协议,生成byte数组
byte[] bytes = {0x2A, data.getCmd(), data.getDataLength()};
byte[] dataBytes = data.getData().getBytes();
//将所有数据合并成一个byte数组
byte[] all = ByteUtil.byteMergerAll(bytes, dataBytes, new byte[]{0x2A});
//发送数据
byteBuf.writeBytes(all);
}
}
- 程序执行的效果如下:
五、在客户端发送数据我们使用了Encoder
进行编码,那么服务端要接收这些数据就需要在Decoder
中进行解码,这样就能够拿到正确的数据了
- 服务端的Decoder实现,将数据解码生成
PkgDataBean
实体
public class ServerDecoder extends ByteToMessageDecoder {
private static final String TAG = "ServerDecoder";
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int length = byteBuf.readableBytes();
//收到的数据包
byte[] data = byteBuf.readBytes(length).array();
//判断数据包是不是一个正确的数据包
if (data[0] == 0x2A && data[0] == data[data.length - 1]) {
PkgDataBean bean = new PkgDataBean();
bean.setCmd(data[1]);
bean.setDataLength(data[2]);
byte[] bytes = Arrays.copyOfRange(data, 3, 3 + bean.getDataLength());
bean.setData(new String(bytes));
Log.d(TAG, "收到了客户端发送的数据:" + bean.toString());
}
}
}
- 运行的效果
六、上面我们说到的都是客户端发送数据,服务端接收数据;在实际开发中肯定都是双方都是有数据发送和接收的,所以双方都会有Encoder
、Decoder
进行编解码,用法都是一样的这里就不累赘阐述了
七、在解码器中我们将数据解析出来了,那么就需要使用到业务中了;也就是在Handler
中处理,所以Handler可以说是我们的数据处理中心、包括重连、心跳、客户端上下线等等…(这些后面的文章会讲到)
-
先介绍一下重点的几个方法
- channelRead0()——>当收到数据的回调
- channelActive()——>有客户端连接过来的回调
- channelInactive()——>有客户端断开了连接的回调
- userEventTriggered()——>空闲事件的回调
- exceptionCaught()——>发生异常的回调
-
Handler中的数据从哪传过来的呢?这个当然是从
Decoder
中来的了,如下:
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int length = byteBuf.readableBytes();
//收到的数据包
byte[] data = byteBuf.readBytes(length).array();
//判断数据包是不是一个正确的数据包
if (data[0] == 0x2A && data[0] == data[data.length - 1]) {
PkgDataBean bean = new PkgDataBean();
bean.setCmd(data[1]);
bean.setDataLength(data[2]);
byte[] bytes = Arrays.copyOfRange(data, 3, 3 + bean.getDataLength());
bean.setData(new String(bytes));
Log.d(TAG, "收到了客户端发送的数据:" + bean.toString());
//将数据传递给下一个Handler,也就是在NettyServer给ChannelPipeline添加的处理器
list.add(bean);
}
}
- 将数据解析完毕后使用list.add(),将数据传递至Handler
Handler
的代码如下:
public class ServerHandler extends SimpleChannelInboundHandler<PkgDataBean> {
private static final String TAG = "ServerHandler";
/**
* 当收到数据的回调
*
* @param channelHandlerContext 封装的连接对像
* @param bean
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, PkgDataBean bean) throws Exception {
Log.d(TAG, "收到了解码器处理过的数据:" + bean.toString());
}
}
- 运行的效果
- 这里也同样接受一个泛型需要注意的是这里指定的泛型必须与Decoder中list.add()的类型一致,否则会出错!
八、下一篇文章将着重对断线重连、心跳处理进行讲解说明
Demo将会在本系列文章第四篇文章中给出