netty 构建TcpServer

协议

  • 帧头 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}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值