【Netty实战指南】10个案例带你轻松掌握基础技能

135 阅读3分钟

✨这里是第七人格的博客✨小七,欢迎您的到来~✨

🍅系列专栏:实战🍅

✈️本篇内容: Netty基础实战✈️

🍱本篇收录完整代码地址:gitee.com/diqirenge/a…🍱

楔子

学习一门技术,少不了看他的示例demo,本篇文章就以10个小例子,带你轻松掌握Netty开发的基础技能。

最简单的服务端例子

模块名称

demo-netty-001

仓库地址

gitee.com/diqirenge/a…

模块描述

最简单的服务端例子

代码实现

添加依赖

<dependencies>
    <!-- Netty4.1 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>${netty_version}</version>
    </dependency>
</dependencies>
<properties>
    <netty_version>4.1.36.Final</netty_version>
</properties>

编写NettyServer

/**
 * Netty服务端
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class NettyServer {

    public static void main(String[] args) {
        // 启动一个server,设置端口是7777
        new NettyServer().bing(7777);
    }

    private void bing(int port) {
        //配置服务端NIO线程组
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    // 非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());
            ChannelFuture f = b.bind(port).sync();
            System.out.println("服务端启动成功,端口是:" + port);
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            childGroup.shutdownGracefully();
            parentGroup.shutdownGracefully();
        }

    }

}

继承ChannelInitializer,实现自己的管道初始化器

/**
 * 管道初始化器
 * 用于初始化管道,在服务端链接建立时,会调用initChannel方法
 * <p>
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        System.out.println("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
                + ",服务端端口:" + channel.localAddress().getPort());
    }

}

测试

1、下载NetAssist网络调试工具

2、启动服务端,输出以下结果

服务端启动成功,端口是:7777

3、启动NetAssist网络调试工具

NetAssist.jpg

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接

控制台输出:

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777

增加服务端处理客户端信息

模块名称

demo-netty-002

仓库地址

gitee.com/diqirenge/a…

模块描述

增加服务端处理客户端信息

代码实现

继承ChannelInboundHandlerAdapter 实现channelRead方法

/**
 * Netty 服务端处理类
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        //接收msg消息
        ByteBuf buf = (ByteBuf) msg;
        byte[] msgByte = new byte[buf.readableBytes()];
        buf.readBytes(msgByte);
        System.out.print(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " [服务端]接收到[客户端]消息:");
        System.out.println(new String(msgByte, Charset.forName("GBK")));
    }

}

修改MyChannelInitializer,添加我们处理数据的方法

protected void initChannel(SocketChannel channel) {
    System.out.println("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
            + ",服务端端口:" + channel.localAddress().getPort());
    // 添加我们处理数据的方法
    channel.pipeline().addLast(new MyServerHandler());
}

测试

1、启动服务端,输出以下结果

服务端启动成功,端口是:7777

2、启动NetAssist网络调试工具

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接

控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777

(4)发送消息

关注公众号【奔跑的码畜】,一起进步不迷路

控制台输出

2023-11-08 14:52:16 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路

在管道中添加基于换行符的解码器

模块名称

demo-netty-003

仓库地址

gitee.com/diqirenge/a…

模块描述

在管道中添加基于换行符的解码器

代码实现

在initChannel方法中添加以下代码

// 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));

修改MyServerHandler

/**
 * Netty 服务端处理类
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("[管道激活时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
                + ",服务端端口:" + channel.localAddress().getPort());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " [服务端]接收到[客户端]消息:" + msg);
    }

}

测试

1、启动服务端,输出以下结果

服务端启动成功,端口是:7777

2、启动NetAssist网络调试工具

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接,控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777

(4)发送消息

关注公众号【奔跑的码畜】,一起进步不迷路

①不换行

控制台没有输出

②换行

控制台输出如下

2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路

添加断开链接以及异常处理

模块名称

demo-netty-004

仓库地址

gitee.com/diqirenge/a…

模块描述

添加断开链接以及异常处理

代码实现

在MyServerHandler中添加断开连接处理

@Override
public void channelInactive(ChannelHandlerContext ctx) {
    System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
            + " [客户端]断开链接" + "服务端[" + ctx.channel().localAddress().toString() + "]");
}

在MyServerHandler中添加异常处理

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    ctx.close();
    System.out.println("异常信息:\r\n" + cause.getMessage());
}

测试

1、启动服务端,输出以下结果

服务端启动成功,端口是:7777

2、启动NetAssist网络调试工具

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接,控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777

(4)发送消息(末尾换行)

关注公众号【奔跑的码畜】,一起进步不迷路

控制台输出如下

2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路

(5)点击断开

控制台输出

2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]

使用writeAndFlush,通知客户端消息

模块名称

demo-netty-005

仓库地址

gitee.com/diqirenge/a…

模块描述

使用writeAndFlush,通知客户端消息

代码实现

在MyServerHandler使用writeAndFlush,通知客户端消息

@Override
public void channelActive(ChannelHandlerContext ctx) {
    SocketChannel channel = (SocketChannel) ctx.channel();
    System.out.println("[管道激活时]监听到有一客户端链接到服务端...服务端IP:" + channel.localAddress().getHostString()
            + ",服务端端口:" + channel.localAddress().getPort());
    //通知客户端链接建立成功
    String str = "[管道激活时]通知客户端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
            + " "
            + channel.localAddress().getHostString() + "\r\n";
    ctx.writeAndFlush(str);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
    System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
            + " [服务端]接收到[客户端]消息:" + msg);
    //通知客户端链消息发送成功
    String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
            + " 服务端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
    ctx.writeAndFlush(str);
}

因为要通知客户端,所以我们在initChannel方法中添加编码器

// 编码
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));

测试

1、启动服务端,输出以下结果

服务端启动成功,端口是:7777

2、启动NetAssist网络调试工具

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接

①控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:169.254.232.1,服务端端口:7777

②NetAssist输出

【Receive from 169.254.232.1 : 7777】:[管道激活时]通知客户端链接建立成功 2023-11-08 15:00:57 169.254.232.1

(4)发送消息(末尾换行)

关注公众号【奔跑的码畜】,一起进步不迷路

①控制台输出

2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路

②NetAssist输出

2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]

(5)点击断开

控制台输出如下

2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]

添加管道组

模块名称

demo-netty-006

仓库地址

gitee.com/diqirenge/a…

模块描述

添加管道组

代码实现

添加管道处理类,放一个默认组就行了

/**
 * 管道处理类
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class ChannelHandler {

    // 自定义管道组
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

}

修改MyServerHandler

(1)channelActive

//当有客户端链接后,添加到channelGroup通信组
ChannelHandler.channelGroup.add(ctx.channel());

(2)channelRead

//收到消息后,群发给客户端
ChannelHandler.channelGroup.writeAndFlush(str);

(3)channelInactive

// 当有客户端退出后,从channelGroup中移除。
ChannelHandler.channelGroup.remove(ctx.channel());

测试

1、启动服务端,输出以下结果

服务端启动成功,端口是:7777

2、启动NetAssist网络调试工具

(1)协议类型选择TCP Client

(2)服务器端口填写7777

(3)点击连接

(4)重复以上步骤,再开一个客户端

(5)选择任意客户端发送消息(末尾换行)

关注公众号【奔跑的码畜】,一起进步不迷路

①控制台输出

2023-11-08 14:56:03 [服务端]接收到[客户端]消息:关注公众号【奔跑的码畜】,一起进步不迷路关注公众号【奔跑的码畜】,一起进步不迷路

②NetAssist客户端①输出

2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]

③NetAssist客户端②输出

2023-11-08 15:02:00 服务端已经接收到信息了,消息内容[关注公众号【奔跑的码畜】,一起进步不迷路]

(5)断开任意NetAssist客户端

控制台输出如下

2023-11-08 14:58:47 [客户端]断开链接服务端[/169.254.232.1:7777]

最简单的客户端例子

模块名称

demo-netty-007

仓库地址

gitee.com/diqirenge/a…

模块描述

最简单的客户端例子

代码实现

编写Netty客户端

/**
 * Netty客户端
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class NettyClient {

    public static void main(String[] args) {
        new NettyClient().connect("127.0.0.1", 7777);
    }

    private void connect(String inetHost, int inetPort) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.AUTO_READ, true);
            b.handler(new MyChannelInitializer());
            ChannelFuture f = b.connect(inetHost, inetPort).sync();
            System.out.println("客户端启动成功...");
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }

}

编写对应的管道初始化器

/**
 * 管道初始化器
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/07
 */
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        System.out.println("[初始化管道时]客户端链接到服务端。channelId:"+channel.id());
    }

}

