WebSocket

前言

逆天5月20号,不知道是什么时候开始兴起的这一天,让我在本就繁忙的学习生活中又添加了不少的狗粮🙃🙃🙃

正题:
WebSocket是基于TCP的一种新的网络协议,它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,
两者之间就可以创建持久性的连接,并进行双向数据传输(协议名:ws)

HTTP与WebSocket对比

HTTP

http的请求与响应是客户端发起请求,服务端处理请求,发出响应(请求响应模式,称为短连接)。也就是说这个完整的过程需要最先由客户端发起
客户端发送一个,后端响应一个。像极了我和女神聊天一样的🤡🤡🤡(请求响应的聊天方式,简称:http式聊天)

WebSocket

首先客户端发送请求,客户端发出握手
后端发出应答
建立完成后,客户端可以主动发请求,后端也可以主动发请求

总结

  • http是短连接
  • WebSocket是长连接
  • http通信是单向的,基于请求响应模式
  • WebSocket是双向通信
  • WebSocket和http底层都是TCP协议

应用场景

  • 视频弹幕
  • 网页聊天
  • 体育实况更新
  • 股票基金报价实时更新

WebSocket有以下特点:

是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式。
HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)

实现步骤

  1. 直接使用websocket.html页面作为WebSocket客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
    </head>
    <body>
    <input id="text" type="text" />
    <button onclick="send()">发送消息</button>
    <button onclick="closeWebSocket()">关闭连接</button>
    <div id="message">
    </div>
    </body>
    <script type="text/javascript">
    var websocket = null;
    var clientId = Math.random().toString(36).substr(2);

    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
    //连接WebSocket节点
    websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
    }
    else{
    alert('Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function(){
    setMessageInnerHTML("error");
    };

    //连接成功建立的回调方法
    websocket.onopen = function(){
    setMessageInnerHTML("连接成功");
    }

    //接收到消息的回调方法
    websocket.onmessage = function(event){
    setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function(){
    setMessageInnerHTML("close");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
    websocket.close();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
    document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //发送消息
    function send(){
    var message = document.getElementById('text').value;
    websocket.send(message);
    }

    //关闭连接
    function closeWebSocket() {
    websocket.close();
    }
    </script>
    </html>

  2. 导入WebSocket的maven坐标

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 导入WebSocket服务端组件WebSocketServer,用于和客户端通信
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    package com.sky.websocket;

    import org.springframework.stereotype.Component;
    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * WebSocket服务
    */
    @Component
    @ServerEndpoint("/ws/{sid}")
    public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
    * 连接建立成功调用的方法
    */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
    System.out.println("客户端:" + sid + "建立连接");
    sessionMap.put(sid, session);
    }

    /**
    * 收到客户端消息后调用的方法
    *
    * @param message 客户端发送过来的消息
    */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
    System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
    * 连接关闭调用的方法
    *
    * @param sid
    */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
    System.out.println("连接断开:" + sid);
    sessionMap.remove(sid);
    }

    /**
    * 群发
    *
    * @param message
    */
    public void sendToAllClient(String message) {
    Collection<Session> sessions = sessionMap.values();
    for (Session session : sessions) {
    try {
    //服务器向客户端发送消息
    session.getBasicRemote().sendText(message);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    }

  2. 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.sky.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;

    /**
    * WebSocket配置类,用于注册WebSocket的Bean
    */
    @Configuration
    public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
    return new ServerEndpointExporter();
    }

    }

  3. 导入定时任务类WebSocketTask,定时向客户端推送数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.sky.task;

    import com.sky.websocket.WebSocketServer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;

    @Component
    public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
    * 通过WebSocket每隔5秒向客户端发送消息
    */
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
    webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
    }