为什么选择Spring Boot、Netty和WebSocket?
- 实时性:WebSocket提供了实时的双向通信能力,适合需要实时交互的应用场景,如在线聊天、实时游戏等。
- 性能:Netty作为一个高性能的NIO框架,能够处理大量的并发连接,适合高流量的应用场景。
- 易用性:Spring Boot提供了简化的配置和启动机制,使得集成Netty和WebSocket变得更加容易。
1 添加依赖
<!--netty的依赖-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.97.Final</version>
</dependency>
2. 配置Netty服务器
创建一个Netty服务器类,用于启动Netty服务器并配置WebSocket处理管道。例如:
package com.example.fruit.component;
import com.example.fruit.handler.MyChannelHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
public class NettyServer {
private final int port = 9090; // 服务器端口
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new HttpObjectAggregator(65536));
ch.pipeline().addLast(new MyChannelHandler());
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));
ch.pipeline().addLast(new WebSocketFrameAggregator(65536));
}
});
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("WebSocket server started at ws://localhost:" + port + "/ws");
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
3. 配置监听的配置类
package com.example.fruit.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wb.api.utils.Alex;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/*
*
* 前端给服务端发送消息时调用的处理器
*
*
* */
@Component
public class MyChannelHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static volatile ConcurrentHashMap<String, Channel> channels = new ConcurrentHashMap<>();
//String message = "";
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
String message = textWebSocketFrame.text();
System.out.println("Received message from client: " + message);
//返回消息给客户端
//broadcast(message);
//建立是将管道添加到数组遍历
channels.put(message, channelHandlerContext.channel());
//System.out.println(channels.get("A") + "::::" + channels.get("B"));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//建立是将管道添加到数组遍历
//channels.put(message, ctx.channel());
// 当连接建立时被调用
System.out.println("Client connected: " + ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 当连接关闭时被调用
System.out.println("Client disconnected: " + ctx.channel().remoteAddress());
}
/*发送消息给前端*/
public static void broadcast(String message) {
ChannelFuture channelFuture = channels.get("192.168.0.139").writeAndFlush(new TextWebSocketFrame(message));
}
/*发送消息给前端*/
public static void broadcast(Alex alex, String region) {
//判断管道存在
if (channels.get(region) != null) {
//转换对象为JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(alex);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
ChannelFuture channelFuture = channels.get(region).writeAndFlush(new TextWebSocketFrame(jsonString));
}
// BinaryWebSocketFrame 传递和发送音频
// new TextWebSocketFrame(); //发送文本
}
}
channels:管理连接的集合 broadcast:发送消息给前端的方法 objectMapper:将发送给前端的信息 转换成JSON的对象
如何实现对连接的某个用户实现推送消息,个人理解,在中channelActive连接建立时查询一下当时登录的账号信息,然后通过键值对添加到channels进行管理。
4. 前端集成
在前端应用中,使用WebSocket API连接到服务器,并监听服务器推送的消息。例如:
const socket = new WebSocket("ws://192.168.0.139:9090/ws");
//console.log(socket);
socket.onopen = function(event) {
console.log("Connected to WebSocket server");
socket.send("Hello Server");
};
socket.onmessage = function(event) {
console.log(socket);
//const message = JSON.parse(event.data);
// 解析JSON字符串为对象
console.log("Received from server:", event.data);
//alert(message);
};
socket.onclose = function(event) {
console.log("Disconnected from WebSocket server");
};