netty服务器,客户端初体验

一,首先引入依赖

<dependencies>
        <!-- Netty依赖包
        https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <useSystemClassLoader>false</useSystemClassLoader>
                </configuration>
            </plugin>
        </plugins>
    </build>

二,编写服务器端

/**
 * 配置服务器的启动代码。最少需要设置服务器绑定的端口,用来监听连接请求。
 *
 */
public class LiuServer {

    private final int port;

    public LiuServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        //设置端口值
        int port = 8888;
        //呼叫服务器的 start() 方法
        new LiuServer(port).start();
    }

    public void start() throws Exception {
        //Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期。
        //NioEventLoopGroup是一个处理I/O操作的多线程事件循环
        //bossGroup作为boss,接收传入连接
        //因为bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好.
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //workerGroup作为worker,处理boss接收的连接的流量和将接收的连接注册进入这个worke
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //ServerBootstrap负责建立服务端
            //你可以直接使用Channel去建立服务端,但是大多数情况下你无需做这种乏味的事情
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    //指定使用NioServerSocketChannel产生一个Channel用来接收连接  指定NIO的模式 NioServerSocketChannel对应TCP, NioDatagramChannel对应UDP
                    .channel(NioServerSocketChannel.class)
                    //设置 socket 地址使用所选的端口
                    .localAddress(new InetSocketAddress(port))
                    //ChannelInitializer用于配置一个新的Channel
                    //用于向你的Channel当中添加ChannelInboundHandler的实现
                    //添加 EchoServerHandler 到 Channel 的 ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            //ChannelPipeline用于存放管理ChannelHandel
                            //ChannelHandler用于处理请求响应的业务逻辑相关代码
                            // //配置通信数据的处理逻辑, 可以addLast多个
                            ch.pipeline().addLast(
                                    new LiuServerHandler());
                        }
                    })//对Channel进行一些配置
                    //注意以下是socket的标准参数
                    //BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
                    //Option是为了NioServerSocketChannel设置的,用来接收传入连接的  设置TCP缓冲区
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //保持连接
                    //是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。
                    //childOption是用来给父级ServerChannel之下的Channels设置参数的
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //绑定的服务器;sync 等待服务器关闭 ,也可以在该处再绑定端口。 bind返回future(异步), 加上sync阻塞在获取连接处
            ChannelFuture f = b.bind().sync();
            System.out.println(LiuServer.class.getName() + " 服务启动开始监听端口:" + f.channel().localAddress());
            //sync()会同步等待连接操作结果,用户线程将在此wait(),直到连接操作完成之后,线程被notify(),用户代码继续执行
            //closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
            f.channel().closeFuture().sync();
        } finally {
            //释放 channel 和 块,直到它被关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

编写服务器端处理类

/**
 * 处理服务器端通道
 * ChannelInboundHandlerAdapter继承自ChannelHandlerAdapter,实现了ChannelInboundHandler接口
 * ChannelInboundHandler接口提供了不同的事件处理方法,可进行重写
 * 实现了服务器的业务逻辑,决定了连接创建后和接收到信息后该如何处理
 *
 * @author chenhx
 * @version DiscardServerHandler.java, v 0.1 2018-07-13 下午 4:05
 */

//Sharable注解 标识这类的实例之间可以在 channel 里面共享
@ChannelHandler.Sharable
public class LiuServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 每个信息入站都会调用
     * 接收数据进行处理
     * 事件处理程序方法。每当从客户端接收到新数据时,使用该方法来接收客户端的消息。 在此示例中,接收到的消息的类型为ByteBuf。
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("客户端发来消息: " + in.toString(CharsetUtil.UTF_8));
        //ChannelHandlerContext提供各种不同的操作用于触发不同的I/O时间和操作
        //调用write方法来逐字返回接收到的信息
        //这里我们不需要调用释放,因为Netty会在写的时候自动释放
        //只调用write是不会释放的,它会缓存,直到调用flush
        //将所接收的消息返回给发送者。注意,这还没有冲刷数据
        ctx.write(in);
    }

    /**
     *  当前读操作读取的最后一个消息被channelRead()方法消费时调用. 如果ChannelOption.AUTO_READ 属性被设置为off,
     *  不会再尝试从当前channel中读取inbound数据, 直到ChannelHandlerContext.read()方法被调用.
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        System.out.println("channel 通道读取完成");
        //冲刷所有待审消息到远程节点。关闭通道后,操作完成
        //第一种写法 写一个空的buf,并刷新写出区域。完成后关闭sock channel连接。
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
        //ctx.flush(); // 第二种方法:在client端关闭channel连接,这样的话,会触发两次channelReadComplete方法。
        //ctx.flush().close().sync(); // 第三种:改成这种写法也可以,但是这中写法,没有第一种方法的好。
    }

    /**
     * 读操作时捕获到异常时调用
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) {
        //打印异常堆栈跟踪
        cause.printStackTrace();
        //关闭通道
        ctx.close();
    }


}

三,编写客户端类

/**
 * 通过host和port连接服务器。
 *
 */
public class LiuClient {

    private final String host;
    private final int port;

    public LiuClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        final String host = "127.0.0.1";
        //端口与服务器端匹配
        final int port = 8888;

        new LiuClient(host, port).start();
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            //指定 EventLoopGroup 来处理客户端事件。由于我们使用 NIO 传输,所以用到了 NioEventLoopGroup 的实现
            b.group(group)
                    .channel(NioSocketChannel.class)
                    //设置服务器的地址和端口
                    .remoteAddress(new InetSocketAddress(host, port))
                    //当建立一个连接和一个新的通道时,创建添加到 EchoClientHandler 实例 到 channel pipeline
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(
                                    new LiuClientHandler());
                        }
                    });

            //连接到远程;等待连接完成 也可以在这里设置服务器地址和端口
            ChannelFuture f = b.connect().sync();
            //阻塞直到 Channel 关闭
            f.channel().closeFuture().sync();
        } finally {
            //调用 shutdownGracefully() 来关闭线程池和释放所有资源
            group.shutdownGracefully().sync();
        }
    }
}

客户端处理类

@ChannelHandler.Sharable
public class LiuClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    /**
     * 服务器的连接被建立后调用
     * 建立连接后该 channelActive() 方法被调用一次
     *
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //当被通知该 channel 是活动的时候就发送信息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty! " + Long.toString(System.currentTimeMillis()),
                CharsetUtil.UTF_8));
    }

    /**
     * 从服务器接收到数据调用
     *
     * @param ctx
     * @param in
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx,
                             ByteBuf in) {
        System.out.println("服务器发来消息: " + in.toString(CharsetUtil.UTF_8));
    }

    /**
     * 捕获异常时调用
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) {
        //记录错误日志并关闭 channel
        cause.printStackTrace();
        ctx.close();
    }
}
 

发布了23 篇原创文章 · 获赞 0 · 访问量 196

猜你喜欢

转载自blog.csdn.net/liuerchong/article/details/105173136