WebSocket 解读及实现

image

WebSocket 解读

1.1 WebSocket 简介

WebSocket 协议W在2008年诞生,2011年成为国际标准,所有浏览器都已经支持了。其是基于TCP的一种新的网络协议,是 HTML5 开始提供的一种在单个TCP连接上进行全双工通讯的协议,它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
  WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以数据互相传送。
  HTTP协议是一种无状态、单向的应用层协议,其采用的是请求/响应模型,通信请求只能由客户端发起,服务端对请求做出应答响应,无法实现服务器主动向客户端发起消息,这就注定如果服务端有连续的状态变化,客户端想要获知就非常的麻烦。而大多数Web应用程序通过频繁的异步JavaScript 和 aJax 请求实现长轮询,其效率很低,而且非常的浪费很多的带宽等资源。
  HTML5定义的WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态,这相比于轮询方式的不停建立连接显然效率要大大提高。

image

浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
  WebSocket 服务端在各个主流应用服务器厂商中已基本获得符合 JEE JSR356 标准规范 API 的支持(详见JSR356 WebSocket API 规范),以下列举了部分常见的商用及开源应用服务器对 WebSocket Server 端的支持情况

image

对于 WebSocket 客户端,主流的浏览器(包括 PC 和移动终端)现已都支持标准的 HTML5 的 WebSocket API,这意味着客户端的 WebSocket JavaScirpt 脚本具备良好的一致性和跨平台特性,以下列举了常见的浏览器厂商对 WebSocket 的支持情况。

image

1.2 Websocket 执行流程

对于 WebSocket 的整个生命周期,主要由以下几个事件组成

  • 连接建立:客户端向服务端请求建立连接并完成连接建立
  • 数据上行:客户端通过已经建立的连接向服务端发送数据
  • 数据下行:服务端通过已经建立的连接向客户端发送数据
  • 客户端断开:客户端要求断开已经建立的连接
  • 服务端断开:服务端要求断开已经建立的连接

image

  • 当 Browser 和 WebSocketServer 连接成功后,会触发 onopen 消息;
  • 如果连接失败,发送、接收数据失败或者处理数据出现错误,browser 会触发 onerror 消息;
  • 当 Browser 接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数 evt 中包含 Server 传输过来的数据;
  • 当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。
  • 我们可以看出所有的操作都是采用异步回调的方式触发,这样不会阻塞UI,可以获得更快的响应时间,更好的用户体验。

1.3 WebSocket 协议

WebSocket 协议使用 wswss URL协议,以分别代表不安全和安全的WebSocket请求。使用WebSocket构造函数来创建一个WebSocket连接,构造函数会返回一个WebSocket实例,可以用来监听事件。以下代码用于创建Web Socket连接 :

// 创建一个新的WebSocket. 
var websocket = new WebSocket(url, [protocol] );

以上代码中的第一个参数url,指定连接的URL。第二个参数protocol是可选的,指定了可接受的子协议。

1.4 WebSocket 属性

以下是 WebSocket 对象的属性。

image

image

1.5 WebSocket 事件

WebSocket 是纯事件驱动,通过监听事件可以处理到来的数据和改变的链接状态。以下是 WebSocket 对象的相关事件。

image

  • webSocket.onopen
    • 在客户端和服务器之间建立连接后,将从Web Socket实例触发 open 事件,它被称为客户端和服务器之间的初始握手。连接建立后引发的事件称为 onopen ,open事件触发并建立了一个连接,用于指定连接成功后的回调函数。
      websocket.onopen = function(evt) {
          console.log(evt);
      };
      
  • webSocket.onclose
    • 关闭事件标志着服务器和客户端之间通信结束,无法进一步传输消息。实例对象的onclose属性,用于指定连接关闭后的回调函数。

image

  • webSocket.onmessage
    • 消息事件通常发生当服务器发送一些数据时,无论何时发送数据,都会触发 onmessage 函数。实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。

image

> 注意:服务器发送给客户端的消息可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)

image

  • webSocket.onerror
    • 响应意外故障的时候触发,用于指定报错时的回调函数,是调用服务器重连逻辑以及处理来自 WebSocket 对象的异常的最佳场所。
      websocket.onerror = function(evt) {
          console.log(evt);
      };
      

1.6 WebSocket 方法

以下是 WebSocket 对象的相关方法。

