【Netty】客户端和服务端实现双向通信

2019-09-04 10:08:49 浏览数 (1)

时间,犹如白驹过隙,一眨眼,国庆假期已经过去了四天了,日子总是像指间流过的细沙,在不经意间悄然滑落。所以,你该开始学Netty了

,今天开始学习如何实现服务端和客户端的双向通信,毕竟和聊天一样,要有来有回才有意思

1

客户端发送数据到服务端

上篇文章提到,读写处理逻辑是在 Bootstraphandler()方法指定的,上节课写的如下代码:

代码语言:javascript复制
.handler(new ChannelInitializer<Channel>() {
         @Override
         protected void initChannel(Channel channel) {
             // 指定数据读写处理逻辑 
             channel.pipeline().addLast(new StringEncoder());
         }
});

现在,我们自定义一段处理逻辑给它,如下:

代码语言:javascript复制
// 设置线程组
 bootstrap.group(group)
      // 设置线程模型
      .channel(NioSocketChannel.class)
      // 设置连接读写处理逻辑
      .handler(new ChannelInitializer<Channel>() {
              @Override
              protected void initChannel(Channel channel) {
                   // channel.pipeline() 责任链模式 返回和这条连接相关的逻辑处理链
                   // addLast 添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了
                    channel.pipeline().addLast(new CustomizeHandler());
               }
      });

上述代码中的

  • channel.pipeline()责任链模式 返回和这条连接相关的逻辑处理链
  • addLast() 添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了

其中,CustomizeHandler.java代码如下:

代码语言:javascript复制
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;
import java.util.Date;

/**
 * Created by zhoudl on 2018/10/4.
 */
public class CustomizeHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(new Date()   ": 客户端开始写数据");
        // 1. 获取数据
        ByteBuf buffer = getByteBuf(ctx);
        // 2. 写数据
        ctx.channel().writeAndFlush(buffer);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1. 获取二进制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2. 准备数据,指定字符串的字符集为 utf-8
        byte[] bytes = "你好,苍穹盛夏童鞋!".getBytes(Charset.forName("utf-8"));
        // 3. 填充数据到 ByteBuf
        buffer.writeBytes(bytes);
        return buffer;
    }

}

继承自ChannelInboundHandlerAdapterchannelActive()方法会在连接成功之后自动回调;

写数据的过程分为两部分:

  • 获取一个ByteBuf格式的二进制数据,这个结构是Netty对二进制数据做的抽象;
  • ctx.alloc()学过C/C 的人肯定知道 alloc 和内存相关,所以这行代码的意思是获取ByteBuff的内存管理器,而这个内存管理器的作用就是分配一个ByteBuff出来;
  • 填充数据到ByteBuff中,这样就达到了Netty传输数据的要求;
  • 使用ctx.channel().writeAndFlush(buffer);将数据写出到服务端。

上述代码和传统的Java Socket编程不同的一点就是写出的数据格式不同,Netty是自己对二进制数据做了一层抽象,定义了一个ByteBuff的结构出来,无论数据读还是写,Netty都只需要着这样的格式才行,下面开始学习服务端如何读取到这端数据。

2

服务端读取客户端数据

同理,服务端的读写处理逻辑处理还是在ServerBootstrapchildHandler()方法中,这里除了单词不同之外,其他和客户端同理,这就是Netty API 友好的体现方式之一,学了客户端,服务端猜也能猜个大概,所谓大胆猜测,小心验证。

代码语言:javascript复制
.childHandler(new ChannelInitializer<Channel>() {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        channel.pipeline().addLast(new StringDecoder());
        channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                System.out.println(msg);
            }
        });
    }
})

一样的,接下来我们自定义一个处理器出来,代码如下:

代码语言:javascript复制
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;
import java.util.Date;

/**
 * Created by zhoudl on 2018/10/4.
 */
public class CustomizeServerHandler 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")));
    }
}

同样的,你会发现,它继承了ChannelInboundHandlerAdapter,不同的在于这里是读数据,所以覆盖的方法变了,换成了read()方法,当客户端连接成功并发送数据之后这个方法被自动回调。

接下来开始学习服务端向客户端回应数据的过程,学完上边这俩之后,现在应该已经没什么难度了。

3

服务端向客户端回应数据

此处写数据和客户端写数据过程类似,我就不再赘述了,直接上代码,简单直接又明了,代码如下:

代码语言:javascript复制
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;
import java.util.Date;

/**
 * Created by zhoudl on 2018/10/4.
 */
public class CustomizeServerHandler 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")));
        // 回复数据到客户端
        System.out.println(new Date()   ": 服务端写出数据");
        ByteBuf out = getByteBuf(ctx);
        ctx.channel().writeAndFlush(out);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "你好,我是苍穹盛夏!".getBytes(Charset.forName("utf-8"));
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(bytes);
        return buffer;
    }

}

4

标题内容

紧接着,客户端需要读取服务端发过来的数据,而读取数据的过程和上述服务端读取客户端数据的代码无异,将以下代码添加到CustomizeHandler中,便能实现客户端读数据的逻辑,代码如下:

代码语言:javascript复制
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf byteBuf = (ByteBuf) msg;
    System.out.println(new Date()   ": 客户端读到数据 -> "   byteBuf.toString(Charset.forName("utf-8")));
}

4

总结

  • Netty数据交换传输的载体是ByteBuff,只有把数据填充到ByteBuff之后才能使得对端接收成功数据;
  • ByteBuff是通过连接的内存管理器创建的;
  • 写数据的方法是writeAndFlush;
  • 逻辑处理链`pipeline `,使用该方法添加逻辑处理器,具体的深层次原理以后再讨论,今天先学到这里。

关于ByteBuff的学习会单独写一篇文章,因为内容比较多!

以上代码会同步更新在本人的Github和CSDN上

Github地址:https://github.com/Bylant/LeetCode

0 人点赞