现在,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
以上信息摘自维基百科( https://zh.wikipedia.org/wiki/WebSocket )
简单点说,WebSocket 就是减小客户端与服务器端建立连接的次数,减小系统资源开销,只需要一次 HTTP 握手,整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直与客户端保持连接,直到你关闭请求,同时由原本的客户端主动询问,转换为服务器有信息的时候推送。当然,它还能做实时通信、更好的二进制支持、支持扩展、更好的压缩效果等这些优点。
推荐一个知乎上叫 Ovear 的网友关于 WebSocket 原理的回答,嘻哈风格科普文,简直不要更赞了!地址: https://www.zhihu.com/question/20215561/answer/40316953
Websocket使用 ws
或 wss
的统一资源标志符,类似于 HTTP
或 HTTPS
,其中 wss
表示在 TLS 之上的 Websocket ,相当于 HTTPS 了。如:
默认情况下,Websocket 的 ws 协议使用 80 端口;运行在TLS之上时,wss 协议默认使用 443 端口。其实说白了,wss 就是 ws 基于 SSL 的安全传输,与 HTTPS 一样样的道理。
这种情况,毫无疑问我们就需要使用 wss:\
安全协议了,我们是不是简单的把 ws:\
改为 wss:\
就行了?那试试呗。
很明显 SSL 协议错误,说明就是证书问题了。记着,这时候我们一直拿的是 IP地址 + 端口号
这种方式连接 WebSocket 的,这根本就没有证书存在好么,况且生成环境你也要用 IP地址 + 端口号
这种方式连接 WebSocket 吗?肯定不行阿,要用域名方式连接 WebSocket 阿。
不用废话,直接在配置 HTTPS 域名位置加入如下配置:
这样就完成了,在 HTTPPS 下以域名方式连接 WebSocket ,可以开心的玩耍了。
稍微解释一下 Nginx 配置
Nginx 自从 1.3 版本就开始支持 WebSocket 了,并且可以为 WebSocket 应用程序做反向代理和负载均衡。
WebSocket 和 HTTP 协议不同,但是 WebSocket 中的握手和 HTTP 中的握手兼容,它使用 HTTP 中的 Upgrade 协议头将连接从 HTTP 升级到 WebSocket,当客户端发过来一个 Connection: Upgrade
请求头时,Nginx 是不知道的,所以,当 Nginx 代理服务器拦截到一个客户端发来的 Upgrade
请求时,需要显式来设置 Connection
、 Upgrade
头信息,并使用 101(交换协议)返回响应,在客户端和代理服务器、后端服务器之间建立隧道来支持 WebSocket。
当然,还需要注意一下,WebSockets 仍然受到 Nginx 缺省为60秒的 proxy_read_timeout 的影响。这意味着,如果你有一个程序使用了 WebSockets,但又可能超过60秒不发送任何数据的话,那你要么需要增加超时时间,要么实现一个 ping 的消息以保持联系。使用 ping 的解决方法有额外的好处,可以发现连接是否被意外关闭。
更具体文档详见 Nginx 官方文档: http://nginx.org/en/docs/http/websocket.html
小伙伴的需求可能还需要区分每一个websocket链接,在这我提供一下我实现的方法(基于spring boot)
spring boot的 websocket的maven依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
配置文件
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的
,作用为:注册bean对象
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的
,作用为:配置spring容器(应用上下文)
WebSocketServer代码
package com.zity.xx.xx.controller.websocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Copyright (C) 2012-2018 天津紫藤科技有限公司. Co. Ltd. All Rights Reserved. * * @author sun * @version v1 * @description 请在此处填写该类的描述 * @module * @date 17/8/15 */ @ServerEndpoint(value = "/websocket/{telephone}") @Component public class WebSocketServer { public static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全ConcurrentHashMap,手机号为key用来存放每个客户端对应的WebSocketServer对象。 private static MapwebSocketSet = new ConcurrentHashMap (); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(@PathParam("telephone")String telephone, Session session) { log.info("telephone:{},session:{}",telephone,session.getId()); if(!webSocketSet.containsKey(telephone)){ addOnlineCount(); //在线数加1 log.info("有新连接加入!当前在线人数为" + getOnlineCount()); } this.session = session; webSocketSet.put(telephone,this); sendMessage(telephone + "创建链接"); } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { String key = getKey(webSocketSet,this); if(!StringUtils.isEmpty(key)){ log.info("key:{}", key); webSocketSet.remove(key); subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("来自客户端的消息:" + message); } @OnError public void onError(Session session, Throwable error) { log.info("非正常关闭,发生错误!====>" + error.toString() + "当前在线人数为" + getOnlineCount()); } /** * 发送消息 * * @param message 消息 */ public void sendMessage(String message){ try { //判断session链接是否存在 if(this.session.isOpen() == true){ this.session.getBasicRemote().sendText(message); }else { log.info("该连接已经断开!"); } } catch (IOException e) { e.printStackTrace(); } } public void onFailure(Throwable t){ log.info(t.toString()); } /** * 自定义消息 */ public static void sendOneInfo(String id,String message){ WebSocketServer socketServer = webSocketSet.get(id); //判当前webSocket是否存在 if(socketServer != null){ socketServer.sendMessage(message); }else { log.info("该--" + id + "--WebSocket已经断开连接!"); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } private static String getKey(Map map,WebSocketServer value){ String key=""; for (Map.Entry entry : map.entrySet()) { if(value.equals(entry.getValue())){ key=entry.getKey(); } } return key; } }
js代码块
$(function () { var websocket = null; //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("服务器地址/websocket/手机号"); } else { alert('Not support websocket') } //连接发生错误的回调方法 websocket.onerror = function () { }; //连接成功建立的回调方法 websocket.onopen = function (event) { } //接收到消息的回调方法 websocket.onmessage = function (e) { //需要实时显示的内容,在此操作 } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { websocket.close(); } //关闭连接 function closeWebSocket() { websocket.close(); } })
希望能给大家提供帮助,转载部分WebSocket 结合 Nginx 实现域名及 WSS 协议访问