根据netty创建简单的tcp服务器,需要自实现三个类,encoder(用于把对象解析为字节码),decoder(用于把字节码解析为对象), handler(业务逻辑处理)。
这里通过netty简单搭建一个自定义协议的tcp服务器。
encoder
public class MessageEncoder extends
MessageToByteEncoder<Package>{
@Override
public void encode(ChannelHandlerContext ctx, Package dataPackage, ByteBuf out)
throws Exception{
// 业务代码返回的package对象 取其中的数据部分(json格式),转为字节 返回到客户端
logger.debug("encode message");
out.writeBytes(dataPackage.data.getBytes("UTF-8"));
}
decoder 解码时首先读取一个整数,再读取一个换行符,把头部信息读取完毕后,读取包体部分。
public class MessageDecoder extends ByteToMessageDecoder{
private int LINE_MAX_LENGTH = 10;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf,
List<Object>out) throws Exception{
Package dataPackage = new Package();
//readLine();
readHeader(bytebuf, dataPackage);
byte[] bytes = new byte[dataPackage.length];
bytebuf.readBytes(bytes);
String data = new String(bytes, Charset.defaultCharset());
dataPackage.data = data;
logger.info("read data " + dataPackage.data);
out.add(dataPackage);
}
private void readHeader(ByteBuf bytebuf, Package dataPackage) {
logger.debug("read header");
dataPackage.version = readLine(bytebuf);
dataPackage.method = readLine(bytebuf);
dataPackage.flag = readLine(bytebuf);
dataPackage.machineId = readLine(bytebuf);
dataPackage.length = readLine(bytebuf);
dataPackage.date = readLine(bytebuf);
dataPackage.code = readLine(bytebuf);
logger.debug("length" + dataPackage.length);
}
private int readLine(ByteBuf buffer) {
int number = buffer.readInt();
buffer.readByte();
return number;
}
}
业务逻辑 此部分搬照博客 https://www.jianshu.com/p/9d89b2299ce4
public class LiveHandler extends SimpleChannelInboundHandler<Package> { // 1
private static Map<Integer, LiveChannelCache> channelCache = new HashMap<>();
private Logger logger = LoggerFactory.getLogger(LiveHandler.class);
public LiveHandler() {
super();
logger.debug("logic handler init");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Package msg) {
// context 传过来的数据 会调用此方法解析
logger.debug("get a connection");
Channel channel = ctx.channel();
final int hashCode = msg.machineId;
System.out.println("channel hashCode:" + hashCode + " msg:" + msg + " cache:" + channelCache.size());
if (!channelCache.containsKey(hashCode)) {
System.out.println("channelCache.containsKey(hashCode), put key:" + hashCode);
// 关闭连接时 需要把缓存中的项移除
channel.closeFuture().addListener(future -> {
logger.info("channel close, remove key:" + hashCode);
channelCache.remove(hashCode);
});
//future 用于判断客户端是否在线,定义一个5s/10s后执行的任务,如果中间有心跳包
//到达 则取消次任务 重新建立新的 如果没有则到时 判定客户端下线。
ScheduledFuture scheduledFuture = ctx.executor().schedule(
() -> {
System.out.println("schedule runs, close channel:" + hashCode);
channel.close();
}, 10, TimeUnit.SECONDS);
channelCache.put(hashCode, new LiveChannelCache(channel, scheduledFuture));
}
switch (msg.method) {
case 1: {
LiveChannelCache cache = channelCache.get(hashCode);
ScheduledFuture scheduledFuture = ctx.executor().schedule(
() -> channel.close(), 5, TimeUnit.SECONDS);
cache.getScheduledFuture().cancel(true);
cache.setScheduledFuture(scheduledFuture);
//ctx.channel().writeAndFlush(msg);
break;
}
case 2: {
logger.debug("method2");
DataHandler dh = new DataHandler();
Package rsp_pkg = dh.handle(msg);
//ctx.write(msg);
//ctx.flush();
//ctx.channel().writeAndFlush(rsp_pkg);
logger.debug("after message to context");
channelCache.entrySet().stream().forEach(entry -> {
Channel otherChannel = entry.getValue().getChannel();
otherChannel.writeAndFlush(rsp_pkg);
});
break;
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.debug("channelReadComplete");
super.channelReadComplete(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.debug("exceptionCaught");
if(null != cause) cause.printStackTrace();
if(null != ctx) ctx.close();
}
}
在main中
public class test {
private final int port;
private static Logger logger = LoggerFactory.getLogger(test.class);
public test(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception{
logger.debug("will start");
new test(8000).start();
}
public void start() throws Exception{
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
b.group(group).channel(NioServerSocketChannel.class);
b.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
public void initChannel(SocketChannel ch) throws Exception{
logger.debug("init channel" + ch);
ch.pipeline().addLast("decoder", new MessageDecoder())
.addLast("encoder", new MessageEncoder())
.addLast("handler", new LiveHandler());
}
});
b.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.bind(port).sync();
}
}