Netty(二):简单客户端和服务端通信

客户端

NettyClient

public class NettyClient {

  private final static Logger LOGGER = LoggerFactory.getLogger(NettyClient.class);

  public static void main(String[] args) throws InterruptedException {
    // 首先,netty通过Bootstrap启动客户端,服务端一般使用ServerBootstrap
    Bootstrap client = new Bootstrap();

    //第1步 定义线程组,处理读写和链接事件,没有了accept事件
    EventLoopGroup group = new NioEventLoopGroup();
    client.group(group);

    //第2步 绑定客户端通道
    client.channel(NioSocketChannel.class);

    //第3步 给NIoSocketChannel初始化handler, 处理读写事件
    client.handler(new ChannelInitializer() {  //通道是NioSocketChannel
        @Override
        protected void initChannel(NioSocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            //字符串编码器,一定要加在SimpleClientHandler 的上面
            pipeline.addLast(new StringEncoder());
            pipeline.addLast(new DelimiterBasedFrameDecoder(
                    Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
            //找到他的管道 增加他的handler
            pipeline.addLast(new SimpleClientHandler());
        }
    });

    //连接服务器
    ChannelFuture future = client.connect("localhost", 8080).sync();
    Channel channel =  future.channel();
    //代码分析的很复杂,但结论很简单:被Bootstrap引导的NioSocketChannel在构造好之后就进入了open状态,之后通过把自己注册进EventLoop进入registered状态,接着连接服务器进入active状态。
    // channel.isOpen() --> channel.isRegistered() -->channel.isActive()
    future.addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture channelFuture) throws Exception {
            if (channelFuture.isSuccess()){
                LOGGER.info("---客户端启动成功,连接的是===port:"+8080);
                //发送数据给服务器
                StationInforationCheck user = new StationInforationCheck();
                user.setAcStakemanufactor("李静");
                user.setAcStakeNum(12);
                user.setDcStakemanufactor("LOL");
                //发送给客户端信息
                channel.writeAndFlush(JSONObject.toJSONString(user)+"\r\n");
                for(int i=0;i<5;i++){
                    //这里发送的收必须是换行符之类的,不然不能编码和解码
                    String msg = "ssss"+i+"\r\n";
                    channel.writeAndFlush(msg);
                }
            }else {
                Throwable cause = future.cause();
                cause.printStackTrace();
            }
        }
    });
    //当通道关闭了,就继续往下走
    channel.closeFuture().sync();

    //接收服务端返回的数据
    AttributeKey key = AttributeKey.valueOf("ServerData");
    Object result = channel.attr(key).get();
    System.out.println(result.toString());
  }
}

SimpleClientHandler:clientd的业务处理类

public class SimpleClientHandler  extends ChannelInboundHandlerAdapter {

    public NettyClient nettyClient;

    //向新的连接的发送连接问候
    //当一个新的连接已经被建立时,channelActive(ChannelHandlerContext)将会被调用,随后才调用channelRead去读取
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // Send greeting for a new connection.
        //InetAddress.getLocalHost().getHostName() 获取电脑主机名
        ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
        ctx.write("It is " + new Date() + " now.\r\n");
        ctx.flush();
    }

    //channelRead 读取远端消息的
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            String value = ((ByteBuf) msg).toString(Charset.defaultCharset());
            System.out.println("服务器端返回的数据:" + value);
        }

        AttributeKey key = AttributeKey.valueOf("ServerData");
        ctx.channel().attr(key).set("客户端处理完毕");

        //把客户端的通道关闭
        ctx.channel().close();
    }

    //channel关闭后触发这个方法
    //这有两种可能,一种服务端主动 close,还有客户端 colse,你的 handler 里重写捕获异常了吗,如果没有捕获异常,则操作此 channel 的任何异常都会关闭此 channel
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("失败断开连接触发channelInactive方法==========");
        closeChannelAndReConnect(ctx);
    }

    //在读取操作期间,有异常抛出时会调用。
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }

    //通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息,此时会调用
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("这是读取的最后一条数据================================================================================================");
    }

    public void closeChannelAndReConnect(ChannelHandlerContext ctx){
        Channel channel = ctx.channel();
        if (channel !=null){
            channel.close();
            ctx.close();
        }
        //触发重连
    }
}

