Java Netty 服务端向客户端发送16进制数据

放假前夕,接手一个不太熟悉的任务,不过好在用的东西,比较熟,就是netty通讯。具体遇到什么问题嘞,我们来看一下。

netty服务端可以接收消息,但是不能正确的发送消息给客户端,最开始看到的时候,没有注意到,会是编码问题,具体我们来看一下吧。

在写的过程中,看到这篇文章,我才意识到,我可能被同事已有的代码误导了:

 这里比较郁闷的是,人家没有加编码,而我这边是,加了编码,初看 没意识到。然后在解码器里边去处理了接收的消息,还想发送消息出去。

这里就不给大家看没改之前的,直接上正确代码。

启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
public class NettyServer {
 
    private static class SingletionWSServer {
        static final NettyServer instance = new NettyServer();
    }
 
    public static NettyServer getInstance() {
        return SingletionWSServer.instance;
    }
 
    private EventLoopGroup mainGroup;
    private EventLoopGroup subGroup;
    private ServerBootstrap server;
    private ChannelFuture future;
 
    public NettyServer() {
        // 主线程组
        mainGroup = new NioEventLoopGroup();
 
        // 子线程组
        subGroup = new NioEventLoopGroup();
        // netty服务器的创建,ServerBootstrap是一个启动类
        server = new ServerBootstrap();
        server.group(mainGroup, subGroup)// 设置主从线程组
                .option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO))
                .channel(NioServerSocketChannel.class)// 设置nio双向通道
                .childHandler(new NettyServerInitializer());// 子处理器,用于处理subGroup
    }
 
    /**
     * 启动
     */
    public void bind(int port) {
        try {
            this.future = server.bind(port).sync();
            System.err.println("netty websocket server 启动完毕...");
            log.info("netty websocket server 启动完毕...");
            this.future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.err.println("netty websocket server 启动异常..." + e.getMessage());
            log.debug("netty websocket server 启动异常..." + e.getMessage());
        }
    }
}

NettyServerInitializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import com.slife.netty.coder.NettyMessageDecoder;
import com.slife.netty.coder.NettyMessageEncoder;
import com.slife.netty.handler.HeartBeatHandler;
import com.slife.netty.handler.NettyServerHandler;
 
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
 
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        log.info("初始化 SocketChannel");
 
        ChannelPipeline pipeline = ch.pipeline();
 
        // 自定义解码器
        pipeline.addLast(new NettyMessageDecoder());
 
        // 自定义编码器
        pipeline.addLast(new NettyMessageEncoder());
 
        // 自定义的空闲检测
        pipeline.addLast(new HeartBeatHandler());
        // ========================增加心跳支持 end ========================
 
        /**
         *
         * @param maxFrameLength
         *            帧的最大长度
         * @param lengthFieldOffset
         *            length字段偏移的地址
         * @param lengthFieldLength
         *            length字段所占的字节长
         * @param lengthAdjustment
         *            修改帧数据长度字段中定义的值,可以为负数 因为有时候我们习惯把头部记入长度,若为负数,则说明要推后多少个字段
         * @param initialBytesToStrip
         *            解析时候跳过多少个长度
         * @param failFast
         *            为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异
         */
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024 * 1024, 4, 4, 2, 0));
 
        // 自定义hanler 处理解码消息并回复信息
        pipeline.addLast(new NettyServerHandler());
    }
 
}

这里需要解析一下的是这个类,LengthFieldBasedFrameDecoder,上述代码的注解是翻译过来的,定义的参数值,大家要依据自己的实际情况去设置。

监控:HeartBeatHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
 
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {
 
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
 
        // 判断evt是否是IdleStateEvent(用于触发用户事件,包含 读空闲/写空闲/读写空闲)
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt; // 强制类型转换
 
            if (event.state() == IdleState.READER_IDLE) {
                System.out.println("进入读空闲...");
            } else if (event.state() == IdleState.WRITER_IDLE) {
                System.out.println("进入写空闲...");
            } else if (event.state() == IdleState.ALL_IDLE) {
                System.out.println("channel关闭前channelGroup数量为:"+ NettyServerHandler.channelGroup.size());
                System.out.println("进入读写空闲...");
                Channel channel = ctx.channel();
                //关闭无用的channel,以防资源浪费
                channel.close();
                System.out.println("channel关闭后channelGroup数量为:"+ NettyServerHandler.channelGroup.size());
            }
        }
    }
}

解码器:NettyMessageDecoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.List;
 
import com.netty.constant.Delimiter;
import com.netty.pojo.GpsMessage;
import com.netty.pojo.LoginMsg;
import com.utils.CrcUtils;
 
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
 
