协议
- 帧头 header int类型 四个字节
- 长度 int类型 四个字节
- 内容 json字符串转byte[]
netty maven引入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.58.Final</version>
</dependency>
netty tcpserver 构建
public class NettyServer {
private final static Logger log = LoggerFactory.getLogger(NettyServer.class);
private static final int port = 8098;
private static class SingletionNettyServer {
static final NettyServer instance = new NettyServer();
}
public static NettyServer getInstance() {
return NettyServer.SingletionNettyServer.instance;
}
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private ServerBootstrap server;
private ChannelFuture f;
public NettyServer() {
bossGroup = new NioEventLoopGroup(); // 用来接收进来的连接
workerGroup = new NioEventLoopGroup();// 用来处理已经被接收的连接
server = new ServerBootstrap();//是一个启动NIO服务的辅助启动类
server.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 这里告诉Channel如何接收新的连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 自定义处理类
ch.pipeline().addLast(new SimpleChatServerInitializer());
}
});
server.option(ChannelOption.SO_BACKLOG, 128);
server.childOption(ChannelOption.SO_KEEPALIVE, true);
}
public void start() throws Exception {
//NioEventLoopGroup是用来处理IO操作的多线程事件循环器
try {
f = server.bind(port).sync();// 绑定端口,开始接收进来的连接
log.info("netty tcp server start success...");
} catch (Exception e) {
log.error("netty tcp server start error..." + e.getMessage());
}
}
}
public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyDecoder());
ch.pipeline().addLast(new NettyEncoder());
// 自定义处理类
ch.pipeline().addLast(new NettyServerHandler());
}
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private Log logger = LogFactory.getLog(NettyServerHandler.class);
private volatile static ConcurrentHashMap<String, ChannelHandlerContext> ipMap = new ConcurrentHashMap<>();
/**
* @return java.util.List<java.lang.String>
* @author zy
* @Description 获得所有的在线TCPClientIp;
* @time 2021/12/17 9:45
* @Param []
**/
public static Map<String, ArrayList> getAllOnlineTcpClient() {
HashMap<String, ArrayList> map = new HashMap<>();
ArrayList<String> list = new ArrayList<>(ipMap.keySet());
map.put("ipList", list);
return map;
}
//收到数据时调用
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
EncoderOrDecoderVo in = (EncoderOrDecoderVo) msg;
AjaxResult dto = JSONObject.parseObject(in.getContent(), AjaxResult.class);
System.out.println(dto);
int code = (int) dto.get("code");
switch (code) {
case 0:
// 心跳
System.out.println(DateUtil.format.format(new Date()) + " 客户端:[" + ctx.channel().remoteAddress().toString() + "]心跳消息");
ReferenceCountUtil.release(msg);
break;
case 1:
// 获取在线客户端
ctx.channel().writeAndFlush(getOnlineDeveice());
ReferenceCountUtil.release(msg);
break;
default:
if (dto.containsKey("data")) {
String data = dto.get("data").toString();
analysisData(in, data, ctx);
}
}
} catch (Exception e) {
logger.error("接收数据解析时出现错误" + e.getMessage());
sendErrorMessage(ctx, "接收数据解析时出现错误");
} finally {
// 抛弃收到的数据
ReferenceCountUtil.release(msg);
}
}
/*
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIP = insocket.getAddress().getHostAddress();
logger.info("连接的客户端地址:【" + clientIP + "】连接的客户端ID:【" + ctx.channel().id() + "】");
logger.info("" + ctx.channel().id());
// 先移除后加入
ipMap.remove(clientIP);
ipMap.put(clientIP, ctx);
EncoderOrDecoderVo encoderOrDecoderVo = new EncoderOrDecoderVo(AjaxResult.success());
ctx.writeAndFlush(encoderOrDecoderVo);
super.channelActive(ctx);
}
}
编解码相关
@Data
public class EncoderOrDecoderVo {
// 协议头
private int header = NettyConstant.hreader;
// 数据长度
private int length ;
// 数据内容
private byte[] content;
public EncoderOrDecoderVo(int length , byte[] content){
this.length = length;
this.content = content;
}
public EncoderOrDecoderVo(AjaxResult ajaxResult){
String string = JSON.toJSONString(ajaxResult);
byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
this.length = bytes.length;
this.content = bytes;
}
}
解码
public class NettyDecoder extends ByteToMessageDecoder {
/**
* <pre>
* 协议开始的标准,int类型,占据4个字节.
* 表示数据的长度,int类型,占据4个字节.
* </pre>
*/
public final int BASE_LENGTH = 4 + 4;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
// 可读长度必须大于基本长度
if (buffer.readableBytes() >= BASE_LENGTH) {
// 防止socket字节流攻击
// 防止,客户端传来的数据过大
// 因为,太大的数据,是不合理的
if (buffer.readableBytes() > 2048) {
buffer.skipBytes(buffer.readableBytes());
}
// 记录包头开始的index
int beginReader;
while (true) {
// 获取包头开始的index
beginReader = buffer.readerIndex();
// 标记包头开始的index
buffer.markReaderIndex();
// 读到了协议的开始标志,结束while循环
if (buffer.readByte() == NettyConstant.hreader) {
break;
}
// 未读到包头,略过一个字节
// 每次略过,一个字节,去读取,包头信息的开始标记
buffer.resetReaderIndex();
buffer.readByte();
// 当略过,一个字节之后,
// 数据包的长度,又变得不满足
// 此时,应该结束。等待后面的数据到达
if (buffer.readableBytes() < BASE_LENGTH) {
return;
}
}
// 消息的长度
int length = buffer.readInt();
// 判断请求数据包数据是否到齐
if (buffer.readableBytes() < length) {
// 还原读指针
buffer.readerIndex(beginReader);
return;
}
// 读取data数据
byte[] data = new byte[length];
buffer.readBytes(data);
EncoderOrDecoderVo protocol = new EncoderOrDecoderVo(data.length, data);
out.add(protocol);
}
}
}
编码
public class NettyEncoder extends MessageToByteEncoder<EncoderOrDecoderVo> {
@Override
protected void encode(ChannelHandlerContext tcx, EncoderOrDecoderVo msg,
ByteBuf out) throws Exception {
// 写入消息的具体内容
// 1.写入消息的开头的信息标志(String)
out.writeInt(msg.getHeader());
// 2.写入消息的长度(int 类型)
out.writeInt(msg.getLength());
// 3.写入消息的内容(byte[]类型)
out.writeBytes(msg.getContent());
}
}
启动
public static void main(String[] args) {
NettyServer.getInstance().start();
}
测试内容,收到连接后服务端主动向连接的客户端发送数据。
{“msg”:“操作成功”,“code”:1000}