webSocket

扫码_搜索联合传播样式-标准色版-压缩版.jpg

webSocket

1.为什么会有webSocket的出现?

默认HTTP协议只支持请求响应模式,也就是常说的请求-响应模式,这样做可以简化Web服务器,减少服务器的负担,加快响应速度。这种机制对于信息变化不是特别频繁的应用尚能相安无事,但是对于那些实时要求比较高的应用来说,比如说在线游戏、在线证券、设备监控、新闻在线播报、RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。所以我们需要一种服务端可以主动向客户端推送消息的技术,来保证消息的实时性。但是在webSocket出现之前,实时获取消息也是有几种解决方案的。

轮询:

轮询是比较常用并且简单的实现实时消息的方式,简单来说就是客户端不断向服务端请求数据,这个频率可能是一分钟甚至一秒一次,以此保持尽可能实时的获取最新数据。

以Ajax+轮询的方式,实现消息的获取

setInterval('send()', 1000);//轮询执行,1s一次

 function send() {
 $.ajax({
 url : '../case/quatrzLoad.ajax',
 type : 'post',
 dataType : 'JSON',
 async : false,
 cache : false,
 data : {},
 success : function(result) {
 if (result.success) {
 //右下角消息提醒
 bottomRight();
 //声音提醒
 playSound("../audio/remind.mp3");
 }
​
 }
 });
 }

轮询的方式适用于用户量比较少的应用,而且实现简单。但是频繁的请求会给服务器带来很大的压力。

长连接:

在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。 优点:消息即时到达,不发无用请求;管理起来也相对便。 缺点:服务器维护一个长连接会增加开销。

长轮询:

客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。 优点:在无消息的情况下不会频繁的请求,耗费资小。 缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。

2.webSocket是什么?

WebSocket同样是HTML 5规范的组成部分之一。WebSocket 相较于上述几种连接方式,实现原理较为复杂,用一句话概括就是:客户端向 WebSocket 服务器通知(notify)一个带有所有 接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。由于 WebSocket 本身是基于TCP协议的,所以在服务器端我们可以采用构建 TCP Socket 服务器的方式来构建 WebSocket 服务器。

这个 WebSocket 是一种全新的协议。它将 TCP 的 Socket(套接字)应用在了web page上,从而使通信双方建立起一个保持在活动状态连接通道,并且属于全双工(双方同时进行双向通信)。

其实是这样的,WebSocket 协议是借用 HTTP协议 的 101 switch protocol 来达到协议转换的,从HTTP协议切换成WebSocket通信协议。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。其他特点包括:

  • 建立在 TCP 协议之上,服务器端的实现比较容易。

  • 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443 ,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

  • 数据格式比较轻量,性能开销小,通信高效。

  • 可以发送文本,也可以发送二进制数据。

  • 没有同源限制,客户端可以与任意服务器通信。

  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

3.WebSocket如何创建?

WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。我们来看看WebSocket连接是如何创建的。

首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;

  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为WebSocket连接;

  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;

  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。

现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。

安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。

4.webSocket 客户端基本使用:

webSocket的api很简洁,甚至感觉粗暴。但是确实使用起来很方便:

​
$(function() {
 var socket;
 if(typeof(WebSocket) == "undefined") {
 alert("您的浏览器不支持WebSocket");
 return;
 }
​
 $("#btnConnection").click(function() {
 //实现化WebSocket对象,指定要连接的服务器地址与端口
 socket = new WebSocket("ws://192.16.20.39:8080/ops_console_war/ws/"+$("#cur_userId").val());
 //打开事件
 socket.onopen = function() {
 alert("Socket 已打开");
 //socket.send("这是来自客户端的消息" + location.href + new Date());
 };
 //获得消息事件
 socket.onmessage = function(msg) {
 console.log(msg);
 if (msg.success) {
 console.log("111");
 }else{
 console.log("222");
 }
 };
 //关闭事件
 socket.onclose = function() {
 alert("Socket已关闭");
 };
 //发生了错误事件
 socket.onerror = function() {
 alert("发生了错误");
 }
 });
​
 $("#btnSend").click(function() {
 socket.send("这是来自客户端的消息" + location.href + new Date());
 });
​
 $("#btnClose").click(function() {
 socket.close();
 });
​
​
 $("#btnSendToAll").click(function(){
 $.ajax({
 url : '../user/sendMessage.ajax',
 type : 'post',
 dataType : 'JSON',
 data : {
 },
 success : function(data) {
 console.log("成功")
 },error : function(msg) {
 console.log("失败")
 }
 });
 });
});