public class NettyMessageDecoder extends ByteToMessageDecoder {
 
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("开始解码:");
        int length = in.readableBytes();
        if (length < Delimiter.MINIMUM_LENGTH)
            return;
        in.markReaderIndex(); // 我们标记一下当前的readIndex的位置
 
        // 解码后消息对象
        GpsMessage gpsMessage = new GpsMessage();
        byte packetLen = in.readByte();
        int nPacketLen = packetLen & 0xff;
        gpsMessage.setPacketLen(nPacketLen);
        /**
         * 协议
         */
        byte agreement = in.readByte();
        gpsMessage.setAgreement(agreement);
        ByteBuf frame = null;
        if (agreement == Delimiter.LOGIN_PACKET) { // 登录包
            LoginMsg loginMsg = new LoginMsg();
            frame = CrcUtils.decodeCodeIDFrame(ctx, in);
            String sCode = CrcUtils.bytesToHexString(frame);
            System.out.println("编号:" + sCode);
            loginMsg.setCardId(sCode);
            gpsMessage.setContent(loginMsg);
        } else if (agreement == Delimiter.STATUS_PACKET) {// 心跳包
            System.out.println(" 心跳包:");
            frame = CrcUtils.decodeCodeIDFrame(ctx, in);
            String sContent = CrcUtils.bytesToHexString(frame);
            System.out.println("心跳包内容:" + sContent);
            gpsMessage.setContent(sContent);
        }
        out.add(gpsMessage);
        System.out.println("解码结束!");
    }
 
}

编码器:NettyMessageEncoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.netty.pojo.GpsMessage;
 
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
 
public class NettyMessageEncoder extends MessageToByteEncoder<GpsMessage> {
 
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, GpsMessage gpsMessage, ByteBuf byteBuf) throws Exception {
        // 2、写入数据包长度
        byteBuf.writeInt(gpsMessage.getPacketLen());
 
        // 3、写入请求类型
        byteBuf.writeByte(gpsMessage.getAgreement());
 
        // 4、写入预留字段
        //byteBuf.writeByte(nettyMessage.getHeader().getReserved());
 
        // 5、写入数据
        byteBuf.writeBytes(gpsMessage.getContent().toString().getBytes());
    }
}

处理消息的handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import org.springframework.util.StringUtils;
 
