Netty+WebSocket,Channel和用户绑定关系,实现后台根据用户id向前端推送信息

以SpringBoot为模板

1.pom依赖

 		<!-- WebSocket -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.36.Final</version>
        </dependency>

2.springboot启动类

@SpringBootApplication
public class SpringBootApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootApplication.class, args);

		new Thread(new ClientsCheck()).start();  // 客户端检查

		try {
			new NettyServer(12345).start();
		} catch (Exception e) {
			System.out.println("NettyServerError:" + e.getMessage());
		}
	}

}

3.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 WebSocketHandler());
							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.ChannelHandlerPool

通道组池,管理所有websocket连接

public class ChannelHandlerPool {

	public ChannelHandlerPool() {
	}

	public static Set<Channel> channelGroup = Collections.synchronizedSet(new HashSet<>());

}

5.WebSocketHandler

public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

	private final String USER = "user";
	private final AttributeKey<String> key = AttributeKey.valueOf(USER);

	@Override
	protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {

	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("与客户端建立连接,通道开启!");
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("与客户端断开连接,通道关闭!");
		// 添加到channelGroup 通道组
		ChannelHandlerPool.channelGroup.remove(ctx.channel());
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 首次连接是FullHttpRequest,处理参数
		if (null != msg && msg instanceof FullHttpRequest) {
			FullHttpRequest request = (FullHttpRequest) msg;
			String uri = request.uri();

			ConcurrentMap<String, String> paramMap = getUrlParams(uri);
			System.out.println("接收到的参数是:" + JSON.toJSONString(paramMap));

			online(paramMap.get("uid"), ctx.channel());
			// 如果url包含参数,需要处理
			if (uri.contains("?")) {
				String newUri = uri.substring(0, uri.indexOf("?"));
				request.setUri(newUri);
			}
		} else if (msg instanceof TextWebSocketFrame) {
			// 正常的TEXT消息类型
			TextWebSocketFrame frame = (TextWebSocketFrame) msg;
			System.out.println("read0: " + frame.text());
		}
		super.channelRead(ctx, msg);
	}

	private static ConcurrentMap<String, String> getUrlParams(String url) {
		ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
		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;
		}
	}

	/**
	 * 上线一个用户
	 *
	 * @param channel
	 * @param userId
	 */
	private void online(String userId, Channel channel) {
		// 保存channel通道的附带信息,以用户的uid为标识
		channel.attr(key).set(userId);
		ChannelHandlerPool.channelGroup.add(channel);
	}

}

6.ClientsCheck

通道连接数

public class ClientsCheck implements Runnable {
	@Override
	public void run() {
		try {
			while (true) {
				int size = ChannelHandlerPool.channelGroup.size();
				System.out.println("client quantity -> " + size);
				Thread.sleep(5000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

7.Controller

@RestController
public class IndexController {

	@PostMapping("/send")
	public String send2User(@RequestParam(value = "uid") String uid, @RequestParam(value = "data") String data) {
		List<Channel> channelList = getChannelByName(uid);
		if (channelList.size() <= 0) {
			return "用户" + uid + "不在线!";
		}
		channelList.forEach(channel -> channel.writeAndFlush(new TextWebSocketFrame(data)));
		return "success";
	}

	/**
	 * 根据用户id查找channel
	 * 
	 * @param name
	 * @return
	 */
	public List<Channel> getChannelByName(String name) {
		AttributeKey<String> key = AttributeKey.valueOf("user");
		return ChannelHandlerPool.channelGroup.stream().filter(channel -> channel.attr(key).get().equals(name))
				.collect(Collectors.toList());
	}

}

8.test.html

注:
1.我只会简单的前端js,自己引入jquery.js
2.以uid为55,创建了一个通道连接

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Netty-Websocket</title>
</head>
<script src="webjars/jquery/3.4.1/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>

9.效果演示

访问:http://127.0.0.1:8700/test.html
在这里插入图片描述
后台控制台打印,说明用户uid:55 已经建立通道
在这里插入图片描述
访问接口:http://127.0.0.1:8700/send,实现后端向前端推送消息
在这里插入图片描述

查看test.html页面
在这里插入图片描述
完成后端向uid:55, 推送“test:Netty+WebSocket”的内容

dome:
链接: https://pan.baidu.com/s/1ZaBMGifNKzFVE08wrny3Sg 提取码: q92k

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Netty是一个高性能的网络编程框架,它提供了一种基于事件驱动的异步、事件驱动的网络应用程序框架。而WebSocket是一种在单个TCP连接上进行全双工通信的协议,它可以在客户端和服务器之间实现实时的双向通信。 在Netty中使用WebSocket进行消息推送,可以通过以下步骤实现: 1. 创建一个WebSocket服务器: - 创建一个ServerBootstrap对象,并设置相关参数,如端口号、线程模型等。 - 添加一个ChannelInitializer,用于初始化每个新连接的Channel。 - 在ChannelInitializer中添加一个WebSocketServerProtocolHandler,用于处理WebSocket握手和帧的编解码。 - 添加自定义的ChannelHandler,用于处理具体的业务逻辑。 2. 处理WebSocket连接和消息: - 在自定义的ChannelHandler中,重写channelRead方法,处理接收到的WebSocket消息。 - 可以根据业务需求,对接收到的消息进行解析和处理,并将结果返回给客户端。 3. 推送消息给客户端: - 在需要推送消息的地方,获取到所有已连接的WebSocket客户端Channel。 - 遍历所有客户端Channel,将消息写入到Channel中,实现消息的推送。 4. 异常处理和连接管理: - 在自定义的ChannelHandler中,重写exceptionCaught方法,处理异常情况。 - 可以根据具体需求,对异常进行处理,如关闭连接、记录日志等。 - 可以使用ChannelGroup来管理所有已连接的WebSocket客户端Channel,方便进行批量操作。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值