0、一个基本的Netty客户端和服务端程序

版权声明:如果觉得文章对你有用,转载不需要联系作者,但请注明出处 https://blog.csdn.net/jinxin70/article/details/86561136

前言(可跳过)

我觉的学习一个新技术,最开始的开始不是看什么概念,而是直接撸示例代码。在看概念性的内容和实践中找到一种平衡,不要一直连续看概念性的内容,也不要只是埋在代码里。

1、自己debug看看,把代码运行的流程仔细的看看,每一行,每一个陌生的类型和方法签名。

如果示例代码给的好,让你可以运行,然后debug起来,就已经入门了。

2、接着就是看一些其他的特性,对关键的特性或者你感兴趣的特性的原理进行学习,基本上就是高级使用了。当然这一部分根据你的基础可能会时间很长,可能不长。

因为高级特性往往涉及到底层的复杂实现,或者操作系统内核调用的实现(这个一般知道具体哪里使用什么系统调用,这个系统调用是做什么的即可)。高级特性还会涉及到一些设计模式(设计原则和各种设计模式)、数据结构和算法的使用(数组、队列、链表、树等等)、基于硬件API的优化(字宽对齐)

说了这么多,就是想说这篇文章会直接上代码:),源码

TimeServer.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class TimeServer {

    public void bind(int port) throws  Exception{

        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try{
            //SocketChannel启动器
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());

            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();

            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }finally {
            //优雅推出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if(args != null && args.length > 0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                //do nothing
            }
        }

        new TimeServer().bind(port);
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            //新增两个解码器
            socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
            socketChannel.pipeline().addLast(new StringDecoder());
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

}

使用Netty开发服务端的步骤大体如上面的代码,我一开始看了不适应,一脸懵,但是熟悉就好,不要纠结于代码形式:

1、配置两个线程组bossGroup和workerGroup,为什么这样写,这个是由Netty选用的线程模型决定的,可以看Netty线程模型了解更多。

2、接下来的大部分工作是在try方法体内进行的,首先new一个ServerBootstrap启动器对象

3、将两个线程组配置到启动器中

4、将NioServerSocketChannel配置到启动器中,Netty是对JavaNIO的封装,服务端通信使用NioServerSocketChannel,NioServerSocketChannel是基于NIO selector的实现,包装了NIO中的ServerSocketChannel。

5、设置backlog为1024(backlog概念待查,目前不影响进度)

6、配置绑定IO事件的处理器,一般用来写业务逻辑。

7、使用同步方式绑定到一个端口,返回一个ChannelFuture

8、通过ChannelFuture同步的关闭channel通信

9、最后finally中关闭两个线程组

ChildChannelHandler这里作为TimeServer.java的内部类

ChildChannelHandler内部,在channel的pipeline上加了两个解码处理器,后续会讲到其作用,还有一个自定义处理器TimeServerHandler。

何时使用内部类?当A类对B类依赖很高,且B类不会被其他类依赖,就可以将B类当做A的内部类,这体现了封装的思想,尽可能的降低类的可见性

如果要问,为什么不直接把B类的处理逻辑直接写在A类的某个单独抽出来的方法中?

这又是一种封装的思路了,如果A类中的某些方法做的事情违反了单一职责原则,就值得思考下是不是可以将该方法写成一个类,当然并不总是必须的,不必为了抽象而抽象。

TimeServerHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    private int counter;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String body = (String) msg;
        System.out.println("The time server receive order : " + body
                + " ; the counter is : " + ++counter);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
                System.currentTimeMillis()).toString() : "BAD ORDER";
        currentTime = currentTime + System.getProperty("line.separator");
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.writeAndFlush(resp);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

TimeServerHandler继承自ChannelInboundHandlerAdapter

重写了两个方法channelRead和exceptionCaught,方法的意思就是字面意思。

channelRead内部:

1、首先将客户端传过来的msg转成字符串

2、输出带有客户端传的信息和count信息的字符串到控制台

3、如果客户端传过来的信息和QUERY TIME ORDER相等(不区分大小写),就返回当前事件,否则返回BAD ORDER

4、将currentTime的二进制数据构造成一个大端对齐的ByteBuf

5、将缓冲区的数据从channel的一端发送到另一端。

TimeClient.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class TimeClient {
    public void connect(int port,String host) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //发起异步连接操作
            ChannelFuture f = b.connect(host,port).sync();
            //等待客户端链路关闭
            f.channel().closeFuture().sync();
        }finally {
            //优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if(args != null && args.length > 0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){

            }
        }
        new TimeClient().connect(port,"localhost");
    }
}

1、创建一个EvenLoop线程组

2、创建一个Bootstrap启动实例

3、在Bootstrap实例上配置线程组、channel、option和handler,和服务端的使用很类似

handler中配置了两个Netty提供的解码器和一个自定义解码器

4、使用Bootstrap实例同步连接IP和端口

5、等待客户端链路关闭

6、finlly中退出线程组

TimeClientHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.logging.Logger;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());

    private int counter;

    private byte[] req;

    public TimeClientHandler(){
        req = ("QUERY TIME ORDER" + System.getProperty("line.separator"))
                .getBytes();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf message = null;
        for (int i = 0; i < 100; i++) {
            message = Unpooled.buffer(req.length);
            message.writeBytes(req);
            ctx.writeAndFlush(message);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String body = (String) msg;
        System.out.println("Now is : " + body + " ; the counter is : "
                + ++counter);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 释放资源
        logger.warning("Unexpected exception from downstream : "
                + cause.getMessage());
        ctx.close();
    }
}

TimeClientHandler也是继承自ChannelInboundHandlerAdapter

重写了三个方法:

channelActive中向channel另一端发送100条信息

channelRead负责读取channel另一端发过来的数据

exceptionCaught负责处理异常

猜你喜欢

转载自blog.csdn.net/jinxin70/article/details/86561136