前言
Netty服务端项目模块级代码
这一篇主要是用于使用 Socket 通信的端对端的项目,本篇采取Java代码编写服务端,通过自定义编解码器实现数据约定及安全性校对,如有BUG请广大网友批评指针。
下面展示项目的包结构:
- 最外层的类为Netty初始化及基础的通道处理;
- 其中有三个大目录,business\codec\protocol,分别是业务包、编解码器、私定协议;
- request包及response包为请求消息体及响应消息体的封装;
将依赖引入后,将进行正式开始流程说明。
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
本文是采取与SpringBoot的结合方式进行编码。
一、初始化启动
首先将服务端进行进程启动。
NettyServerInitialize.java
package org.springblade.netty;
import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.CompletableFuture;
/**
* @author 李家民
*/
@Component
public class NettyServerInitialize {
private static Logger log = LoggerFactory.getLogger(NettyServerInitialize.class);
/** 进程组 */
private static EventLoopGroup bossGroup;
private static EventLoopGroup workerGroup;
private static ServerBootstrap serverBootstrap;
/** 启动监听 */
private static ChannelFuture channelFuture;
/** 端口 */
private static Integer port = 10222;
static {
// Server初始化
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(2);
serverBootstrap =
new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new NettyServerChannel());
}
@PostConstruct
public void starter() {
CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
try {
channelFuture = serverBootstrap.bind(port)
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("监听端口 " + port + " 成功");
} else {
log.error("监听端口 " + port + " 失败");
}
}
}).channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
});
}
}
然后我们需要通道处理器进行流水线处理。
NettyServerChannel.java
package org.springblade.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import org.springblade.netty.codec.BusinessMsgDecoder;
import org.springblade.netty.codec.BusinessMsgEncoder;
/**
* 通道初始化器
*/
public class NettyServerChannel extends ChannelInitializer<SocketChannel> {
public NettyServerChannel() {
super();
}
/**
* 在将ChannelHandler添加到实际上下文并准备好处理事件后调用 如果覆盖此方法 请确保您调用 super
* @param ctx 通道处理器上下文
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
}
/**
* Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 编解码器 自定义
pipeline.addLast(new BusinessMsgEncoder());
pipeline.addLast(new BusinessMsgDecoder());
pipeline.addLast(new NettyServerInboundHandler());
}
}
二、消息入站
从上面的代码可以看到,我们在ChannelPipeline中加入了三个处理器,分别是
- 编码器;
- 解码器;
- 入站处理器;
我们按照数据的流入到转出纵观看向这幅代码,数据进站前,流入解码器。
BusinessMsgDecoder.java
package org.springblade.netty.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.netty.protocol.MsgProtocol;
import org.springblade.netty.protocol.request.ServerRequest;
import org.springblade.netty.protocol.response.SendResponse;
import java.util.Arrays;
import java.util.List;
/**
* 自定义业务解码器
* @author lijiamin
*/
public class BusinessMsgDecoder extends ByteToMessageDecoder {
private static final Logger logger = LoggerFactory.getLogger(BusinessMsgDecoder.class);
/**
* 消息解码量化交易
* flag(1 byte)+length(4 byte,后边内容的长度)+protocol code(4 byte)+content
* length的长度包括 :消息号 + 内容
* @param channelHandlerContext
* @param byteBuf
* @param list
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
// 数据包完整性校对
Integer dateLength = MsgProtocol.flagSize + MsgProtocol.lengthSize + MsgProtocol.msgCodeSize;
int readableBytes = byteBuf.readableBytes();
if (byteBuf.readableBytes() < dateLength) {
// 数据包长度不足
logger.info("decode -- 数据包长度不足");
return;
}
// 返回表示 ByteBuf 当前可读取的字节数
byte[] req = new byte[readableBytes];
// 把 ByteBuf 里面的数据全部读取到字节数组中
byteBuf.readBytes(req);
if (req[0] != 1) {
logger.info("flag 错误");
return;
}
// 参数体长度
byte codeLength = req[4];
if (codeLength != (req.length - 4 - 1)) {
logger.info("数据包尚不完整");
return;
}
// 协议码
byte codeByte = req[8];
if (readableBytes == 9) {
// 无参传递
list.add(new ServerRequest((int) codeByte, ""));
} else {
// 有参传递
byte[] detailBytes = Arrays.copyOfRange(req, 11, req.length);
list.add(new ServerRequest((int) codeByte, new String(detailBytes)));
}
}
}
当我们根据私定的协议对数据进行处理完成后,放入下一站处理器。
关于约定的协议格式后续会进行说明
NettyServerInboundHandler.java
package org.springblade.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.undertow.util.CopyOnWriteMap;
import org.springblade.netty.business.dispatcher.MsgDispatcher;
import org.springblade.netty.protocol.GameSession;
import org.springblade.netty.protocol.request.ServerRequest;
import org.springblade.netty.protocol.response.ResponseMsg;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
/**
* 处理器
* @author 李家民
*/
public class NettyServerInboundHandler extends ChannelInboundHandlerAdapter {
/** 通道储存 */
private static CopyOnWriteMap<String, Channel> mapChannel = new CopyOnWriteMap<String, Channel>();
/** 消息分发处理器 */
private static MsgDispatcher msgDispatcher = new MsgDispatcher();
/**
* 消息广播推送
* @param msg 消息体
*/
public static void sendBroadcast(ResponseMsg msg) {
Set<String> setAddress = new HashSet<>();
// 广播
mapChannel.forEach(new BiConsumer<String, Channel>() {
@Override
public void accept(String address, Channel channel) {
if (channel.isActive()) {
channel.writeAndFlush(msg);
} else {
setAddress.add(address);
}
}
});
// 将失活连接铲除
for (String address : setAddress) {
mapChannel.remove(address);
}
}
/**
* 通道关闭
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
mapChannel.remove(ctx.channel().remoteAddress().toString());
super.channelInactive(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketAddress remoteAddress = ctx.channel().remoteAddress();
mapChannel.put(remoteAddress.toString(), ctx.channel());
super.channelActive(ctx);
}
/**
* 消息读取
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ServerRequest serverRequest = (ServerRequest) msg;
msgDispatcher.dispatchMsg(new GameSession(ctx), serverRequest);
}
/**
* 数据读取完成后的操作
* 1. writeAndFlush代表写入+刷新
* 2. 还需要对返回的数据进行编码
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
/**
* 发生异常后, 一般是需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
cause.printStackTrace();
super.exceptionCaught(ctx, cause);
}
}
三、关于双方私定的协议
interface MsgProtocol
package org.springblade.netty.protocol;
/**
* 消息协议相关常量
* @author xxx
*/
public interface MsgProtocol {
/** 默认flag值 */
byte defaultFlag = 1;
/** 最大长度 */
int maxPackLength = 1024 * 5;
/** 标识数占得 byte数 */
int flagSize = 1;
/** 协议中 长度部分 占用的byte数,其值表示( 协议号+内容) 的长度 */
int lengthSize = 4;
/** 消息号占用的byte数 */
int msgCodeSize = 4;
}
- flag(1 byte)+length(4 byte,后边内容的长度)+protocol code(4 byte)+content
- length的长度包括 :消息号 + 内容
另外对ChannelHandlerContext也有一层自己的包装GameSession
package org.springblade.netty.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.Data;
import org.springblade.netty.protocol.response.ResponseMsg;
/**
* 消息通道的包装
* @author 李家民
*/
public class GameSession {
/** 通道 */
private ChannelHandlerContext channelHandlerContext;
/** 在线状态 */
public Boolean isOnline = false;
/**
* 构造方法
* @param channelHandlerContext 通道
*/
public GameSession(ChannelHandlerContext channelHandlerContext) {
this.channelHandlerContext = channelHandlerContext;
}
/**
* 消息发送
* @param msg 消息体
*/
public void sendMessage(ResponseMsg msg){
// ...校验是否登录
// ...
channelHandlerContext.writeAndFlush(msg);
}
}
另:请求体与响应体的包装
- ServerRequest
- ResponseMsg
- ServerResponse
- SendResponse
- MsgBodyWrap
请求体
/**
* 请求消息体
* @author 李家民
*/
@Data
@AllArgsConstructor
public class ServerRequest {
private Integer code;
private String data;
}
响应体
ResponseMsg
package org.springblade.netty.protocol.response;
import io.netty.buffer.ByteBuf;
/**
* 此接口声明了响应消息编码函数,服务端生产的每个响应消息都必须实现此接口。<br>
* 这里声明的函数在protocolEncoder中会被自动调用,编码成byte stream发送到客户端。
*
* @author xxx
*
*/
public interface ResponseMsg {
/**
* 设置消息号
* @param code
*/
public void setMsgCode(int code);
/**
* 返回消息的整体封包
* @return
*/
public ByteBuf entireMsg(ByteBuf out);
/**
* 释放资源(数据流、对象引用)
*/
public void release();
}
public class ServerResponse implements ResponseMsg
package org.springblade.netty.protocol.response;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import org.springblade.netty.protocol.MsgProtocol;
/**
* 服务端发给客户端的消息。 所有返回给客户端的消息都最好继承于它.<br>
* 这里封装了基本的输出字节操作。
* @author dyz
*/
public class ServerResponse implements ResponseMsg {
protected MsgBodyWrap output = MsgBodyWrap.newInstance4Out();
private int msgCode;
private int status;
/** 必须调用此方法设置消息号 */
public ServerResponse(int status, int msgCode) {
setStatus(status);
setMsgCode(msgCode);
}
public void setStatus(int status) {
this.status = status;
}
public void setMsgCode(int code) {
msgCode = code;
}
public synchronized ByteBuf entireMsg(ByteBuf out) {
byte[] body = output.toByteArray();
// 标志 byte 长度short
int length = MsgProtocol.flagSize+MsgProtocol.lengthSize+MsgProtocol.msgCodeSize+ body.length+4;
// 处理
out.writeByte(MsgProtocol.defaultFlag);
out.writeInt(length);
out.writeInt(msgCode);
out.writeInt(status);
out.writeBytes(body);
return out;
}
/**
* 释放资源(数据流、对象引用)
*/
public synchronized void release() {
if (output != null) {
output.close();
output = null;
}
output = null;
}
}
public class ServerResponse implements ResponseMsg
package org.springblade.netty.protocol.response;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import org.springblade.netty.protocol.MsgProtocol;
/**
* 服务端发给客户端的消息。 所有返回给客户端的消息都最好继承于它.<br>
* 这里封装了基本的输出字节操作。
* @author xxx
*/
public class ServerResponse implements ResponseMsg {
protected MsgBodyWrap output = MsgBodyWrap.newInstance4Out();
private int msgCode;
private int status;
/** 必须调用此方法设置消息号 */
public ServerResponse(int status, int msgCode) {
setStatus(status);
setMsgCode(msgCode);
}
public void setStatus(int status) {
this.status = status;
}
public void setMsgCode(int code) {
msgCode = code;
}
public synchronized ByteBuf entireMsg(ByteBuf out) {
byte[] body = output.toByteArray();
// 标志 byte 长度short
int length = MsgProtocol.flagSize+MsgProtocol.lengthSize+MsgProtocol.msgCodeSize+ body.length+4;
// 处理
out.writeByte(MsgProtocol.defaultFlag);
out.writeInt(length);
out.writeInt(msgCode);
out.writeInt(status);
out.writeBytes(body);
return out;
}
/**
* 释放资源(数据流、对象引用)
*/
public synchronized void release() {
if (output != null) {
output.close();
output = null;
}
output = null;
}
}
public class SendResponse extends ServerResponse
package org.springblade.netty.protocol.response;
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
/**
* 消息返回模板
* @author 李家民
*/
public class SendResponse extends ServerResponse {
/**
* 消息返回模板
* @param status 消息状态 例如:200 成功
* @param data 实体数据
* @param msgCode 返回码
* @param detail 对这条数据的描述信息
*/
public SendResponse(int status, Object data, int msgCode, String detail) {
super(status, msgCode);
try {
if (status > 0) {
output.writeUTF(JSONObject.toJSONString(data));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
output.close();
}
}
}
最后就是这个消息体的封装
package org.springblade.netty.protocol.response;
import java.io.*;
/**
* 消息体封装 用于封装从客户端读取到的数据 或者发送到客户端的数据
* @author xxx
*/
public class MsgBodyWrap {
private ByteArrayInputStream in;
private ByteArrayOutputStream out;
private DataInputStream dataIn;
private DataOutputStream dataOut;
public static MsgBodyWrap newInstance4In(byte[] buffer) {
return new MsgBodyWrap(buffer);
}
public static MsgBodyWrap newInstance4Out() {
return new MsgBodyWrap();
}
private MsgBodyWrap(byte[] buffer) {
in = new ByteArrayInputStream(buffer);
dataIn = new DataInputStream(in);
}
private MsgBodyWrap() {
out = new ByteArrayOutputStream();
dataOut = new DataOutputStream(out);
}
public byte readByte() throws IOException {
return dataIn.readByte();
}
public int read() throws IOException {
return dataIn.read();
}
public short readShort() throws IOException {
return dataIn.readShort();
}
public int readInt() throws IOException {
return dataIn.readInt();
}
public long readLong() throws IOException {
return dataIn.readLong();
}
public float readFloat() throws IOException {
return dataIn.readFloat();
}
public double readDouble() throws IOException {
return dataIn.readDouble();
}
public String readUTF() throws IOException {
return dataIn.readUTF();
}
public void writeByte(int value) throws IOException {
dataOut.writeByte(value);
}
public void writeBoolean(boolean value) throws IOException {
dataOut.writeBoolean(value);
}
public void writeBytes(byte[] value) throws IOException {
dataOut.write(value);
}
public void writeShort(int value) throws IOException {
dataOut.writeShort(value);
}
public void writeInt(int value) throws IOException {
dataOut.writeInt(value);
}
public void writeLong(long value) throws IOException {
dataOut.writeLong(value);
}
public void writeFloat(float value) throws IOException {
dataOut.writeFloat(value);
}
public void writeDouble(double value) throws IOException {
dataOut.writeDouble(value);
}
public void writeUTF(String value) throws IOException {
int bufferSize = 2147483647;
int i = 0;
int sum = 0;
while (i < value.length()) {
int endIdx = java.lang.Math.min(value.length(), i + bufferSize);
String jsosPart = value.substring(i, endIdx);
dataOut.writeUTF(jsosPart);
sum += jsosPart.length();
i += bufferSize;
}
assert sum == value.length();
}
public byte[] toByteArray() {
return this.out.toByteArray();
}
/**
* 关闭缓冲读写数据流
*/
public void close() {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dataIn != null) {
try {
dataIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dataOut != null) {
try {
out.flush();
dataOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四、消息处理器 - 策略模式
通过上面说到,在入站消息完成解码工作后,开始对消息的消费,也就是我们的业务处理动作,此时我们需要区分消息,大量的if及switch显然并不优雅,所以这里是采取了策略模式。
MsgProcessor.java
package org.springblade.netty.business.dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.netty.protocol.GameSession;
import org.springblade.netty.protocol.request.ServerRequest;
/**
* 策略模式 - 消息处理
*/
public abstract class MsgProcessor {
private static final Logger logger = LoggerFactory.getLogger(MsgProcessor.class);
public void handle(GameSession gameSession, ServerRequest serverRequest) {
try {
process(gameSession, serverRequest);
} catch (Exception e) {
logger.error("消息处理出错 - msg code:" + serverRequest.getCode());
e.printStackTrace();
}
}
public abstract void process(GameSession gameSession, ServerRequest serverRequest) throws Exception;
}
接下来是注册器与分配器,根据消息code进行区分.
分配器
package org.springblade.netty.business.dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.netty.business.handle.INotAuthProcessor;
import org.springblade.netty.protocol.GameSession;
import org.springblade.netty.protocol.request.ServerRequest;
import java.util.HashMap;
import java.util.Map;
/**
* 消息分发器 根据消息号 找到相应的消息处理器
* @author xxx
*/
public class MsgDispatcher {
private static final Logger logger = LoggerFactory.getLogger(MsgDispatcher.class);
private Map<Integer, MsgProcessor> processorsMap = new HashMap<Integer, MsgProcessor>();
public MsgDispatcher() {
for (MsgProcessorRegister register : MsgProcessorRegister.values()) {
processorsMap.put(register.getMsgCode(), register.getProcessor());
}
logger.info("初始化 消息处理器成功 --- ");
}
/**
* 通过协议号得到MsgProcessor
* @param msgCode
* @return
*/
public MsgProcessor getMsgProcessor(int msgCode) {
return processorsMap.get(msgCode);
}
/**
* 派发消息协议
* @param gameSession
* @param serverRequest
*/
public void dispatchMsg(GameSession gameSession, ServerRequest serverRequest) {
MsgProcessor processor = getMsgProcessor(serverRequest.getCode());
// 是否登录判断
try {
if (true || processor instanceof INotAuthProcessor) {
processor.handle(gameSession, serverRequest);
}
} catch (NullPointerException e) {
logger.error("存在未实现的接口:接口码 = " + serverRequest.getCode());
e.printStackTrace();
}
}
}
注册器
package org.springblade.netty.business.dispatcher;
import lombok.Getter;
import org.springblade.netty.business.MsgCode;
import org.springblade.netty.business.handle.LoginMsgHandler;
/**
* 消息处理器注册类
* @author xxx
*/
@Getter
public enum MsgProcessorRegister {
/** 登陆处理器 */
login(MsgCode.CODE_1.getReq(), new LoginMsgHandler()),
;
private int msgCode;
private MsgProcessor processor;
/**
* 不允许外部创建
* @param msgCode
* @param processor
*/
private MsgProcessorRegister(int msgCode, MsgProcessor processor) {
this.msgCode = msgCode;
this.processor = processor;
}
}
另:关于消息码
package org.springblade.netty.business;
import lombok.Getter;
/**
* 消息码
* @author 李家民
*/
@Getter
public enum MsgCode {
////////////////////// 系统级请求响应从 1 开始 ////////////////////////////
/** 登陆处理器 */
CODE_1(1, 2),
////////////////////// 业务级请求响应从 100 开始 //////////////////////////
/** XXX */
CODE_100(101, 102),
///////////////////////////////// 广播消息从 300 开始 ////////////////////
/** XXX */
CODE_301(0, 301);
/** 请求码 */
private Integer req;
/** 响应码 */
private Integer resp;
MsgCode(Integer req_code, Integer resp_code) {
this.req = req_code;
this.resp = resp_code;
}
}
五、业务处理器
此时的业务处理器,需要实现上述的MsgProcessor,通过多态实现设计模式中的策略模式。
package org.springblade.netty.business.handle;
import org.springblade.core.tool.api.R;
import org.springblade.netty.business.MsgCode;
import org.springblade.netty.business.dispatcher.MsgProcessor;
import org.springblade.netty.protocol.GameSession;
import org.springblade.netty.protocol.request.ServerRequest;
import org.springblade.netty.protocol.response.SendResponse;
import java.util.HashMap;
import java.util.Map;
public class LoginMsgHandler extends MsgProcessor implements INotAuthProcessor {
@Override
public void process(GameSession gameSession, ServerRequest serverRequest) throws Exception {
// ...业务处理
Map<String, Object> map = new HashMap<>();
map.put("realName", "牛逼");
map.put("faceInformation", "");
map.put("isLogin", true);
map.put("msg", 1);//成功
map.put("user", null);
// ...
R loginInfo = R.data(map);
// 返回数据
// ...0x000002
gameSession.sendMessage(new SendResponse(200, loginInfo, MsgCode.CODE_1.getResp(), "测试我登录成功了"));
}
}
然而我们也需要有无需校验的接口标识
package org.springblade.netty.business.handle;
/**
* 标志接口 实现此处理器的processor不需要验证
* @author xxx
*/
public interface INotAuthProcessor {
}
六、消息的出站
完成业务代码后,将消息发送到对端的客户端,此时需要编码器的处理。
package org.springblade.netty.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.springblade.netty.protocol.response.ResponseMsg;
/**
* 自定义业务编码器
* @author 李家民
*/
public class BusinessMsgEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
// 引用计数处理 - 多出站处理器会导致资源过度释放
out.retain();
ResponseMsg valueMsg = (ResponseMsg) msg;
ctx.writeAndFlush(valueMsg.entireMsg(out));
// 资源释放
valueMsg.release();
}
}
七、Gitee地址
| 网页链接 |
|---|
| netty-server: 服务模块,通用于常规TCP/WebSocket等,后续将逐渐补充 (gitee.com) |
总结
我的工作中作为服务器开发经常要与各式各样的客户端进行对接,在本次的项目中,将apache mina框架完美平移到了Netty中,花了两天的时间,也算是收获颇多。