利用Netty构建自定义协议的通信

本文详细介绍如何使用Netty框架自定义应用层协议,包括创建协议Header和Content,以及实现编码器(Encoder)和解码器(Decoder)的过程。通过具体代码示例,展示如何在服务端和客户端进行协议的编解码操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如:HTTP,Telnet,FTP,SMTP等等。

在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优秀的网络通信框架,可以帮助我们完成自定义协议的通信。

一般而言,我们制定的协议需要两个部分:

  • Header : 协议头部,放置一些Meta信息。
  • Content : 应用之间交互的信息主体。

 

添加maven坐标(使用经典的netty4):

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.36.Final</version>
</dependency>

 

例如:

| Version | Content-Length | SessionName | Content |

其中Version,Content-Length,SessionName就是Header信息,Content就是交互的主体。给这个协议起一个名字叫做

MyProtocol,依照 MyProtocol 协议,我们构建一个类。
package com.onyx.rpc.orange.cat.netty;

// 消息的头部
public class MyProtocolHeader {

    // 协议版本
    private int version;
    // 消息内容长度
    private int contentLength;
    // 服务名称
    private String serverName;


    public MyProtocolHeader(int version, int contentLength, String serverName) {
        this.version = version;
        this.contentLength = contentLength;
        this.serverName = serverName;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public int getContentLength() {
        return contentLength;
    }

    public void setContentLength(int contentLength) {
        this.contentLength = contentLength;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }


    @Override
    public String toString() {
        return "MyProtocolHeader{" +
                "version=" + version +
                ", contentLength=" + contentLength +
                ", serverName='" + serverName + '\'' +
                '}';
    }
}

 

package com.onyx.rpc.orange.cat.netty;

/**
 * 消息的主体
 */
public class MyProtocolMessage {

    private MyProtocolHeader myProtocolHeader;
    private String content;

    public MyProtocolMessage(MyProtocolHeader myProtocolHeader, String content) {
        this.myProtocolHeader = myProtocolHeader;
        this.content = content;
    }

    public MyProtocolHeader getMyProtocolHeader() {
        return myProtocolHeader;
    }

    public void setMyProtocolHeader(MyProtocolHeader myProtocolHeader) {
        this.myProtocolHeader = myProtocolHeader;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "MyProtocolMessage{" +
                "myProtocolHeader=" + myProtocolHeader +
                ", content='" + content + '\'' +
                '}';
    }
}

那么我们在Netty中如何去对这种自定义的协议编码(Encode)呢?

Netty中对数据进行编码解码需要利用Codec组件,Codec组件中分为:

  • Encoder : 编码器,将出站的数据从一种格式转换成另外一种格式。
  • Decoder : 解码器,将入站的数据从一种格式转换成另外一种格式。
package com.onyx.rpc.orange.cat.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 *  编码器,将出站的数据从一种格式转换成另外一种格式。
 */
public class MyEncoder extends MessageToByteEncoder<MyProtocolMessage> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyProtocolMessage message, ByteBuf out) throws Exception {

        // 将Message转换成二进制数据
        MyProtocolHeader header = message.getMyProtocolHeader();

        // 这里写入的顺序就是协议的顺序.
        // 写入Header信息
        out.writeInt(header.getVersion());
        out.writeInt(message.getContent().length());
        out.writeBytes(header.getServerName().getBytes());

        // 写入消息主体信息
        out.writeBytes(message.getContent().getBytes());
    }
}
package com.onyx.rpc.orange.cat.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * 解码器,将入站的数据从一种格式转换成另外一种格式
 */
public class MyDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        // 获取协议的版本
        int version = in.readInt();
        // 获取消息长度
        int contentLength = in.readInt();
        // 获取SessionId
        byte[] sessionByte = new byte[36];
        in.readBytes(sessionByte);
        String name = new String(sessionByte);

        // 组装协议头
        MyProtocolHeader header = new MyProtocolHeader(version, contentLength, name);

        // 读取消息内容
        ByteBuf buf = in.readBytes(in.readableBytes());
        byte[] content = new byte[buf.readableBytes()];
        buf.readBytes(content);

        MyProtocolMessage message = new MyProtocolMessage(header, new String(content));
        out.add(message);
    }
}

编写一个服务端启动类:

package com.onyx.rpc.orange.cat.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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;

public class Server {

    // 指定端口号
    private static final int PORT = 8888;

    public static void main(String args[]) throws InterruptedException {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 指定socket的一些属性
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)  // 指定是一个NIO连接通道
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ServerInitializer());

            // 绑定对应的端口号,并启动开始监听端口上的连接
            Channel ch = serverBootstrap.bind(PORT).sync().channel();

            System.out.println("自定义协议启动地址:127.0.0.1:" + PORT);

            // 等待关闭,同步端口
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 

package com.onyx.rpc.orange.cat.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {

        ChannelPipeline pipeline = channel.pipeline();

        // 添加编解码器, 由于ByteToMessageDecoder的子类无法使用@Sharable注解,
        // 这里必须给每个Handler都添加一个独立的Decoder.
        pipeline.addLast(new MyEncoder());
        pipeline.addLast(new MyDecoder());

        // 添加逻辑控制层
        pipeline.addLast(new ServerHandler());

    }

    static class ServerHandler extends SimpleChannelInboundHandler<MyProtocolMessage> {

        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, MyProtocolMessage myProtocolMessage) throws Exception {
            // 简单地打印出server接收到的消息
            System.out.println(myProtocolMessage.toString());
        }
    }

}

最后一步了,.写一个客户端类:

package com.onyx.rpc.orange.cat.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.util.UUID;

public class Client {

    public static void main(String args[]) throws InterruptedException {

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
              .channel(NioSocketChannel.class)
              .handler(new ClientInitializer());

            // Start the connection attempt.
            Channel ch = b.connect("127.0.0.1", 8888).sync().channel();

            int version = 1;
            String name = UUID.randomUUID().toString();
            String content = "自定义的协议....哈哈";

            MyProtocolHeader header = new MyProtocolHeader(version, content.length(), name);
            MyProtocolMessage message = new MyProtocolMessage(header, content);
            ch.writeAndFlush(message);

            ch.close();
        } finally {
            group.shutdownGracefully();
        }
    }
}

 

package com.onyx.rpc.orange.cat.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {

        ChannelPipeline pipeline = channel.pipeline();

        // 这里必须给每个Handler都添加一个独立的Decoder.
        pipeline.addLast(new MyEncoder());
        pipeline.addLast(new MyDecoder());

        // and then business logic.
        pipeline.addLast(new ClientHandler());

    }


    static class ClientHandler extends SimpleChannelInboundHandler<MyProtocolMessage>{

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, MyProtocolMessage msg) throws Exception {
            System.out.println(msg);
        }
    }


}

 

依次启动server 和client就可以了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值