经过对Netty的基础认识,设计模型的初步了解,来写个测试,试试手感

上篇也说到官方推荐我们使用主从线程池模型,那就选择这个模型进行操作

需要操作的步骤:

  • 需要构建两个主从线程组
  • 写一个服务器入口启动器
  • 设置Channel双向通道
  • 配置线程池处理器,操作Channel
  • 监听机制,比如监听启动端口,监听服务器关闭等等

利用IDEA快速一键构建一个springboot项目,然后去maven仓库找netty依赖,找个4.x.x最新的依赖就可以,5.x.x的版本已经被废弃,不要使用

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.32.Final</version>
</dependency>

简单的目录展示

Netty 简单服务器 (三)-LMLPHP

为了简单直接测试,在启动类同级建一个NettyServer启动类,一个main方法就可以了,贴图:

package com.yus.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Netty 启动类
 * 简单实现客户端发送连接请求,服务器返回信息
 */
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {

        // 1。定义两个主从线程组
        // 主线程组 --- 用于接收客户端的连接,不处理事件,就是接任务的包工头
        EventLoopGroup primaryGroup = new NioEventLoopGroup();

        // 从线程组 --- 处理主线程组接收连接注册之后的事件,就是纯打工的
        EventLoopGroup subGroup = new NioEventLoopGroup();

        try {
            // 2。服务启动器
            ServerBootstrap bootstrap = new ServerBootstrap();

            // 根据Netty选择不同的线程模型,选择不同的重载方法,这里是主从线程模型
            // 业务的分配等功能,都由bootstrap负责处理,不需要我们处理
            // .channel --- 设置通道类型
            // .childHandler 针对[subGroup从线程组]处理每一个channel,可以选择实现类,也可以自定义内部类的方式构建
            // 每一个Channel由多个handle共同组成管道(pipeline)
            bootstrap.group(primaryGroup, subGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInit());

            // 3。绑定启动端口,确保链接启动,使用sync同步等待端口启动完成
            ChannelFuture future = bootstrap.bind(8088).sync();

            // 4。用于监听关闭的Channel ,同步方式
            future.channel().closeFuture().sync();
        } finally {
            //使用shutdownGracefully关闭 ,原shutdown方法已经过时
            primaryGroup.shutdownGracefully();
            subGroup.shutdownGracefully();
        }
    }
}

初始化器

package com.yus.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * 初始化器 --- 初始化每一个Channel
 * .channel设置的类型是泛型
 */
public class ChannelInit extends ChannelInitializer<SocketChannel> {

    /**
     * channel pipeline handle 三者的关系
     *
     * 1。注册的channel通道
     * 2。进入初始化器pipeline管道
     * 3。pipeline包含多个handle,共同处理channel,也可以理解为拦截处理
     */
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        //通过 channel 获取管道
        ChannelPipeline pipeline = channel.pipeline();

        //通过管道 添加handle ,name选填
        //使用netty提供的一个编解码HttpServerCodec
        //HttpServerCodec:当请求到服务端,需要解码,然后响应客户端,需要编码
        pipeline.addLast("HttpServerCodec",new HttpServerCodec());

        //添加自定义的助手类
        pipeline.addLast("HelloHandle",new HelloHandle());
    }
}

助手类

package com.yus.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

/**
 * 自定义的助手类
 * <p>
 * 客户端向服务端发起请求之后,数据存放在缓冲区
 * 然后服务端从缓冲区中读取,整体操作是一个入栈
 * <p>
 * SimpleChannelInboundHandler 入栈
 * 要往客户端写点东西返回,使用HttpObject
 */
public class HelloHandle extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        Channel channel = ctx.channel();

        System.out.println("远程地址:" + channel.remoteAddress());

        //操作 缓冲区 参数(返回自定义字符串,字符集) 此处设置的字符集仅供ByteBuf使用
        ByteBuf buf = Unpooled.copiedBuffer("Hello,Netty,会乱码吗?!", CharsetUtil.UTF_8);

        //构建响应,将buf数据返回 参数(http版本号,http返回状态,) http1.1会开启长链接
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

        //设置响应头部的数据类型以及长度,返回需要设置charset=UTF-8,针对response设置
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,buf.readableBytes());

        //把response响应到客户端
        //write只将response写到缓冲区,writeAndFlush将response写到缓冲区,并刷到客户端
        ctx.writeAndFlush(response);
    }
}

启动访问: localhost:8088 

-----------------------------------------------------------

12-18 21:40