##### 一、SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)
原文链接: https://zhengkai.blog.csdn.net/article/details/91552993
思路: 用netty实现 后台的 websocket 相当于前端利用websocket协议 后端用netty实现复杂的业务逻辑
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
SpringBootApplication
启动器中需要new一个NettyServer,并显式调用启动netty。
@SpringBootApplication
public class SpringCloudStudyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStudyDemoApplication.class,args);
try {
new NettyServer(12345).start();
System.out.println("https://blog.csdn.net/moshowgame");
System.out.println("http://127.0.0.1:6688/netty-websocket/index");
}catch(Exception e) {
System.out.println("NettyServerError:"+e.getMessage());
}
}
}
其中 ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10)); 请求为 websocket
/**
* NettyServer Netty服务器配置
* @author zhengkai.blog.csdn.net
* @date 2019-06-12
*/
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 WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
ch.pipeline().addLast(new MyWebSocketHandler());
}
});
ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
cf.channel().closeFuture().sync(); // 关闭服务器通道
} finally {
group.shutdownGracefully().sync(); // 释放线程池资源
bossGroup.shutdownGracefully().sync();
}
}
}
MyChannelHandlerPool
通道组池,管理所有websocket连接
/**
* MyChannelHandlerPool
* 通道组池,管理所有websocket连接
* @author zhengkai.blog.csdn.net
* @date 2019-06-12
*/
public class MyChannelHandlerPool {
public MyChannelHandlerPool(){}
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
MyWebSocketHandler
处理ws一下几种情况:
channelActive与客户端建立连接
channelInactive与客户端断开连接
channelRead0客户端发送消息处理
/**
* NettyServer Netty服务器配置
* @author zhengkai.blog.csdn.net
* @date 2019-06-12
*/
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 WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10));
ch.pipeline().addLast(new MyWebSocketHandler());
}
});
ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
cf.channel().closeFuture().sync(); // 关闭服务器通道
} finally {
group.shutdownGracefully().sync(); // 释放线程池资源
bossGroup.shutdownGracefully().sync();
}
}
}
socket.html
主要是连接ws,发送消息,以及消息反馈
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Netty-Websocket</title>
<script type="text/javascript">
// by zhengkai.blog.csdn.net
var socket;
if(!window.WebSocket){
window.WebSocket = window.MozWebSocket;
}
if(window.WebSocket){
socket = new WebSocket("ws://127.0.0.1:12345/ws");
socket.onmessage = function(event){
var ta = document.getElementById('responseText');
ta.value += event.data+"\r\n";
};
socket.onopen = function(event){
var ta = document.getElementById('responseText');
ta.value = "Netty-WebSocket服务器。。。。。。连接 \r\n";
};
socket.onclose = function(event){
var ta = document.getElementById('responseText');
ta.value = "Netty-WebSocket服务器。。。。。。关闭 \r\n";
};
}else{
alert("您的浏览器不支持WebSocket协议!");
}
function send(message){
if(!window.WebSocket){return;}
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
}else{
alert("WebSocket 连接没有建立成功!");
}
}
</script>
</head>
<body>
<form onSubmit="return false;">
<label>ID</label><input type="text" name="uid" value="${uid!!}" /> <br />
<label>TEXT</label><input type="text" name="message" value="这里输入消息" /> <br />
<br /> <input type="button" value="发送ws消息"
onClick="send(this.form.uid.value+':'+this.form.message.value)" />
<hr color="black" />
<h3>服务端返回的应答消息</h3>
<textarea id="responseText" style="width: 1024px;height: 300px;"></textarea>
</form>
</body>
</html>
Controller
写好了html当然还需要一个controller来引导页面。
@RestController
public class IndexController {
@GetMapping("/index")
public ModelAndView index(){
ModelAndView mav=new ModelAndView("socket");
mav.addObject("uid", RandomUtil.randomNumbers(6));
return mav;
}
}
版权声明:本文为CSDN博主「Moshow郑锴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/moshowgame/article/details/91552993
涉及问题解答**
1、
3个问题: 1.如何主动后台推送,而不是通过前端发送才进行消息推送 2.如果需要部署到域名,不用ip+port访问,使用了netty,端口肯定与项目本身不同时,该如何统一项目和ws的域名地址 3.使用前后端跨域要怎么处理? 小白一枚。希望博主从百忙中抽空回复2 年前回复
西城无故事回复: 1. 用其他的触发条件 (如定时器、线程)触发发送操作。 23. nginx配置反向代理2
2、
问个问题,虽然netty很好,但毕竟springboot原生也支持websocket,而且开发体验更棒,应用中也没发现问题,使用netty是不是多此一举?10 月前回复
留住风的小树回复華灯初上:可以做到方法级别的权限控制,因为调用是wss,最终还是要进到你写的方法的,可以在websocket请求时带上特定的参数7 月前回复
留住风的小树回复華灯初上:可以做到方法级别的权限控制,因为调用是wss,最终还是要尽到你写的方法的,可以在websocket请求时带上特定的参数7 月前回复
码哥码哥華灯初上回复留住风的小树:我有个问题,如果这样集成netty 那不相当于重新开一个端口,就把springmvc就屏蔽了,这样对权限的认证需要自己重写? 我的理解对吗?7 月前回复
留住风的小树回复:性能问题而已
##### 二、SpringBoot2.0集成WebSocket,实现后台向前端推送信息
原文连接:链接: https://blog.csdn.net/moshowgame/article/details/80275084#google_vignette
- 真实业务场景下,一个项目可能有多个页面都要使用websocket,创建一个链接,消息中使用type字段区分业务类型,和后端websocketServer进行消息传输。 是这么做吧? 还是每个使用websocket的页面,单独new 一个websocket实例
- 放行后你还会遇到一些问题,比如有些公司是由 nginx+gateway进行代理转发请求的, 所以你会遇到这样的错误 WebSocket连接错误Error during WebSocket handshake Unexpected response code 404 你需要修改nginx配置,参考这篇文章即可解决 https://www.cnblogs.com/cnsyear/p/12635301.html
- 关于第一次连接,请求头怎么携带token呢?楼主有没有试过stomp协议的websocket?
- WebSocketServer 是一个单例。那么每次onOpen时,this.session 和 this.user 都会发生改变。这样是不是有问题?
- 例子我都实现成功了,但是我就是有一点概念没有想明白,困扰我好几天了,就是websocket连接成功之后,后端如何向前端发送消息,如果说后端用定时器,然后定时执行推送给前端的话,和前端用setTimeOut轮询差不多啊,就是这一点我一直没有想明白,希望楼主帮我梳理一下,谢谢!
- 关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法 ****
- @Component和@ServerEndpoint关于是否单例模式,能否使用static Map等一些问题的解答
看到大家都在热心的讨论关于是否单例模式这个问题,请大家相信自己的直接,如果websocket是单例模式,还怎么服务这么多session呢。
websocket是原型模式,@ServerEndpoint每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式。
Spring的@Component默认是单例模式,请注意,默认 而已,是可以被改变的。
这里的@Component仅仅为了支持@Autowired依赖注入使用,如果不加则不能注入任何东西,为了方便。
什么是prototype 原型模式? 基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例B的话,就需要使用原型模式。
关于在原型模式下使用static 的webSocketMap,请注意这是ConcurrentHashMap ,也就是线程安全/线程同步的,而且已经是静态变量作为全局调用,这种情况下是ok的,或者大家如果有顾虑或者更好的想法的化,可以进行改进。 例如使用一个中间类来接收和存放session。
为什么每次都@OnOpen都要检查webSocketMap.containsKey(userId) ,首先了为了代码强壮性考虑,假设代码以及机制没有问题,那么肯定这个逻辑是废的对吧。但是实际使用的时候发现偶尔会出现重连失败或者其他原因导致之前的session还存在,这里就做了一个清除旧session,迎接新session的功能。 - 因为楼上的没看全,有两个注解偏偏只看一个,@ServerEndpoint代表websocket,是原型模式,每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式 。。。这里的@Component只是为了方便@Autowired注入而已,没有实际意义,也不是单例模式的代表
- 如果自己在本地做demo,按照上面博客 照做没有什么问题,但是工作当中,我们都是会走gateway网关的 所以我们会碰到一个问题,那就是请求会被拦截,所以你需要对ws请求放行,可以在配置中心放行 如 - /imserver/**