Netty与websocket
最近在写毕设,在我的毕设项目中需要有聊天的功能,在网上做一些的搜索后最终选择了后端使用Netty来实现websocket通信。
Netty是什么?
对于Java的网络通信进行封装(Java的NIO)和加强了的同步非阻塞的网络通信框架
Websocket是什么?
WebSocket 是基于 HTTP 协议的一种新的通信协议,他的特点就是服务器同样可以主动的向客户端发送消息,并且连接成功一次之后就一直连接着(持久化协议)。(而HTTP是无状态的非持久化协议,需要客户端携带一些信息才能记住你是哪个客户,服务器很被动)
后端
Netty重要组件
1.老板线程组 EventLoopGroup (线程池 cpu核心乘2)
- 作用:主要负责连接,还有下发任务
- 主要负责连接建立、监听IO事件、IO事件读写以及将事件分发到Handlers 处理器。
2.员工线程组 EventLoopGroup (线程池 cpu核心乘2)
- 作用:主要工作 非阻塞的执行业务处理逻辑
3.服务端的启动类 ServerBootstarp
- 作用:负责构造
4.通道 channel 是nio的ServerSocketChannel
- 主要用来接收信息的通道
5.业务责任链 channelHandler (主要编写代码逻辑区域)
- 每一个handler责可以处理一些特定的事情
所有配置完毕后,使用channel绑定端口,由于这些是多线程都是异步的所以需要使用sync方法阻塞当前线程,才能收到想要的信息
基本绑定监听端口
public static void main(String[] args) throws InterruptedException {
//主线程 老板负责分发命令
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//子线程 负责处理工作
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
//服务器类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//将主线程 和 子线程放入其中
serverBootstrap.group(bossGroup,workGroup)
//放入一个服务器管道
.channel(NioServerSocketChannel.class)
//自定义管道初始化类需要继承 ChannelInitializer<SocketChannel>
.childHandler(new ServerInitialzer());
//绑定端口号,启动服务端
ChannelFuture future = serverBootstrap.bind(7530).sync();
//对关闭通道进行监听
future.channel().closeFuture().sync();
}finally {
//结束之后无论如何都要关闭流
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
设置管道
public class ServerInitialzer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//http编解码器
pipeline.addLast(new HttpServerCodec());
//写操作流添加
pipeline.addLast(new ChunkedWriteHandler());
//最大的聚合字节流大小
pipeline.addLast(new HttpObjectAggregator(1024*64));
//类似拦截器 只要是路径有/chat就会进行关于netty的操作
pipeline.addLast(new WebSocketServerProtocolHandler("/chat"));
//自定义处理器,正真处理的业务类
pipeline.addLast(new ChatHandler());
}
}
1.HttpServerCodec :HttpRequestDecoder 和 HttpResponseEncoder 的组合,因为在处理 Http 请求时这两个类是经常使用的,所以 Netty 直接将他们合并在一起更加方便使用。
2.ChunkedWriteHandler:写处理器
3.HttpObjectAggregator:设置管道最大流限制
4.WebSocketServerProtocolHandler:设置那路径进入这个管道
5.ChatHandler:自定一的一个管道操作处理类 需要继承SimpleChannelInboundHandler
管道处理器
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用于记录通道
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
//客户端获得的消息
String content = msg.text();
System.out.println("接收到的消息" + content);
//将数据刷新到客户端
clients.writeAndFlush(new TextWebSocketFrame("服务器在:" + LocalDateTime.now()) + "接收到的信息为" + content);
}
/**
* 用户进入客户端
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//添加如客户端通道
clients.add(ctx.channel());
}
/**
* 用户离开客户端
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
clients.remove(ctx.channel());
}
}
channelRead0:默认需要实现接口,只要是这个管道的数据都需要在次操作
handlerAdded:进入这个管道的第一步就会走这个接口
handlerRemoved:断开连接就会走这个接口
前端
如何使用websocket协议与后端的Neety进行通信
答:websocket协议与http协议的连接方式不同http协议开头是http://localhost:8080。对于websocket是ws://localhost:8081来进行连接,其实用起来和很简单如下
这边我后端使用的是7530(这边不要和springboot的web端口号重了),并且我们上面设置了netty监听的是/chat路径的所有消息
const socket = new WebSocket('ws://localhost:7530/chat');
接下里就介绍一下我现在这个阶段用的比较多的几个方法
1.检验是否连接成功
//连接成功就会出发这个方法
socket.onopen = function() {
console.log('WebSocket连接已建立');
};
2.向服务器直接发送json消息
socket.send(JSON.stringify(sendChat))
3.服务器返回的消息
// 监听接收到服务器消息的事件
socket.onmessage = function(event) {
const message = event.data;
console.log('接收到服务器的消息',mssage);
}
连接后的效果
跟http还是有很大区别的这个Message里面有你上传到服务器的消息就是蓝色的向上箭头,服务器给你返回的消息是红色的向下的箭头,都是可以在前端的onmessage的方法中取到
遇到的问题
在Netty的ChatHandler处理器中获取不到springboot的bean
使用set方法在能获取Bean的地方给他注入进去
如下
@Resource
private ChatMsgService chatMsgService;
@Resource
private UsersService usersService;
@PostConstruct
public void start() throws InterruptedException {
//服务器类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//将主线程 和 子线程放入其中
serverBootstrap.group(bossGroup,workGroup)
//放入一个服务器管道
.channel(NioServerSocketChannel.class)
//初始化管道
.childHandler(new ServerInitialzer());
//绑定端口号,启动服务端
ChannelFuture future = serverBootstrap.bind(7530).sync();
//对关闭通道进行监听
// future.channel().closeFuture().sync();
ChatHandler.setChatHandler(chatMsgService,usersService);
if (future.isSuccess()) {
log.info("启动 Netty Server");
}
}
ChatHandler内部的方法
public static void setChatHandler(ChatMsgService chatService,UsersService userService){
chatMsgService = chatService;
usersService = userService;
}