经过对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>
简单的目录展示
为了简单直接测试,在启动类同级建一个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
-----------------------------------------------------------