1.pom依赖
<!-- WebSocket -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
2.SpringBootApplication
@SpringBootApplication
@EnableFeignClients
@EnableScheduling
@EnableAsync//开启异步调用
public class ServiceWSApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceWSApplication.class, args);
try {
new NettyServer(12345).start();
}catch(Exception e) {
System.out.println("NettyServerError:"+e.getMessage());
}
}
}
3.NettyServer
启动的NettyServer,这里进行配置
/**
* NettyServer Netty服务器配置
*/
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.option(ChannelOption.SO_BACKLOG, 1024);
sb.group(group, bossGroup) // 绑定线程池
.channel(NioServerSocketChannel.class) // 指定使用的channel
.localAddress(this.port)// 绑定监听端口
.childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("收到新连接");
//websocket协议本身是基于http协议的,所以这边也要使用http解编码器
ch.pipeline().addLast(new HttpServerCodec());
//以块的方式来写的处理器
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new HttpObjectAggregator(8192));
ch.pipeline().addLast(new MyWebSocketHandler());
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
}
});
ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
cf.channel().closeFuture().sync(); // 关闭服务器通道
} finally {
group.shutdownGracefully().sync(); // 释放线程池资源
bossGroup.shutdownGracefully().sync();
}
}
}
4.MyChannelHandlerPool
通道组池,管理所有websocket连接
/**
* MyChannelHandlerPool
* 通道组池,管理所有websocket连接
*/
public class MyChannelHandlerPool {
public MyChannelHandlerPool(){}
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//用户id=>channel
public static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
}
5.MyWebSocketHandler
处理ws一下几种情况:
channelActive与客户端建立连接
channelInactive与客户端断开连接
channelRead0客户端发送消息处理
/**
* MyWebSocketHandler
* WebSocket处理器,处理websocket连接相关
*/
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("与客户端建立连接,通道开启!当前通道数量----"+MyChannelHandlerPool.channelGroup.size());
//添加到channelGroup通道组
MyChannelHandlerPool.channelGroup.add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("与客户端断开连接,通道关闭!当前通道数量----"+MyChannelHandlerPool.channelGroup.size());
//添加到channelGroup 通道组
MyChannelHandlerPool.channelGroup.remove(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//首次连接是FullHttpRequest,处理参数 by zhengkai.blog.csdn.net
if (null != msg && msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
String uri = request.uri();
System.out.println("uri-----"+uri);
Map paramMap=getUrlParams(uri);
System.out.println("接收到的参数是:"+ JSON.toJSONString(paramMap));
System.out.println("uid------"+paramMap.get("uid"));
online(ctx.channel(),paramMap.get("uid")+"");
//如果url包含参数,需要处理
if(uri.contains("?")){
String newUri=uri.substring(0,uri.indexOf("?"));
System.out.println("newUri----------"+newUri);
request.setUri(newUri);
}
}else if(msg instanceof TextWebSocketFrame){
//正常的TEXT消息类型
TextWebSocketFrame frame=(TextWebSocketFrame)msg;
System.out.println("客户端收到服务器数据:" +frame.text()+"------当前通道数量----"+MyChannelHandlerPool.channelGroup.size());
}
super.channelRead(ctx, msg);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
}
private void sendAllMessage(String message){
//收到信息后,群发给所有channel
MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(message));
}
/**
* 上线一个用户
*
* @param channel
* @param userId
*/
public void online(Channel channel, String userId) {
//先判断用户是否在web系统中登录?
//这部分代码个人实现,参考上面redis中的验证
MyChannelHandlerPool.channelMap.put(userId, channel);
AttributeKey<String> key = AttributeKey.valueOf("user");
channel.attr(key).set(userId);
}
private static Map getUrlParams(String url){
Map<String,String> map = new HashMap<>();
url = url.replace("?",";");
if (!url.contains(";")){
return map;
}
if (url.split(";").length > 0){
String[] arr = url.split(";")[1].split("&");
for (String s : arr){
String key = s.split("=")[0];
String value = s.split("=")[1];
map.put(key,value);
}
return map;
}else{
return map;
}
}
}
6.socket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Netty-Websocket</title>
</head>
<script src="jquery.js"></script>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
// uid字段要和后台WebSocketHandler类中的paramMap.get("uid")字段对应
socket = new WebSocket("ws://127.0.0.1:12345/ws?uid=" + 55); // 55代表用户id
socket.onmessage = function (event) {
$("#ws").html(event.data)
console.log(event.data)
};
socket.onopen = function (event) {
console.log("Netty-WebSocket服务器。。。。。。连接")
};
socket.onclose = function (event) {
console.log("Netty-WebSocket服务器。。。。。。关闭")
};
} else {
alert("您的浏览器不支持WebSocket协议!");
}
</script>
<body>
<div id="ws" class="ws">
</div>
</body>
</html>
7.Controller
@Api(tags = "实时推送相关接口")
@RestController
public class IndexController {
@ApiOperation("给某一个用户发送实时消息")
@PostMapping("/push/toUserId")
@Async
@ApiImplicitParams({ @ApiImplicitParam(name = "message", value = "发送消息", defaultValue = "", required = true),
@ApiImplicitParam(name = "toUserId", value = "用户Id", defaultValue = "", required = true) })
public void pushToUser(@RequestParam("message") String message, @RequestParam("toUserId") String toUserId) {
Channel channel = getChannelByUserId(toUserId);
// 收到信息后,发给当前用户的channel
if (channel != null) {
if (online(toUserId)) {
channel.writeAndFlush(new TextWebSocketFrame(message));
} else {// 不在前剔除当前用户
MyChannelHandlerPool.channelMap.remove(toUserId);
}
}
}
@ApiOperation("给多个用户发送实时消息")
@ApiImplicitParams({ @ApiImplicitParam(name = "message", value = "发送消息", defaultValue = "", required = true),
@ApiImplicitParam(name = "userIdList", value = "用户id-List<String>", defaultValue = "", required = true) })
@PostMapping("/push/users")
@Async
public void pushToUsers(@RequestParam("message") String message,
@RequestParam("userIdList") List<String> userIdList) {
System.out.println("用户id集合" + userIdList.size());
userIdList.forEach(id -> {
Channel channel = getChannelByUserId(id);
System.out.println("channel----" + id + "-----" + channel);
if ("null".equals(channel)) {
System.out.println("字符串null");
}
if (channel != null) {
if (online(id)) {
System.out.println("-------发送的通道" + id);
channel.writeAndFlush(new TextWebSocketFrame(message));
} else {
System.out.println(id + "-----不在线剔除");
MyChannelHandlerPool.channelMap.remove(id);
}
}
});
}
@ApiOperation("给所有在线用户发送实时消息")
@ApiImplicitParams({ @ApiImplicitParam(name = "message", value = "发送消息", defaultValue = "", required = true), })
@PostMapping("/push/all")
@Async
public void pushAll(String message) {
MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(message));
}
/**
* 根据用户id获取该用户的通道
*
* @param userId
* @return
*/
public Channel getChannelByUserId(String userId) {
System.out.println("getChannelByUserId当前通道数量----" + MyChannelHandlerPool.channelGroup.size());
return MyChannelHandlerPool.channelMap.get(userId);
}
/**
* 判断一个用户是否在线
*
* @param userId
* @return
*/
public Boolean online(String userId) {
return MyChannelHandlerPool.channelMap.containsKey(userId)
&& MyChannelHandlerPool.channelMap.get(userId) != null;
}
/**
* 判断一个通道是否有用户在使用 可做信息转发时判断该通道是否合法
*
* @param channel
* @return
*/
public boolean hasUser(Channel channel) {
AttributeKey<String> key = AttributeKey.valueOf("user");
return (channel.hasAttr(key) || channel.attr(key).get() != null);// netty移除了这个map的remove方法,这里的判断谨慎一点
}
/**
* Map通配推送
*
* @param keyLike 通配key
* @param message 消息
*/
@Async
@PostMapping("/push/keyLike/toUser")
public void getLikeByMap(@RequestParam("keyLike") String keyLike, @RequestParam("message") String message) {
ConcurrentHashMap<String, Channel> map = MyChannelHandlerPool.channelMap;
for (Map.Entry<String, Channel> entity : map.entrySet()) {
if (entity.getKey().contains(keyLike)) {
Channel channel = entity.getValue();
if (channel != null) {
channel.writeAndFlush(new TextWebSocketFrame(message));
}
}
}
}
}
8.效果演示
这里就做演示了 按着步骤操作木有问题