IO编程(四)Netty编程入门(服务端和客户端双向通信)

通过上篇文章已经分别对netty服务端和客户端有了简单的认识.

详情见:IO编程(三)Netty编程入门(服务端,客户端流程)

下面我们要实现客户端写数据到服务端,服务端读取数据同时写数据返回客户端

回到之前的客户端NettyClient

bootstrap.handler()中添加写数据的方法

        //处理IO逻辑
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                //获取管道(和这条连接相关的逻辑处理链)
                ChannelPipeline pipeline = nioSocketChannel.pipeline();
                //添加我们自己的逻辑处理器
                pipeline.addLast(new MyClientHandler());
            }
        });

创建我们自己的逻辑处理器

MyClientHandler

/**
 * @Author: xuxu
 * @Date: 2019/12/26 14:19
 */
public class MyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //ctx.alloc()获取到一个 ByteBuf 的内存管理器,这个 内存管理器的作用就是分配一个 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        String msg = new Date()+ "--->客户端写数据:你好!Netty!";
        System.out.println(msg);
        //填充数据
        buffer.writeBytes(msg.getBytes("UTF-8"));
        //写数据
        ctx.writeAndFlush(buffer);
    }
}
  1. 逻辑处理器继承自 ChannelInboundHandlerAdapter,然后覆盖了 channelActive()方法,这个方法会在客户端连接建立成功之后被调用
  2. 客户端连接建立成功之后,调用到 channelActive() 方法,在这个方法里面,我们编写向服务端写数据的逻辑
  3. 写数据的逻辑分为两步:首先我们需要获取一个 netty 对二进制数据的抽象 ByteBuf,上面代码中, ctx.alloc() 获取到一个 ByteBuf 的内存管理器,这个 内存管理器的作用就是分配一个 ByteBuf,然后我们把字符串的二进制数据填充到 ByteBuf,这样我们就获取到了 Netty 需要的一个数据格式,最后我们调用 ctx.channel().writeAndFlush() 把数据写到服务端

和传统的 socket 编程不同的是,Netty 里面数据是以 ByteBuf 为单位的, 所有需要写出的数据都必须塞到一个 ByteBuf,数据的写出是如此,数据的读取亦是如此

下面我们来编写服务端读取客户端数据的代码

NettyServer中我们知道服务端处理客户端连接数据是在serverBootstrap.childHandler()中

 //定义每条连接的数据读写,业务逻辑
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        System.out.println("处理当前连接数据");
                    }
                });

serverBootstrap.childHandler中添加

        //定义每条连接的数据读写,业务逻辑
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new MyServerHandler());
                    }
                });

我们创建MyServerHandler 同样继承ChannelInboundHandlerAdapter 但是这次重写channelRead方法,这个方法在接收到客户端发来的数据之后被回调。

/**
 * @Author: xuxu
 * @Date: 2019/12/26 14:42
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date()+":--->服务器读到数据:"+byteBuf.toString(Charset.forName("utf-8")));
    }
}

思考:这里的 msg 参数指的就是 Netty 里面数据读写的载体,为什么这里不直接是 ByteBuf,而需要我们强转一下?

我们先启动服务端,再启动客户端.

服务端控制台

客户端控制台

这样就成功实现了客户端写消息服务端读取消息.

接下来我们需要实现服务端回数据给客户端,同时客户端需要读消息

服务端MyServerHandler 读取客户端信息后 写数据给客户端

/**
 * @Author: xuxu
 * @Date: 2019/12/26 14:42
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date()+":--->服务器读到数据:"+byteBuf.toString(Charset.forName("utf-8")));

        String returnMsg = new Date()+ "--->服务端写数据:你好!客户端!";
        System.out.println(returnMsg);
        //要清空上一次填充的数据
        byteBuf.clear();
        byteBuf.writeBytes(returnMsg.getBytes("utf-8"));
        ctx.writeAndFlush(byteBuf);
    }
}

客户端在MyClientHandler中添加读数据的部分

/**
 * @Author: xuxu
 * @Date: 2019/12/26 14:19
 */
public class MyClientHandler extends ChannelInboundHandlerAdapter {
    //客户端写数据
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //ctx.alloc()获取到一个 ByteBuf 的内存管理器,这个 内存管理器的作用就是分配一个 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        String msg = new Date() + "--->客户端写数据:你好!Netty!";
        System.out.println(msg);
        //填充数据
        buffer.writeBytes(msg.getBytes("UTF-8"));
        //写数据
        ctx.writeAndFlush(buffer);
    }

    //客户端du读数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ":--->客户端读到数据:" + byteBuf.toString(Charset.forName("utf-8")));
    }
}

控制台结果

服务端

客户端

发现不管是服务端还是客户端,读写的流程基本上是一致的

1.客户端和服务端的逻辑处理是均是在启动的时候,通过给逻辑处理链 pipeline 添加逻辑处理器,来编写数据的读写逻辑

客户端连接成功之后会回调到逻辑处理器的 channelActive() 方法,而不管是服务端还是客户端,收到数据之后都会调用到 channelRead 方法。

写数据调用writeAndFlush方法,客户端与服务端交互的二进制数据载体为 ByteBufByteBuf 通过连接的内存管理器创建,字节数据填充到 ByteBuf 之后才能写到对端

发布了229 篇原创文章 · 获赞 49 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/baiyan3212/article/details/103714987