打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
SpringBoot2.0集成WebSocket,实现后台向前端推送信息

什么是WebSocket?


WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

  • 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。

    举例来说,我们想要查询当前的排队情况,只能是页面轮询向服务器发出请求,服务器返回查询结果。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此WebSocket 就是这样发明的。

话不多说,马上进入干货时刻。

maven依赖

SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入

		<dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-websocket</artifactId>         </dependency> 

WebSocketConfig

启用WebSocket的支持也是很简单,几句代码搞定

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * 开启WebSocket支持 * @author zhengkai */@Configuration  public class WebSocketConfig {  	    @Bean      public ServerEndpointExporter serverEndpointExporter() {          return new ServerEndpointExporter();      }    } 

WebSocketServer

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller
直接@ServerEndpoint("/websocket")@Component启用即可,然后在里面实现@OnOpen,@onClose,@onMessage等方法

import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;import org.springframework.stereotype.Component;import cn.hutool.log.Log;import cn.hutool.log.LogFactory;import lombok.extern.slf4j.Slf4j;@ServerEndpoint("/websocket/{sid}")@Componentpublic class WebSocketServer {		static Log log=LogFactory.get(WebSocketServer.class);    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。    private static int onlineCount = 0;    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();    //与某个客户端的连接会话,需要通过它来给客户端发送数据    private Session session;    //接收sid    private String sid="";    /**     * 连接建立成功调用的方法*/    @OnOpen    public void onOpen(Session session,@PathParam("sid") String sid) {        this.session = session;        webSocketSet.add(this);     //加入set中        addOnlineCount();           //在线数加1        log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());        this.sid=sid;        try {        	 sendMessage("连接成功");        } catch (IOException e) {            log.error("websocket IO异常");        }    }    /**     * 连接关闭调用的方法     */    @OnClose    public void onClose() {        webSocketSet.remove(this);  //从set中删除        subOnlineCount();           //在线数减1        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());    }    /**     * 收到客户端消息后调用的方法     *     * @param message 客户端发送过来的消息*/    @OnMessage    public void onMessage(String message, Session session) {    	log.info("收到来自窗口"+sid+"的信息:"+message);        //群发消息        for (WebSocketServer item : webSocketSet) {            try {                item.sendMessage(message);            } catch (IOException e) {                e.printStackTrace();            }        }    }	/**	 * 	 * @param session	 * @param error	 */    @OnError    public void onError(Session session, Throwable error) {        log.error("发生错误");        error.printStackTrace();    }	/**	 * 实现服务器主动推送	 */    public void sendMessage(String message) throws IOException {        this.session.getBasicRemote().sendText(message);    }    /**     * 群发自定义消息     * */    public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {    	log.info("推送消息到窗口"+sid+",推送内容:"+message);        for (WebSocketServer item : webSocketSet) {            try {            	//这里可以设定只推送给这个sid的,为null则全部推送            	if(sid==null) {            		item.sendMessage(message);            	}else if(item.sid.equals(sid)){            		item.sendMessage(message);            	}            } catch (IOException e) {                continue;            }        }    }    public static synchronized int getOnlineCount() {        return onlineCount;    }    public static synchronized void addOnlineCount() {        WebSocketServer.onlineCount++;    }    public static synchronized void subOnlineCount() {        WebSocketServer.onlineCount--;    }}

消息推送

至于推送新信息,可以再自己的Controller写个方法调用WebSocketServer.sendInfo();即可

@Controller@RequestMapping("/checkcenter")public class CheckCenterController {	//页面请求	@GetMapping("/socket/{cid}")	public ModelAndView socket(@PathVariable String cid) {		ModelAndView mav=new ModelAndView("/socket");		mav.addObject("cid", cid);		return mav;	}	//推送数据接口	@ResponseBody	@RequestMapping("/socket/push/{cid}")	public ApiReturnObject pushToWeb(@PathVariable String cid,String message) {  		try {			WebSocketServer.sendInfo(message,cid);		} catch (IOException e) {			e.printStackTrace();			return ApiReturnUtil.error(cid+"#"+e.getMessage());		}  		return ApiReturnUtil.success(cid);	} } 

页面发起socket请求

然后在页面用js代码调用socket,当然,太古老的浏览器是不行的,一般新的浏览器或者谷歌浏览器是没问题的。还有一点,记得协议是ws的哦,如果像我这样封装了一些basePath的路径类,可以replace(“http”,“ws”)来替换协议

    <script>     var socket;      if(typeof(WebSocket) == "undefined") {          console.log("您的浏览器不支持WebSocket");      }else{          console.log("您的浏览器支持WebSocket");          	//实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接              //等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");              socket = new WebSocket("${basePath}websocket/${cid}".replace("http","ws"));              //打开事件              socket.onopen = function() {                  console.log("Socket 已打开");                  //socket.send("这是来自客户端的消息" + location.href + new Date());              };              //获得消息事件              socket.onmessage = function(msg) {                  console.log(msg.data);                  //发现消息进入    开始处理前端触发逻辑            };              //关闭事件              socket.onclose = function() {                  console.log("Socket已关闭");              };              //发生了错误事件              socket.onerror = function() {                  alert("Socket发生了错误");                  //此时可以尝试刷新页面            }              //离开页面时,关闭socket            //jquery1.8中已经被废弃,3.0中已经移除            // $(window).unload(function(){              //     socket.close();              //});      }    </script> 

运行效果

v1.1的效果,刚刚修复了日志,并且支持指定监听某个端口,代码已经全部更新,现在是这样的效果


先打开页面,指定cid,启用socket接收,然后再另一个页面调用刚才Controller封装的推送信息的方法到这个cid的socket,即可向前端推送消息。

后续

针对简单IM的业务场景,进行了一些优化,可以看后续的文章SpringBoot2+WebSocket之聊天应用实战(优化版本)

主要变动是CopyOnWriteArraySet改为ConcurrentHashMap,保证多线程安全同时方便利用map.get(userId)进行推送到指定端口。

相比之前的Set,Set遍历是费事且麻烦的事情,而Map的get是简单便捷的,当WebSocket数量大的时候,这个小小的消耗就会聚少成多,影响体验,所以需要优化。

Websocker注入Bean问题

关于这个问题,可以看最新发表的这篇文章,在参考和研究了网上一些攻略后,项目已经通过该方法注入成功,大家可以参考。
关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法

使用Netty构建高性能websocket服务器

Springboot2构建基于Netty的高性能Websocket服务器(netty-websocket-spring-boot-starter)
只需要换个starter即可实现高性能websocket,赶紧使用吧

另外也感谢大家的阅读和评论,一起进步,谢谢!~~

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Spring+websocket+quartz实现消息定时推送
认识HTML5的WebSocket
WEB即时通讯/消息推送
Web 实时推送技术如何弥补 HTTP 协议的缺陷? | 技术头条
python测试开发django
你还在使用 ajax 轮询吗?试试 WebSocket 让后端主动推送消息
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服