NIO框架Netty搭建简易聊天室
Netty Reactor模型
单线程模型
用户发起 IO 请求到 Reactor 线程
Ractor 线程将用户的 IO 请求放入到通道,然后再进行后续处理
处理完成后,Reactor 线程重新获得控制权,继续其他客户端的处理
缺点:
这种模型一个时间点只有一个任务在执行,这个任务执行完了,再去执行下一个任务。
- 但单线程的 Reactor 模型每一个用户事件都在一个线程中执行:
- 性能有极限,不能处理成百上千的事件
- 当负荷达到一定程度时,性能将会下降
- 某一个事件处理器发生故障,不能继续处理其他事件
多线程模型
Reactor 多线程模型是由一组 NIO 线程来处理 IO 操作(之前是单个线程),所以在请求处理上会比上一中模型效率更高,可以处理更多的客户端请求。
这种模式使用多个线程执行多个任务,任务可以同时执行
缺点:
但是如果并发仍然很大,Reactor 仍然无法处理大量的客户端请求
主从多线程模型
这种线程模型是 Netty 推荐使用的线程模型
这种模型适用于高并发场景,一组线程池接收请求,一组线程池处理 IO
Netty聊天室案例搭建
后端部分
1.创建Maven项目引入netty依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.paic</groupId>
<artifactId>netty_chat</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
</dependencies>
</project>
2.创建netty服务器
package com.paic.netty_chat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @ProjectName: netty_chat
* @Package: com.paic.netty_chat
* @ClassName: WebsocketNettyServer
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/5/7 21:59
* @Version: 1.0
*/
public class WebsocketNettyServer {
public static void main(String[] args) {
//创建两个线程池
NioEventLoopGroup mainGrp = new NioEventLoopGroup();//主线程池
NioEventLoopGroup subGrp = new NioEventLoopGroup();//从线程池
try {
//创建Netty服务器启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
//初始化服务器启动对象
serverBootstrap
//指定使用上面创建的两个线程池
.group(mainGrp,subGrp)
//指定Netty通道类型
.channel(NioServerSocketChannel.class)
//指定通道初始化器用来加载当Channel收到消息事件后,如何进行业务处理
.childHandler(new WebsocketChannelInitializer());
//绑定服务器端口,启动服务器
ChannelFuture future = serverBootstrap.bind(9090).sync();
//等待服务器关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭服务器
mainGrp.shutdownGracefully();
subGrp.shutdownGracefully();
}
}
}
3.创建通道初始化器
package com.paic.netty_chat;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* @ProjectName: netty_chat
* @Package: com.paic.netty_chat
* @ClassName: WebsocketChannelInitializer
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/5/7 22:07
* @Version: 1.0
*/
/**
* 通道初始化器
* 用来加载通道处理器
*/
public class WebsocketChannelInitializer extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel ch) throws Exception {
//获取管道,将一个个的ChannelHandler添加到管道中
ChannelPipeline pipeline = ch.pipeline();
//添加一个http的编解码器
pipeline.addLast(new HttpServerCodec());
//添加一个用于支持大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//添加一个聚合器,这个聚合器主要讲HttpMessage聚合成FullHttpRequest/Response
pipeline.addLast(new HttpObjectAggregator(1024*64));
//需要指定接收请求的路由
// 必须使用以ws后缀结尾的url才能访问
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//添加自定义的Handler
pipeline.addLast(new ChatHandler());
}
}
4.实现自定义Handler
package com.paic.netty_chat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ProjectName: netty_chat
* @Package: com.paic.netty_chat
* @ClassName: ChatHandler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/5/7 22:26
* @Version: 1.0
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用来保存所有客户端连接
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd hh:MM");
//当channel中有新的消息会自动调用
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
//当接收到数据后会自动调用
//获取客户端发送过来的文本消息
String text = msg.text();
System.out.println("接收到消息数据为:"+text);
for(Channel client:clients){
client.writeAndFlush(new TextWebSocketFrame(sdf.format(new Date())+":"+text));
}
}
//当有新的客户端连接服务器之后,会自动调用这个方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
}
前端部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线聊天室</title>
</head>
<body>
<input type="text" id ="message">
<input type="button" value ="发送消息" onclick="sendMsg()">
接收到的消息
<p id="server_message" style="background-color: #AAAAAA"></p>
<script>
var websocket =null;
//判断当前浏览器是否支持websocket
if(window.WebSocket){
websocket = new WebSocket("ws://127.0.0.1:9090/ws");
websocket.onopen = function () {
console.log("建立连接");
}
websocket.onclose = function () {
console.log("断开连接");
}
websocket.onmessage = function (e) {
console.log("接收到服务器消息:"+e.data);
var server_message = document.getElementById("server_message");
server_message.innerHTML +=e.data+"<br/>";
}
} else {
alert("当前浏览器不支持webSocket");
}
function sendMsg(){
var message = document.getElementById("message");
websocket.send(message.value);
}
</script>
</body>
</html>
项目目录
运行效果