服务端

NettyServer

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
    // 首先,netty通过ServerBootstrap启动服务端,netty通过Bootstrap启动客户端
    ServerBootstrap server = new ServerBootstrap();
    EventLoopGroup parentGroup = new NioEventLoopGroup();
    EventLoopGroup childGroup =new NioEventLoopGroup();
    //第1步定义两个线程组,用来处理客户端通道的accept和读写事件
    //parentGroup用来处理accept事件,childgroup用来处理通道的读写事件
    //parentGroup获取客户端连接,连接接收到之后再将连接转发给childgroup去处理
    server.group(parentGroup, childGroup);

    //用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
    //用来初始化服务端可连接队列
    //服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
    server.option(ChannelOption.SO_BACKLOG, 128);

    //第2步绑定服务端通道
    server.channel(NioServerSocketChannel.class);

    //第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
    server.childHandler(new ChannelInitializer() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            //解码器,接收的数据进行解码,一定要加在SimpleServerHandler 的上面
            //maxFrameLength表示这一贞最大的大小
            //delimiter表示分隔符,我们需要先将分割符写入到ByteBuf中,然后当做参数传入;
            //需要注意的是,netty并没有提供一个DelimiterBasedFrameDecoder对应的编码器实现(笔者没有找到),因此在发送端需要自行编码添加分隔符,如 \r \n分隔符
            pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
            //把传过来的数据 转换成byteBuf
            pipeline.addLast(new SimpleServerHandler());
        }
    });
    //第4步绑定8080端口
    ChannelFuture future = server.bind(8080).sync();
    //当通道关闭了,就继续往下走
    future.channel().closeFuture().sync();
  }
}


SimpleServerHandler :服务端业务处理类

public class SimpleServerHandler extends ChannelInboundHandlerAdapter {

 /**
 * 读取客户端通道的数据   //channelRead 读取远端消息的
 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //可以在这里面写一套类似SpringMVC的框架
    //让SimpleServerHandler不跟任何业务有关,可以封装一套框架
    if(msg instanceof ByteBuf){
        System.out.println(((ByteBuf)msg).toString(Charset.defaultCharset()));
    }

    //业务逻辑代码处理框架。。。

    //返回给客户端的数据,告诉客户端已经读到服务端数据了
    String result = "hello client ";
    //ByteBuf 用于存入数据和从里面读取数据,相当于申请了一块内存。
    ByteBuf buf = Unpooled.buffer();
    buf.writeBytes(result.getBytes());
    ctx.channel().writeAndFlush(buf);

    ByteBuf buf2 = Unpooled.buffer();
    buf2.writeBytes("\r\n".getBytes());
    ctx.channel().writeAndFlush(buf2);
    System.out.println("==========");
}
}

其实客户端和服务端使用SimpleChannelInboundHandler 与 ChannelInboundHandler的问题:

SimpleChannelInboundHandler里主要源码:

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }
        }
    }

一般用netty来发送和接收数据都会继承SimpleChannelInboundHandler和ChannelInboundHandlerAdapter这两个抽象类,那么这两个到底有什么区别呢?

其实用这两个抽象类是有讲究的,在客户端的业务Handler继承的是SimpleChannelInboundHandler,而在服务器端继承的是ChannelInboundHandlerAdapter。

最主要的区别就是SimpleChannelInboundHandler在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release。
在客户端,当 channelRead0()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler 负责释放指向保存该消息的 ByteBuf 的内存引用。
在服务端中,你仍然需要将传入消息回送给发送者,而 write()操作是异步的,直到 channelRead()方法返回后可能仍然没有完成。为此,EchoServerHandler
扩展了 ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息而是将消息传递给下一个ChannelHandler处理。消息在 EchoServerHandler 的 channelReadComplete()方法中,当 writeAndFlush()方法被调用时被释放。

从定义类上看:

public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter {
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler

从类的定义中,我们可以看出
SimpleChannelInboundHandler是抽象类,而ChannelInboundHandlerAdapter是普通类。
SimpleChannelInboundHandler支持泛型的消息处理,而ChannelInboundHandlerAdapter不支持泛型

你可能感兴趣的:(Netty)