pom.xml
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.38.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.53</version>
</dependency>
</dependencies>
common
Decoder.java // 解码器
package org.example.common;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class Decoder extends LengthFieldBasedFrameDecoder {
public Decoder() {
super(Integer.MAX_VALUE, 0, 4, -4, 0);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
ByteBuf buffs = (ByteBuf) super.decode(ctx, buf);
if (buffs == null) {
return null;
}
try {
int len = buffs.readInt();
int msgId = buffs.readInt();
// ByteBuf转byte[]
byte[] bs = new byte[buffs.readableBytes()];
buffs.readBytes(bs);
String str = new String(bs);
return new GameMessage(msgId, str);
} finally {
buffs.release();
}
}
}
Encoder.java // 编码器
package org.example.common;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class Encoder extends MessageToByteEncoder<byte[]> {
@Override
protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception {
out.writeBytes(msg);
}
}
GameMessage.java // 消息体
package org.example.common;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
public class GameMessage implements Serializable {
private static final long serialVersionUID = 1L;
private int msgId;
private String str;
public GameMessage(int msgId, String str) {
this.msgId = msgId;
this.str = str;
}
}
SendUtils.java
package org.example.common;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import java.nio.charset.StandardCharsets;
public class SendUtils {
public static void sendMessage(Channel channel, GameMessage gameMessage) {
try {
byte[] bytes = gameMessage.getStr().getBytes(StandardCharsets.UTF_8);
// 要发送的数据
ByteBuf data = channel.alloc().buffer(8 + bytes.length);
data.writeInt(8 + bytes.length);
data.writeInt(gameMessage.getMsgId());
data.writeBytes(bytes);
channel.writeAndFlush(data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
server
GameServer.java // 游戏服启动类
package org.example.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.example.common.Decoder;
import org.example.common.Encoder;
public class GameServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 10240)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // 使用内存池
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // 使用内存池
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(128 * 1024, 256 * 1024))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new IdleStateHandler(5 * 60, 5 * 60, 0), new Decoder(), new Encoder(), new GatewayHandler());
}
});
Channel channel = bootstrap.bind(3000).sync().channel();
System.out.println("server start!!!");
channel.closeFuture().sync();
} catch (Exception e) {
System.exit(1);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
ClientSession.java
package org.example.server;
import io.netty.channel.Channel;
import lombok.Data;
import org.example.common.GameMessage;
import org.example.common.SendUtils;
@Data
public class ClientSession {
private Channel channel;
public ClientSession(Channel channel) {
this.channel = channel;
}
public void parseAndDispatchMessage(GameMessage gameMessage) throws Exception {
System.out.println( gameMessage.toString());
sendMsg(gameMessage);
}
public void sendMsg(GameMessage gameMessage) {
SendUtils.sendMessage(channel, gameMessage);
}
}
GatewayHandler.java // 解码后,进入服务器处理函数
package org.example.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.example.common.GameMessage;
public class GatewayHandler extends ChannelInboundHandlerAdapter {
private ClientSession session;
GatewayHandler() {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
if (msg == null) {
return;
}
GameMessage gameMessage = (GameMessage) msg;
session.parseAndDispatchMessage(gameMessage);
} finally {
ReferenceCountUtil.refCnt(msg);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
session = new ClientSession(ctx.channel());
}
}
client
Client.java
package org.example.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.example.common.Decoder;
import org.example.common.Encoder;
import java.net.InetSocketAddress;
public class Client {
public Client(){
}
public void connect() throws Exception{
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
bootstrap.group(workerGroup)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new Decoder(), new Encoder(), new ClientHandler());
}
});
bootstrap.connect((new InetSocketAddress("localhost", 3000))).sync();
}
}
ClientApp.java
package org.example.client;
public class ClientApp {
public static void main(String[] args) throws Exception {
Client client = new Client();
client.connect();
}
}
ClientHandler.java // 解码后,进入客户端处理函数
package org.example.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.example.common.GameMessage;
public class ClientHandler extends ChannelInboundHandlerAdapter {
private ServerSession session;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
session = new ServerSession(ctx.channel());
session.sendMsg(new GameMessage(1, "我是中国人"));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
GameMessage gameMessage = (GameMessage) msg;
System.out.println(gameMessage.toString());
}
}
ServerSession.java
package org.example.client;
import io.netty.channel.Channel;
import org.example.common.GameMessage;
import org.example.common.SendUtils;
public class ServerSession {
private Channel channel;
public ServerSession(Channel channel) {
this.channel = channel;
}
public void sendMsg(GameMessage gameMessage) {
SendUtils.sendMessage(channel, gameMessage);
}
}
运行结果:
总结:
这是工作中最常用的一种编解码器。数据包格式如下:
4字节 // 记录总长度
4字节 // 记录msgId
body // 消息题