经过对Netty的基础认识,设计模型的初步了解,来写个测试,试试手感
上篇也说到官方推荐我们使用主从线程池模型,那就选择这个模型进行操作
需要操作的步骤:
- 需要构建两个主从线程组
- 写一个服务器入口启动器
- 设置Channel双向通道
- 配置线程池处理器,操作Channel
- 监听机制,比如监听启动端口,监听服务器关闭等等
利用IDEA快速一键构建一个springboot项目,然后去maven仓库找netty依赖,找个4.x.x最新的依赖就可以,5.x.x的版本已经被废弃,不要使用
io.netty netty-all 4.1.32.Final
简单的目录展示
为了简单直接测试,在启动类同级建一个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{ /** * 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;/** * 自定义的助手类 ** 客户端向服务端发起请求之后,数据存放在缓冲区 * 然后服务端从缓冲区中读取,整体操作是一个入栈 *
* SimpleChannelInboundHandler 入栈 * 要往客户端写点东西返回,使用HttpObject */public class HelloHandle extends SimpleChannelInboundHandler
{ @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返回状态,) 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
-----------------------------------------------------------