简单了解一下WebSocket

WebSocket 是一种网络通信协议,很多高级功能都需要它。
在这里插入图片描述

一、为什么需要 WebSocket ?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
举个例子,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。
在这里插入图片描述
这种单向请求的特点,注定了如果服务器有连续 的状态变化,客户端要获知就非常麻烦。我们只能用 “轮询” :每隔一段时间,就发出一个询问,了解服务器有没有新的信息。
但是轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket就是这样发明的。

二、简介

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都以及支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
在这里插入图片描述
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。客户端与服务端进行数据交换时,服务端到客户端的数据包头只有 2 到 10 字节,客户端到服务端需要加上另外 4 字节的掩码。HTTP 每次都需要携带完整头部。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是 ws (如果加密,则为 wss ),服务器网址就是 URL 。

ws://example.com:80/some/path

在这里插入图片描述

三、客户端的简单示例

WebSocket 的用法相当简单。
下面是一个网页脚本的例子(点击这个查看运行结果)基本上一眼就能明白。

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) {
    
     
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
    
    
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
    
    
  console.log("Connection closed.");
};

四、客户端的 API

WebSocket 客户端的 API 如下。

1)WebSocket 构造函数
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。

var ws = new WebSocket('ws://localhost:8080');

执行上面语句后,客户端就会与服务器进行连接。
实例对象的所有属性和方法清单,详情参见这里

2)webSocket.readyState
readyState 属性返回实例对象的当前状态,共有四种。

  • CONNECTING:值为0,表示正在连接。
  • OPEN:值为1,表示连接成功,可以通信了。
  • CLOSING:值为2,表示连接正在关闭。
  • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

下面是一个示例。

switch (ws.readyState) {
    
    
  case WebSocket.CONNECTING://值为0
    // do something
    break;
  case WebSocket.OPEN://值为1
    // do something
    break;
  case WebSocket.CLOSING://值为2
    // do something
    break;
  case WebSocket.CLOSED://值为3
    // do something
    break;
  default:
    // this never happens
    break;
}

3)webSocket.onopen
实例对象的 onopen 属性,用于指定连接成功后的回调函数。

ws.onopen = function () {
    
    
	ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用 addEventListener 方法。

ws.addEventListener('open', function (event) {
    
    
	ws.send('Hello Server!');
});

4)webSocket.onmessage
实例对象的 onmessage 属性,用于指定收到服务器数据后的回调函数。

ws.onmessage = function(event) {
    
    
  var data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
    
    
  var data = event.data;
  // 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据( blob 对象或 Arraybuffer 对象)。

ws.onmessage = function(event){
    
    
  if(typeof event.data === String) {
    
    
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    
    
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

除了动态判断收到的数据类型,也可以使用 binaryType 属性,显示指定收到的二进制数据类型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
    
    
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
    
    
  console.log(e.data.byteLength);
};

5)webSocket.send()
实例对象的 send() 方法用于向服务器发送数据。
发送文本的例子。

ws.send('your message');

发送 Blob 对象的例子。

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

发送 ArrayBuffer 对象的例子。

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
    
    
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

6)webSocket.bufferedAmount
实例对象的 bufferedAmount 属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
    
    
  // 发送完毕
} else {
    
    
  // 发送还没结束
}

7)webSocket.onclose
实例对象的 onclose 属性,用于指定连接关闭后的回调函数。

ws.onclose = function(event) {
    
    
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
    
    
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});

8)webSocket.onerror
实例对象的 onerror 属性,用于指定报错时的回调函数。

socket.onerror = function(event) {
    
    
  // handle error event
};

socket.addEventListener("error", function(event) {
    
    
  // handle error event
});

五、服务器的实现

WebSocket 服务器的实现,可以查看维基百科的列表。

js部分


 <!DOCTYPE html>
 <html>
 <head>
     <title>Java后端WebSocket的Tomcat实现</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">     
 </head>
 <body>
     Welcome<br/><input id="text" type="text"/>
     <button onclick="send()">发送消息</button>
     <hr/>
     <button onclick="closeWebSocket()">关闭WebSocket连接</button>
     <hr/>
     <div id="message"></div>
 </body>
 
 <script type="text/javascript">
     var uid = "admin";
     var websocket = null;
     //判断当前浏览器是否支持WebSocket
     if ('WebSocket' in window) {
    
    
         websocket = new WebSocket("ws://localhost:8080/WebSocketTest/websocket/"+uid);
     }
     else {
    
    
         alert('当前浏览器 Not support websocket')
     }
 
     //连接发生错误的回调方法
     websocket.onerror = function () {
    
    
         setMessageInnerHTML("WebSocket连接发生错误");
     };
 
     //连接成功建立的回调方法
     websocket.onopen = function () {
    
    
         setMessageInnerHTML("WebSocket连接成功");
     }
 
     //接收到消息的回调方法
     websocket.onmessage = function (event) {
    
    
         setMessageInnerHTML(event.data);
     }
 
     //连接关闭的回调方法
     websocket.onclose = function () {
    
    
         setMessageInnerHTML("WebSocket连接关闭");
     }
 
     //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
     window.onbeforeunload = function () {
    
    
         closeWebSocket();
     }
 
     //将消息显示在网页上
     function setMessageInnerHTML(innerHTML) {
    
    
         document.getElementById('message').innerHTML += innerHTML + '<br/>';
     }
 
     //关闭WebSocket连接
     function closeWebSocket() {
    
    
         websocket.close();
     }
 
     //发送消息
     function send() {
    
    
         var message = document.getElementById('text').value;
         websocket.send(message);
         
     }
 </script>
 </html>

Java部分

package test;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 *                 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint(value = "/websocket/{param}")//{}中的数据代表一个参数,多个参数用/分隔
public class WebSocketTest {
    
    
    
    private String uname;
    //
    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
    private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    /**
     * 连接建立成功调用的方法
     * 
     * @param session
     * 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(@PathParam(value = "param") String uid, Session session) {
    
    
        this.session = session;
        this.uname = uid;
        System.out.println(uid);
        webSocketSet.add(this); // 加入set中
        addOnlineCount(); // 在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
    
    
        webSocketSet.remove(this); // 从set中删除
        subOnlineCount(); // 在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }
    /**
     * 收到客户端消息后调用的方法
     * 
     * @param message
     *            客户端发送过来的消息
     * @param session
     *            可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
    
    
        System.out.println("来自客户端的消息:" + message);
        // 群发消息
        for (WebSocketTest item : webSocketSet) {
    
    
            try {
    
    
                if (item.uname.equals(this.uname)) {
    
    
                    item.sendMessage(item.uname + ":" + message);
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
                continue;
            }
        }
    }
    /**
     * 发生错误时调用
     * 
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
    
    
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     * 
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
    
    
        this.session.getBasicRemote().sendText(message);
        // this.session.getAsyncRemote().sendText(message);
    }
    public static synchronized int getOnlineCount() {
    
    
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
    
    
        WebSocketTest.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
    
    
        WebSocketTest.onlineCount--;
    }

}

以上代码来自网络,可实现前端和后端的互相通讯。

六、WebSocketd

推荐一款非常特别的 WebSocket 服务器:WebSocketd
它的最大特点,就是后台脚本不限语言,标准输入(stdin)就是 WebSocket 的输入,标准输出(stdout)就是 WebSocket 的输出。
有兴趣的朋友可以去看看。


好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

猜你喜欢

转载自blog.csdn.net/Cike___/article/details/107228520