测试

1、启动服务端

服务端控制台输出

服务端启动成功,端口是:7777

2、启动客户端NettyClient

①客户端控制台输出

[初始化管道时]客户端链接到服务端。channelId:bbcdec88
客户端启动成功...

②服务端控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777

3、关闭客户端NettyClient

服务端控制台输出

异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]

参考服务端编写客户端数据处理代码

模块名称

demo-netty-008

仓库地址

gitee.com/diqirenge/a…

模块描述

参考服务端编写客户端数据处理代码

代码实现

修改client下的MyChannelInitializer

/**
 * 管道初始化器
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/08
 */
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        System.out.println("[初始化管道时]客户端链接到服务端。channelId:" + channel.id());
        // 添加我们处理数据的方法
        // 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        // 解码转String,注意调整自己的编码格式GBK、UTF-8
        channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
        // 编码
        channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
        //在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyClientHandler());
    }

}

编写MyClientHandler

/**
 * Netty 客户端处理类
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/08
 */
public class MyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("[管道激活时]监听到有一客户端链接到服务端...channelId:" + channel.id());
        // 通知服务端链接建立成功
        String str = "[管道激活时]通知服务端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " "
                + channel.id() + "\r\n";
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " [客户端]接收到[服务端]消息:" + msg);
        //通知客户端链消息发送成功
        String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " 客户端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
        // 收到消息后,通知服务端
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " [客户端]断开链接" + ",channelId[" + ctx.channel().id().toString() + "]";
        System.out.println(str);
        // 收到消息后,通知服务端
        ctx.writeAndFlush(str);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }

}