image

  • webSocket.send
    • 此操作通常首选用于与服务器的某些通信,包括发送消息,包括文本文件,二进制数据或图像.。
  • webSocket.close
    • 此方法代表再见握手.它完全终止连接,在重新建立连接之前不会传输任何数据。

WebSocket 实现

2.1 Java webSocket 服务端

  1. 所需pom依赖

    <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
    <dependency>
      <groupid>org.java-websocket</groupid>
      <artifactid>Java-WebSocket</artifactid>
      <version>1.5.1</version>
    </dependency>
    
  2. WebSocket服务端核心类

    import org.java_websocket.WebSocket;
    import org.java_websocket.handshake.ClientHandshake;
    import org.java_websocket.server.WebSocketServer;
    import java.net.InetSocketAddress;
    import java.util.Iterator;
    
    /**
    * 把今天最好的表现当作明天最新的起点..~
    * 
    * Today the best performance as tomorrow newest starter!
    *
    * @类描述: TODO(这里用一句话描述这个类的作用)
    * @author: <a href="mailto:duleilewuhen@sina.com">独泪了无痕</a>
    * @创建时间: 2020/11/27 13:41
    * @版本: V 1.0.1
    * @since: JDK 1.8
    */
    public class MsgWebSocketServer extends WebSocketServer {
    
       public MsgWebSocketServer(int port) {
           super(new InetSocketAddress(port));
       }
    
       /**
        * websocket进行握手之后调用,并且给WebSocket写做准备
        *
        * @param webSocket
        * @param clientHandshake
        */
       @Override
       public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
           System.out.println("---onOpen---"+webSocket.isOpen()+"--"+webSocket.getReadyState()+"--"+webSocket.getAttachment());
    
           for(Iterator<string> it = clientHandshake.iterateHttpFields(); it.hasNext();) {
               String key = it.next();
               System.out.println(key+":"+clientHandshake.getFieldValue(key));
           }
    
       }
    
       /**
        * WebSocket连接关闭时调用
        *
        * @param webSocket
        * @param i
        * @param s
        * @param b
        */
       @Override
       public void onClose(WebSocket webSocket, int i, String s, boolean b) {
           System.out.println("------------------onClose-------------------");
       }
    
       @Override
       public void onMessage(WebSocket webSocket, String message) {
           System.out.println("收到消息:"+message);
           //收到什么消息,回复什么
           webSocket.send(message);
           if(webSocket.isClosed()) {
    
           } else if (webSocket.isClosing()) {
               System.out.println("ws连接正在关闭...");
           } else if(webSocket.isOpen()) {
               System.out.println("ws连接已打开...");
               System.out.println(webSocket);
           }
       }
    
       /**
        * 错误发生时调用。
        *
        * @param webSocket
        * @param e
        */
       @Override
       public void onError(WebSocket webSocket, Exception e) {
           System.out.println("------------------onError-------------------");
           if(webSocket != null) {
           }
           e.getStackTrace();
       }
    
       /**
        * 当服务器成功启动时调用
        */
       @Override
       public void onStart() {
           System.out.println("------------------onStart-------------------");
       }
    }
    

    服务端基于4个注解实现,如下所是:

    注解说明
    @OnOpen被该注解注释的方法,将在客户端与服务端建立连接时执行
    @OnMessage被该注解注释的方法,将在服务端收到消息时执行
    @OnClose被该注解注释的方法,将在链接关闭时执行
    @OnError被该注解注释的方法,将在链接发生错误时执行
  3. 启动类

    /**
     * 把今天最好的表现当作明天最新的起点..~
     * 
     * Today the best performance as tomorrow newest starter!
     *
     * @类描述: TODO(这里用一句话描述这个类的作用)
     * @author: <a href="mailto:duleilewuhen@sina.com">独泪了无痕</a>
     * @创建时间: 2020/11/27 13:48
     * @版本: V 1.0.1
     * @since: JDK 1.8
     */
    public class WsServer {
        public static void main(String[] args) {
            new MsgWebSocketServer(8290).start();
        }
    }
    
  4. 访问页面

    
    
    	
    		<meta charset="UTF-8">
    		<title>HTML5 WebSocket</title>
    		<script type="text/javascript">
    			function websocketTest() {
    				let webSocket;
    				let commWebSocket;
    				if ("WebSocket" in window) {
    					webSocket = new WebSocket("ws://localhost:8205/websocket/webChat");
    					//连通之后的回调事件
    					webSocket.onopen = function() {
    						//webSocket.send( document.getElementById('username').value+"已经上线了");
    						console.log("已经连通了websocket");
    					};
    
    					//接收后台服务端的消息
    					webSocket.onmessage = function(evt) {
    						const received_msg = evt.data;
    						console.log("数据已接收:" + received_msg);
    					}
    
    					//连接关闭的回调事件
    					webSocket.onclose = function() {
    						console.log("连接已关闭...");
    					};
    				} else {
    					// 浏览器不支持 WebSocket
    					alert("您的浏览器不支持 WebSocket!");
    				}
    			}
    		</script>
    	
    	
    		<div id="see">
    			<a href="javascript:websocketTest()">运行 WebSocket</a>
    		</div>
    	
    
    

