系列文章目录
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、WorkerGroup线程模型
BossGroup和WorkerGroup是如何确定自己有多少个EventGroup的?
bg和wg含有的子线程(NioEvent Group)的个数是默认CPU核数*2的 , 比如一台计算机是双CPU4核的,那么这个值就是8
NettyRuntime.availableProcessors()
即可获取CPU的核数
假设创建有16个子线程 , 那么当第17个客户端连接的时候 , 是使用1号线程去处理的 , 也就是循环的处理
// bossGroup仅处理连接请求
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup和客户端做业务处理
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
这里通过1来限制BossGroup的线程数为1 , 因为BossGroup的线程数不需要有那么多
pipeline底层是一个双向链表,通过pipeline能获得channel(二者是相互包含的),还能反向拿到eventLoop
二、任务队列的Task定时和非定时两种使用场景
下面的两段代码都是基于 Netty编程实例做的修改 , 向NettyServerHandler
加入了异步任务
1.用户自定义的普通任务;
代码如下(示例):
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/**
* 解决阻塞问题,存在长时间的操作的业务代码的时候 ,异步执行
* */
// 用户程序自定义的普通任务
// 下面的任务会被提交到NIOEventLoop的taskQueue中去,"go on..."会被立即输出,然后再去执行execute方法
// 也就是先执行后续的业务代码,再去回复客户端消息
ctx.channel().eventLoop().execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,cli",CharsetUtil.UTF_8));
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,cli",CharsetUtil.UTF_8));
}
});
System.out.println("go on...");
}
2.用户自定义定时任务
代码如下(示例):
//用户自定义定时任务
ctx.channel().eventLoop().schedule(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,cli,3",CharsetUtil.UTF_8));
}
},5, TimeUnit.SECONDS);
Netty抽象除了两组线程池, BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络读写操作
三、Future-Listener机制
- 当Future对象刚刚创建时, 处于非完成状态, 调用者可以通过返回的ChannelFutrue来获取操作执行的状态, 注册监听函数来执行完成后的操作
- 常见操作
- isDone 判断当前操作是否完成
- isSuccess 判断已完成的操作是否成功
- getCause 获取已完成操作失败的原因
- isCancelled 判断已完成的当前操作是否被取消
- addListener 注册监听器
四、HTTP服务实例
代码实例
public class TestServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture cf = serverBootstrap.bind(4396).sync();
cf.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 向管道加入处理器
// 1. 得到管道
ChannelPipeline pipeline = socketChannel.pipeline();
// 2. 加入一个netty提供的httpServerCodec编解码器HttpServerCodec
pipeline.addLast("myHttpServerCodec",new HttpServerCodec());
// 3. 增加一个自定义的handler
pipeline.addLast("myTestHttpServerHandler",new TestHttpServerHandler());
}
}
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
// 当有读取事件时,就会触发 , 用于读取客户端数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 判断msg 是不是http request请求
if (msg instanceof HttpRequest){
// msg真实类型是DefaultHttpRequest
System.out.println("msg 类型 "+msg.getClass());
System.out.println("客户端地址 "+ctx.channel().remoteAddress());
// 通过uri过滤特定资源
HttpRequest httpRequest = (HttpRequest) msg;
// 获取uri
URI uri = new URI((httpRequest.uri()));
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("请求图标,不做响应");
return;
}
// 回复信息给浏览器[要求满足http协议]
ByteBuf content = Unpooled.copiedBuffer("hello world!", CharsetUtil.UTF_8);
//构造http的响应
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
// 将构建好的response返回
ctx.writeAndFlush(response);
}
}
}
为什么浏览器只请求一次,但服务器显示两次请求
msg 类型 class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址 /0:0:0:0:0:0:0:1:53907
msg 类型 class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址 /0:0:0:0:0:0:0:1:53907
事实上,浏览器请求了两次服务器, 一次的请求浏览器图标, 一次是请求对应的接口
如何避免服务器响应除接口外的其它请求
HttpRequest httpRequest = (HttpRequest) msg;
// 获取uri
URI uri = new URI((httpRequest.uri()));
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("请求图标,不做响应");
return;
}
* 每个浏览器对应的pipeline和handler是独立的
http协议不是长连接 , 用完就会断掉 , 所以同一个浏览器第二次请求时 , 会被认为是一个新的浏览器