import com.netty.channel.CardChannelRel;
import com.netty.constant.Delimiter;
import com.netty.pojo.GpsMessage;
import com.netty.pojo.LoginMsg;
import com.utils.ConvertCode;
import com.utils.CrcUtils;
 
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
 
    // 用于记录和管理所有客户端的channel
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 
        System.out.println("处理消息的handler:" + msg);
        Channel currentChannel = ctx.channel();
        // 1. 获取客户端发送的消息
        GpsMessage gpsMessage = (GpsMessage) msg;
        if (gpsMessage != null) {
             
            // 协议
            byte agreement = gpsMessage.getAgreement();
 
            String cardId = "";
            if (agreement == Delimiter.LOGIN_PACKET) { // 登录包
 
                LoginMsg loginMsg = (LoginMsg) gpsMessage.getContent();
                cardId = loginMsg.getCardId();
                CardChannelRel.put(cardId, currentChannel);
                String sReply = "回复";
                System.out.println(" 回复包:" + sReply);
                CardChannelRel.output();
                // 发送消息
                writeToClient(sReply, currentChannel, "登录回复");
            } else if (agreement == Delimiter.STATUS_PACKET) {// 心跳包
                System.out.print("心跳包:");
                String receiveStr = (String) gpsMessage.getContent();
                System.out.println("心跳包内容:" + receiveStr);
                writeToClient(receiveStr, currentChannel, "心跳包回复");
            } else {
                // 发送消息
                // 从全局用户channel关系中获取接受方的channel
                Channel receiverChannel = CardChannelRel.get(cardId);
                if (receiverChannel != null) {
                    // 当receiverChannel不为空的时候,从 ChannelGroup 去查找对应的channel是否存在
                    Channel findChannel = channelGroup.find(receiverChannel.id());
                    if (findChannel != null) {
                        // 用户在线
                        writeToClient("其他消息", currentChannel, "其他消息回复");
                    }
                }
            }
         
        }
    }
 
    /**
     * 公用回写数据到客户端的方法
     *
     * @param 需要回写的字符串
     * @param receiverChannel
     * @param mark
     *            用于打印/log的输出 <br>
     *            //channel.writeAndFlush(msg);//不行 <br>
     *            //channel.writeAndFlush(receiveStr.getBytes());//不行 <br>
     *            在netty里,进出的都是ByteBuf,楼主应确定服务端是否有对应的编码器,将字符串转化为ByteBuf
     */
    public void writeToClient(final String receiveStr, Channel receiverChannel, final String mark) {
        try {
            ByteBuf byteValue = Unpooled.buffer();// netty需要用ByteBuf传输
            byteValue.writeBytes(ConvertCode.hexString2Bytes(receiveStr));// 对接需要16进制
            receiverChannel.writeAndFlush(byteValue).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    StringBuilder sb = new StringBuilder("");
                    if (!StringUtils.isEmpty(mark)) {
                        sb.append("【").append(mark).append("】");
                    }
                    if (future.isSuccess()) {
                        System.out.println(sb.toString() + "回写成功" + byteValue);
                        log.info(sb.toString() + "回写成功" + byteValue);
                    } else {
                        System.out.println(sb.toString() + "回写失败" + byteValue);
                        log.error(sb.toString() + "回写失败" + byteValue);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("调用通用writeToClient()异常" + e.getMessage());
            log.error("调用通用writeToClient()异常:", e);
        }
    }
 
    /**
     * 当客户连接服务端之后(打开链接) 获取客户端的channel,并且放到ChannelGroup中去进行管理
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        channelGroup.add(ctx.channel());
    }
 
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
        String channelId = ctx.channel().id().asLongText();
        System.out.println("客户端被移除,channelId为:" + channelId);
        // 当触发handlerRemoved,ChannelGroup会自动移除对应的客户端channel
        channelGroup.remove(ctx.channel());
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 发生异常之后关键channel。随后从ChannelGroup 中移除
        ctx.channel().close();
        channelGroup.remove(ctx.channel());
    }
 
}

上述类中:Delimiter为自定义的消息类型,大家可根据自己十六进制去定义响应不用的消息类型

CardChannelRel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.HashMap;
 
import io.netty.channel.Channel;
 
/**
 * 用户id和channel的关联关系处理
 */
public class CardChannelRel {
 
    private static HashMap<String, Channel> manager = new HashMap<>();
 
    public static void put(String senderId, Channel channel) {
        manager.put(senderId, channel);
    }
 
    public static Channel get(String senderId) {
        return manager.get(senderId);
    }
     
     
    public static void output() {
        for (HashMap.Entry<String, Channel> entry : manager.entrySet()) {
            System.out.println("CredId:" + entry.getKey() +
                    ",ChannelId:" + entry.getValue().id().asLongText());
        }
    }
} 

效果:

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
 
public class CrcUtils {
    public static String CRC_16(byte[] bytes) {
        int i, j, lsb;
        int h = 0xffff;
        for (i = 0; i < bytes.length; i++) {
            h ^= bytes[i];
            for (j = 0; j < 8; j++) {
                lsb = h & 0x0001; // 取 CRC 的移出位
                h >>= 1;
                if (lsb == 1) {
                    h ^= 0x8408;
                }
            }
        }
        h ^= 0xffff;
        return Integer.toHexString(h).toUpperCase();
    }
 
    public static byte[] hexStringToByte(String hex) {
        int len = (hex.length() / 2);
        byte[] result = new byte[len];
        char[] achar = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
        }
        return result;
    }
 
    private static byte toByte(char c) {
        byte b = (byte) "0123456789ABCDEF".indexOf(c);
        return b;
    }
 
    public static String bytesToHexString(ByteBuf buffer) {
        final int length = buffer.readableBytes();
        StringBuffer sb = new StringBuffer(length);
        String sTmp;
 
        for (int i = 0; i < length; i++) {
            byte b = buffer.readByte();
            sTmp = Integer.toHexString(0xFF & b);
            if (sTmp.length() < 2)
                sb.append(0);
            sb.append(sTmp.toUpperCase());
        }
        return sb.toString();
    }
 
}

参考文章:

1.https://blog.csdn.net/qq_42599616/article/details/105459117?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduend~default-3-105459117.nonecase&utm_term=netty%E8%BF%9E%E6%8E%A5%E6%88%90%E5%8A%9F%E4%B8%8D%E8%83%BD%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE&spm=1000.2123.3001.4430

2.https://github.com/bjmashibing/tank/commit/1121deccf76786b634389629454a0ec0af80765f

3.https://blog.csdn.net/linsongbin1/article/details/77915686?utm_source=blogxgwz2

4.https://blog.csdn.net/yqwang75457/article/details/73913572

posted @   徐徐图之  阅读(7822)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示