LOADING

加载过慢请开启缓存 浏览器默认开启

Netty案例:客户端与服务器之间通信

2023/6/25 网络编程

目标:Netty 开发一个网络应用程序,实现服务端和客户端之间的数据通信。
架构图:

  1. 导入依赖坐标
  2. 编写Netty服务端程序:配置线程组,配置自定义业务处理类,绑定端口号,然后启动Server,等待Client连接
  3. 编写服务端-业务处理类Handler:继承 ChannelInboundHandlerAdapter,并分别重写了三个方法
    1. 读取事件
    2. 读取完成事件
    3. 异常捕获事件
  4. 编写客户端程序:配置了线程组,配置了自定义的业务处理类,然后启动Client,连接Server。
  5. 编写客户端-业务处理类:继承 ChannelInboundHandlerAdapter ,并分别重写了2个方法
    1. 通道就绪事件
    2. 读取事件

1.导入依赖

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

上述代码在 pom 文件中引入了 netty 的坐标

2.服务端程序Server

public class NettyServer {
    public static void main(String[] args) throws Exception{
        //1. 创建一个线程组:接收客户端连接
        EventLoopGroup bossGroup =new NioEventLoopGroup();
        //2. 创建一个线程组:处理网络操作
        EventLoopGroup workerGroup =new NioEventLoopGroup();
        //3. 创建服务器端启动助手来配置参数
        ServerBootstrap serverBootstrap=new ServerBootstrap();

        serverBootstrap.group(bossGroup, workerGroup) //4.设置两个线程组
                .channel(NioServerSocketChannel.class) //5.使用NioServerSocketChannel作为服务器端通道的实现
                .option(ChannelOption.SO_BACKLOG, 128) //6.设置线程队列中等待连接的个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) //7.保持长连接
                .childHandler(new ChannelInitializer<SocketChannel>() {  //8. 创建一个通道初始化对象
                    public void initChannel(SocketChannel sc) {   //9. 往Pipeline链中添加自定义的handler类
                        sc.pipeline().addLast(new NettyServerHandler());
                    }
                });
        System.out.println("......服务端 启动中 init port:9999 ......");
        ChannelFuture cf=serverBootstrap.bind(9999).sync();  //10. 绑定端口 bind方法是异步的  sync方法是同步阻塞的
        System.out.println("......服务端 启动成功 ......");

        //11. 关闭通道,关闭线程组
        cf.channel().closeFuture().sync(); //异步
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

上述代码编写了一个服务端程序,配置了线程组,配置了自定义业务处理类,绑定端口号,然后启动
Server,等待Client连接。

3.服务端-业务处理类ServerHandler

//服务器端的业务处理类
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //读取数据事件,msg就是客戶端发过来的数据。
    public void channelRead(ChannelHandlerContext ctx,Object msg){
        //System.out.println("Server:"+ctx);
        //用缓冲区接受数据
        ByteBuf buf=(ByteBuf) msg;
        //转成字符串

        //必须编码和解码
        // 字符-->字节
        // 字节-->字符
        System.out.println("client msg:"+buf.toString(CharsetUtil.UTF_8));
    }

    //数据读取完毕事件,读取完客户端数据后回复客户端
    public void channelReadComplete(ChannelHandlerContext ctx){
        //Unpooled.copiedBuffer获取到缓冲区
        //第一个参数是向客户端传的字符串
        ByteBuf byteBuf = Unpooled.copiedBuffer("收到收到!我是服务端!", CharsetUtil.UTF_8);
        ctx.writeAndFlush(byteBuf);
    }

    //异常发生事件
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable t){
        //异常时关闭ctx,ctx是相关信息的汇总,关闭它其它的也就关闭了。
        ctx.close();
    }

}

上述代码定义了一个服务端业务处理类,继承 ChannelInboundHandlerAdapter,并分别重写了三个方
法。

4.客户端程序Client

public class NettyClient {
    public static void main(String[] args) throws  Exception{

        //1. 创建一个线程组
        EventLoopGroup group=new NioEventLoopGroup();
        //2. 创建客户端的启动助手,完成相关配置
        Bootstrap b=new Bootstrap();
        b.group(group)  //3. 设置线程组
         .channel(NioSocketChannel.class)  //4. 设置客户端通道的实现类
         .handler(new ChannelInitializer<SocketChannel>() {  //5. 创建一个通道初始化对象
             @Override
             protected void initChannel(SocketChannel socketChannel) throws Exception {
                 socketChannel.pipeline().addLast(new NettyClientHandler()); //6.往Pipeline链中添加自定义的handler
             }
         });
        System.out.println("......客户端 准备就绪 msg发射......");

        //7.启动客户端去连接服务器端  connect方法是异步的   sync方法是同步阻塞的
        ChannelFuture cf=b.connect("127.0.0.1",9999).sync();

        //8.关闭连接(异步非阻塞)
        cf.channel().closeFuture().sync();

    }
}

上述代码编写了一个Client程序,配置了线程组,配置了自定义的业务处理类,然后启动Client,连接
Server。

5.客户端-业务处理类ClientHandler

//客户端业务处理类
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //通道就绪事件
    public void channelActive(ChannelHandlerContext ctx){
        //System.out.println("Client:"+ctx);
        //编码和解码
        ByteBuf byteBuf = Unpooled.copiedBuffer("你好!你好!我是客户端!服务器听到请回复!", CharsetUtil.UTF_8);
        ctx.writeAndFlush(byteBuf);
    }

    //读取数据事件
    public void channelRead(ChannelHandlerContext ctx,Object msg){
        ByteBuf buf=(ByteBuf) msg;
        //字节 --> 字符串
        System.out.println("server msg:"+buf.toString(CharsetUtil.UTF_8));
    }

}

上述代码自定义了一个Client业务处理类,继承 ChannelInboundHandlerAdapter ,并分别重写了2个
方法

6.启动测试

  1. 启动服务端:NettyServer,服务器等待客户端消息,控制台打印:loading-ag-203
  2. 启动客户端:NettyClient,客户端连接服务端,收到来自服务端的信息,客户端控制台打印:loading-ag-205
  3. 此时服务端与客户端控制台:loading-ag-207

总结:这样就完成客户端与服务端之间的通信。下一步我们再做一个在线聊天室的案例。