5.webSocket服务端基本使用:

​
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
​
/**
 * @ClassName WebSocketServer
 * @Description webSocket服务
 * @Author
 * @Date 2019-10-22 下午 3:58
 * @Version V1.0
 */
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {
​
 private static WebSocketServer server = new WebSocketServer();
​
 /***
 * 日志
 */
 Logger logger = LoggerFactory.getLogger(this.getClass());
​
 /***
 * 当前在线用户数量 包含同一用户登录多个浏览器的情况 不等于socketMap的大小
 */
 private static AtomicInteger onlineUserCount = new AtomicInteger(0);
 /***
 * 存储用户id和session对应关系的map
 */
 private static Map socketMap = new ConcurrentHashMap<>(32);
​
​
 /****
 * 当socket开始连接时触发
 * @param userId
 * @param session
 */
 @OnOpen
 public void onOpen(@PathParam("userId") String userId, Session session) {
​
 if(StringUtils.isEmpty(userId)){
​
 logger.error(" 客户端开始建立连接---但连接中缺失参数" );
​
 }else{
​
 socketMap.put(userId,session);
​
​
 logger.info(" 用户id为: "+userId+"  建立socket连接,当前在线用户数量:  "+ socketMap.size());
​
 }
​
 }
​
​
 /****
 * 服务端收到客户端消息时触发
 * @param message
 * @param session
 */
 @OnMessage
 public void onMessage(String message, Session session) {
​
 logger.info(" 客户端向服务端发送消息  : {} ",message);
​
 }
​
 /****
 * socket连接关闭时触发
 * @param session
 * @param closeReason
 */
 @OnClose
 public void onClose(@PathParam("userId")String userId,Session session, CloseReason closeReason) {
 //通过用户id 将用户session删除
 socketMap.remove(userId);
​
 logger.info(" id 为 :"+userId+"  的用户下线,当前在线用户数量 : "+socketMap.size());
 }
​
 /***
 * 连接错误时执行
 * @param t
 */
 @OnError
 public void onError(Throwable t) {
​
 t.printStackTrace();
​
 }
​
 /***
 *
 * 通过用户id向指定用户推送消息
 * @param message
 * @param userId
 * @throws IOException
 */
 public void sendMessage(String message,String userId) throws IOException {
​
 socketMap.get(userId).getBasicRemote().sendText(message);
​
 }
​
​
 /****
 * 向所有用户发送消息 群发消息
 * @param message
 * @throws IOException
 */
 public void sendMessageToAllUser(String message) throws IOException{
​
 long start = System.currentTimeMillis();
 logger.info(" 服务端开始群发消息 , 消息内容:  "+message);
​
 for (String userId : socketMap.keySet()) {
​
 socketMap.get(userId).getBasicRemote().sendText(message);
​
 }
 long end = System.currentTimeMillis();
 logger.info(" 服务端群发消息结束 , 共耗时 :  "+(end-start)+" ms");
​
 }
​
​
 public static Map getSocketMap(){
 return socketMap;
 }
​
}

本人最近因为项目原因,正在将以前的轮询获取消息的代码进行重构。并且因为用户数量并不是太多,所以没有选用其他三方webSocket插件,因此接触到webSocket。过几天,会将完整的消息推送项目分享给大家。

扫码_搜索联合传播样式-标准色版-压缩版.jpg

你可能感兴趣的:(webSocket)