测试

1、启动服务端

服务端控制台输出

服务端启动成功,端口是:7777

2、启动客户端NettyClient

①客户端控制台输出

[初始化管道时]客户端链接到服务端。channelId:5c8eae43
客户端启动成功...
[管道激活时]监听到有一客户端链接到服务端...channelId:5c8eae43

②服务端控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:21:07 [服务端]接收到[客户端]消息:[管道激活时]通知服务端链接建立成功 2023-11-08 15:21:07 5c8eae43

3、关闭客户端NettyClient

服务端控制台输出

异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]

ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter

模块名称

demo-netty-009

仓库地址

gitee.com/diqirenge/a…

模块描述

ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter

代码实现

编写入站消息处理

/**
 * 入站消息处理
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/08
 */
public class MyInMsgHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        SocketChannel channel = (SocketChannel) ctx.channel();
        System.out.println("[管道激活时]监听到有一客户端链接到服务端...channelId:" + channel.id());
        // 通知服务端链接建立成功
        String str = "[管道激活时]通知服务端链接建立成功" + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " "
                + channel.id() + "\r\n";
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " [客户端]接收到[服务端]消息:" + msg);
        //通知客户端链消息发送成功
        String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " 客户端已经接收到信息了,消息内容[" + msg + "]" + "\r\n";
        // 收到消息后,通知服务端
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        String str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + " [客户端]断开链接" + ",channelId[" + ctx.channel().id().toString() + "]";
        System.out.println(str);
        // 收到消息后,通知服务端
        ctx.writeAndFlush(str);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }

}