2.2 Java webSocket 客户端

  1. 所需pom依赖

    <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
    <dependency>
      <groupid>org.java-websocket</groupid>
      <artifactid>Java-WebSocket</artifactid>
      <version>1.5.1</version>
    </dependency>
    
  2. 客户端

    webSocket客户端的实现基于webSocketClient类实现,实例化webSocketClient并重写以下四个方法:

    onOpen与服务端建立连接时执行
    onMessage收到服务端消息时执行
    onClose连接关闭时执行
    onError发生错误时执行
    import lombok.extern.slf4j.Slf4j;
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
    import java.net.URI;
    import java.net.URISyntaxException;
    
    @Slf4j
    public final class MyWebSocketClient {
        /**
         * 创建webSocketClient客户端
         */
        private static WebSocketClient webSocketClient;
    
        /**
         * 创建webSocketClient客户端
         *
         * @param serverUri
         * @return
         * @throws URISyntaxException
         */
        public static WebSocketClient getWebSocketClient(String serverUri) throws URISyntaxException {
            webSocketClient = new WebSocketClient(new URI(serverUri)) {
                @Override
                public void onOpen(ServerHandshake serverHandshake) {
                    log.info("[websocket] 连接成功");
                }
    
                @Override
                public void onMessage(String message) {
                    log.info("[websocket] 收到消息={}", message);
                }
    
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    log.info("[websocket] 退出连接");
                }
    
                @Override
                public void onError(Exception exp) {
                    log.info("[websocket] 连接错误={}", exp.getMessage());
                }
            };
            return webSocketClient;
        }
    }
    
  3. 启动类

    通过参阅 webSocket API 文档,我们可以了解到,在Java中webSocket包含以下几种种状态:

    状态说明
    NOT_YET_CONNECTED表示该webSocket实例还未开始链接,并处于等待链接的状态
    OPEN链接已打开
    CLOSING链接正在关闭
    CLOSED表示链接关闭,该webSocket实例到了消亡的时候。

    不难看出,这几种种状态表明着webSocket的整个生命周期,这对于我们在使用webSocket时解决一些问题是非常关键的。

    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.enums.ReadyState;
    import org.joda.time.DateTime;
    import java.net.URISyntaxException;
    
    public class WsClient {
        public static void main(String[] args) throws URISyntaxException, InterruptedException {
    
            String serverUri = "ws://localhost:8290";
            // 创建webSocketClient客户端
            WebSocketClient client = MyWebSocketClient.getWebSocketClient(serverUri);
             client.connect();
    
            // 检测连接状态,重复尝试连接
            while (!client.getReadyState().equals(ReadyState.OPEN)) {
                System.out.println("连接状态:" + client.getReadyState());
                Thread.sleep(1000);
                if (client.getReadyState().equals(ReadyState.CLOSING) ||     client.getReadyState().equals(ReadyState.CLOSED)) {
                   client.reconnect();
                }
            }
    
            client.send("测试数据!");
    
            while (client.getReadyState() == ReadyState.OPEN) {
                client.send("测试数据!" + new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
                Thread.sleep(1000);
            }
    
            client.close();
        }
    }
    

当webSocketClient初始化完毕之后,webSocketClient提供了两种链接方式,分别是connectreconnect 。这两者虽然都是没有链接的状态,但本质上是有区别的。webSocket想要链接,则只能在 NOT_YET_CONNECTED 状态下进行,一旦状态改变,则无法再次链接,只能reconnect链接。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独泪了无痕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值