//依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
</dependencies>
//入口
package com.nwd.webSocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class,args);
}
}
//第一步
package com.nwd.webSocket.service;
import com.nwd.webSocket.config.WebSocketChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Netty WebSocket 服务器配置
*/
@Component
public class NettyWebSocketServer {
private EventLoopGroup bossGroup; // 主线程组,处理连接请求
private EventLoopGroup workerGroup; // 工作线程组,处理I/O操作
private Channel channel;
@PostConstruct
public void start() throws InterruptedException {
bossGroup = new NioEventLoopGroup(1); // 1个线程
workerGroup = new NioEventLoopGroup(); // 默认CPU核心数*2
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebSocketChannelInitializer()); // 自定义初始化器
// 绑定端口并启动
channel = bootstrap.bind(7002).sync().channel();
System.out.println("Netty WebSocket 服务器启动,端口:7002");
}
@PreDestroy
public void shutdown() {
if (channel != null) {
channel.close();
}
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("Netty WebSocket 服务器关闭");
}
}
//第二步
package com.nwd.webSocket.config;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.AttributeKey;
import org.springframework.util.ObjectUtils;
/**
* WebSocket 通道初始化器
*/
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
// 添加HTTP编解码器
ch.pipeline().addLast(new HttpServerCodec())
// 支持大数据流
.addLast(new ChunkedWriteHandler())
// 聚合HTTP请求为FullHttpRequest
.addLast(new HttpObjectAggregator(8192))
.addLast(new WebSocketFrameHandler.QueryParamInterceptor())
// WebSocket协议处理器,指定路径为/ws
.addLast(new WebSocketServerProtocolHandler("/ws"))
// 自定义消息处理器
.addLast(new WebSocketFrameHandler());
}
}
//第三步
package com.nwd.webSocket.config;
import com.alibaba.fastjson.JSON;
import com.nwd.webSocket.dao.SendMessage;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.AttributeKey;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.ConcurrentHashMap;
/**
* 处理WebSocket消息
*/
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
// 保存所有连接的Channel
private static final ConcurrentHashMap<String, Channel> channels = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
System.out.println("进来");
if (frame instanceof TextWebSocketFrame) {
String message = ((TextWebSocketFrame) frame).text();
SendMessage sendMessage = JSON.parseObject(message, SendMessage.class);
String senderId = ctx.channel().id().asShortText();
AttributeKey<String> userIdKey = AttributeKey.valueOf("userId");
String userId = ctx.channel().attr(userIdKey).get();
if (userId == null) {
ctx.close(); // 未认证的连接直接关闭
return;
}
Channel channel1 = channels.get(sendMessage.getName());
if (!ObjectUtils.isEmpty(channel1)){
if (!userId.equals(sendMessage.getName())){
channel1.writeAndFlush(new TextWebSocketFrame(senderId + ": " + sendMessage.getMsg()));
}
}else {
ctx.channel().writeAndFlush(new TextWebSocketFrame( "用户已离线"));
}
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
String channelId = ctx.channel().id().asShortText();
System.out.println("客户端连接: " + channelId);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
safeRemoveUser(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 记录错误日志
System.err.println("发生异常: " + cause.getMessage());
// 立即清理用户(不等待channelInactive)
safeRemoveUser(ctx);
// 关闭连接(会触发channelInactive)
ctx.close().addListener(future -> {
if (!future.isSuccess()) {
System.err.println("关闭连接失败: " + future.cause());
}
});
}
/**
* 自定义拦截器:从HTTP请求中解析URL参数
*/
public static class QueryParamInterceptor extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
// 解析URL中的用户ID参数(例如:ws://localhost:8080/ws?userId=123)
String uri = request.uri();
System.out.println(uri);
QueryStringDecoder queryDecoder = new QueryStringDecoder(uri);
String userId = queryDecoder.parameters().get("userId").get(0);
if (!ObjectUtils.isEmpty(userId)){
channels.put(userId, ctx.channel());
System.out.println("用户绑定成功: " + userId);
for (String key:channels.keySet()) {
System.out.println("key-"+key);
System.out.println("value-"+channels.get(key));
}
}
System.out.println("userId--:"+userId);
// 将用户ID保存到Channel属性中
ctx.channel().attr(AttributeKey.valueOf("userId")).set(userId);
// 重置请求URI为/ws,避免WebSocket协议处理器校验失败
request.setUri("/ws");
// 传递给下一个处理器
ctx.fireChannelRead(request.retain());
}
}
private void safeRemoveUser(ChannelHandlerContext ctx) {
AttributeKey<String> userIdKey = AttributeKey.valueOf("userId");
String userId = ctx.channel().attr(userIdKey).get();
if (userId != null) {
// 原子性移除用户(防止并发问题)
channels.remove(userId, ctx.channel());
System.out.println("[安全清理] 用户已移除: " + userId);
}
}
}
//第四步
package com.nwd.webSocket.dao;
import lombok.Data;
@Data
public class SendMessage {
private String msg;
private String name;
}
netty集成webSocket
于 2025-03-26 18:04:09 首次发布