编写出站消息处理

/**
 * 出站消息处理
 * 关注公众号【奔跑的码畜】,一起进步不迷路
 *
 * @author 第七人格
 * @date 2023/11/08
 */
public class MyOutMsgHandler extends ChannelOutboundHandlerAdapter {

    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("触发[MyOutMsgHandler.read]方法\r\n");
        super.read(ctx);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ctx.writeAndFlush("触发[MyOutMsgHandler.write]方法\r\n");
        super.write(ctx, msg, promise);
    }

}

测试

1、启动服务端

服务端控制台输出

服务端启动成功,端口是:7777

2、启动客户端NettyClient

①客户端控制台输出

[初始化管道时]客户端链接到服务端。channelId:62e77898
客户端启动成功...
[管道激活时]监听到有一客户端链接到服务端...channelId:62e77898

②服务端控制台输出

[初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
[管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:24:03 [服务端]接收到[客户端]消息:[管道激活时]通知服务端链接建立成功 2023-11-08 15:24:03 62e77898
2023-11-08 15:24:03 [服务端]接收到[客户端]消息:触发[MyOutMsgHandler.read]方法

3、关闭客户端NettyClient

服务端控制台输出

异常信息:
远程主机强迫关闭了一个现有的连接。
2023-11-08 15:16:34 [客户端]断开链接服务端[/127.0.0.1:7777]

netty与springboot集成

模块名称

demo-netty-010

仓库地址

gitee.com/diqirenge/a…

模块描述

netty与springboot集成的例子

代码实现

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.0.4.RELEASE</version>
    </dependency>
</dependencies>

添加配置文件application.yml

server:
  port:8080

netty:
  host: 127.0.0.1
  port: 7777

编写客户端代码

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final Logger logger = LoggerFactory.getLogger(MyChannelInitializer.class);

    @Override
    protected void initChannel(SocketChannel channel) {
        logger.info("[初始化管道时]监听到有一客户端链接到服务端...服务端IP:{},服务端端口:{}", channel.localAddress().getHostString()
                , channel.localAddress().getPort());
        // 添加我们处理数据的方法
        // 基于换行符号,也就是说要在客户端换行,服务端才能接收到数据
        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        // 解码转String,注意调整自己的编码格式GBK、UTF-8
        channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
        // 编码
        channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
        //在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());
    }

}
public class MyServerHandler extends ChannelInboundHandlerAdapter {

    private final Logger logger = LoggerFactory.getLogger(MyServerHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        SocketChannel channel = (SocketChannel) ctx.channel();
        logger.info("[管道激活时]监听到有一客户端链接到服务端...服务端IP:{},服务端端口:{}", channel.localAddress().getHostString()
                , channel.localAddress().getPort());
        ctx.writeAndFlush("您好,欢迎来到【Netty】世界!\r\n");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这里我们已经不再需要自己手动进行解码了,因为Netty已经帮我们自动解码了
        logger.info("{} [服务端]接收到[客户端]消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                , msg);
        ctx.writeAndFlush(msg + "\r\n");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.info("{} [客户端]断开链接 服务端[{}}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                , ctx.channel().localAddress().toString());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
        System.out.println("异常信息:\r\n" + cause.getMessage());
    }
}
public class NettyServer {

    private final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    private final EventLoopGroup parentGroup = new NioEventLoopGroup();
    private final EventLoopGroup childGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture bing(InetSocketAddress address) {
        ChannelFuture channelFuture = null;
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)    //非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());

            channelFuture = b.bind(address).syncUninterruptibly();
            channel = channelFuture.channel();
        } catch (Exception e) {
            logger.error("服务端启动失败...{}", e.getMessage());
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                logger.info("服务端启动成功,端口是:{}", address.getPort());
            } else {
                logger.error("服务端启动失败...");
            }
        }
        return channelFuture;
    }

    public void destroy() {
        if (null == channel) {
            return;
        }
        channel.close();
        parentGroup.shutdownGracefully();
        childGroup.shutdownGracefully();
    }

    public Channel getChannel() {
        return channel;
    }

}

编写NettyController

@RestController
@RequestMapping(value = "/netty/server")
public class NettyController {

    @Resource
    private NettyServer nettyServer;

    @GetMapping("/getAddress")
    public String getAddress() {
        return "[服务端]地址是:" + nettyServer.getChannel().localAddress();
    }

}

编写启动类

@SpringBootApplication
public class NettyApplication implements CommandLineRunner {

    @Value("${netty.host}")
    private String host;
    @Value("${netty.port}")
    private int port;

    @Resource
    private NettyServer nettyServer;

    public static void main(String[] args) {
        SpringApplication.run(NettyApplication.class, args);
    }

    @Override
    public void run(String... args) {
        InetSocketAddress address = new InetSocketAddress(host, port);
        ChannelFuture channelFuture = nettyServer.bing(address);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));
        channelFuture.channel().closeFuture().syncUninterruptibly();
    }

}

测试

1、编写测试类

public class NettyClientTest {

    public static void main(String[] args) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.AUTO_READ, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel channel) {
                    // 基于换行符号
                    channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    // 解码转String,注意调整自己的编码格式GBK、UTF-8
                    channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
                    // 解码转String,注意调整自己的编码格式GBK、UTF-8
                    channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
                    // 在管道中添加我们自己的接收数据实现方法
                    channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 客户端接收到消息:" + msg);
                        }
                    });
                }
            });
            ChannelFuture f = b.connect("127.0.0.1", 7777).sync();
            // 向服务端发送信息
            f.channel().writeAndFlush("www.52javaee.com\r\n");
            f.channel().writeAndFlush("公众号:奔跑的码畜\r\n");
            f.channel().writeAndFlush("作者:第七人格\r\n");
            f.channel().writeAndFlush("向服务端发送信息:netty与springboot的集成例子\r\n");

            f.channel().closeFuture().syncUninterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }

}

2、启动NettyApplication

①服务端控制台输出

2023-11-08 15:26:16.825  INFO 5956 --- [           main] org.example.server.NettyServer           : 服务端启动成功,端口是:7777

3、启动client测试类

①client测试类控制台输出

2023-11-08 15:27:14 客户端接收到消息:您好,欢迎来到【Netty】世界!
2023-11-08 15:27:14 客户端接收到消息:www.52javaee.com
2023-11-08 15:27:14 客户端接收到消息:公众号:奔跑的码畜
2023-11-08 15:27:14 客户端接收到消息:作者:第七人格
2023-11-08 15:27:14 客户端接收到消息:向服务端发送信息:netty与springboot的集成例子

②服务端控制台输出

2023-11-08 15:27:14.816  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyChannelInitializer  : [初始化管道时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:27:14.821  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler       : [管道激活时]监听到有一客户端链接到服务端...服务端IP:127.0.0.1,服务端端口:7777
2023-11-08 15:27:14.843  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler       : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:www.52javaee.com
2023-11-08 15:27:14.843  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler       : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:公众号:奔跑的码畜
2023-11-08 15:27:14.844  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler       : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:作者:第七人格
2023-11-08 15:27:14.844  INFO 5956 --- [ntLoopGroup-3-1] org.example.server.MyServerHandler       : 2023-11-08 15:27:14 [服务端]接收到[客户端]消息:向服务端发送信息:netty与springboot的集成例子

4、执行http请求

### 获取服务端地址
GET http://localhost:8080/netty/server/getAddress

返回结果

[服务端]地址是:/127.0.0.1:7777