websocket

一、介绍

维基百科简绍(https://zh.wikipedia.org/wiki/WebSocket

w3c简绍  https://www.runoob.com/html/html5-websocket.html

WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型应用层。WebSocket协议在2011年由IETF标准化为RFC 6455,后由RFC 7936补充规范。Web IDL中的WebSocket API由W3C标准化。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

WebSocket 通过 HTTP 端口80 和 443 进行工作,与HTTP 不同的是WebSocket 提供全双工通信,WebSocket 将 ws (WebSocket) 和 wss (WebSocket Secure) 定义为两个新的统一资源标识符,分别对应明文和加密连接。

早期,很多网站实现推送技术所用的都是轮询。即浏览器需要不断的向服务器发出请求,然而Http请求与恢复包含较长的头部,而真正有效的数据只有很小的部分,会消耗很多带宽资源。

websocket 在默认情况下使用80端口,运行在TLS 上时,默认使用443 端口。

二、握手协议

WebSocket 是独立的,创建在 TCP 上的协议

WebSocket 通过 HTTP/1.1 协议的 101 状态进行握手

请求

Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: Upgrade
Host: 127.0.0.1:8080
Origin: http://localhost:8080
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: hI8iVpJEo27FDI+3i+q+Ow==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36

回复

Connection: upgrade
Date: Thu, 17 Sep 2020 11:28:59 GMT
Sec-WebSocket-Accept: dm7en9BqxezYWTsXqbCpNi/gTAE=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
Upgrade: websocket

Connection 必须设置Upgrade ,标识客户端希望连接升级

Upgrade 字段必须设置WebSocket ,表示希望升级到WebSocket协议

 WebSocket 属性:

Sokcet .readyState  

  • 0 连接尚未建立
  • 1 连接已经建立,可以进行通信
  • 2 连接正在进行关闭
  • 3 连接已经关闭或者不能打开

事件

  • open 连接建立时触发
  • message 客户端收到服务端数据时触发
  • error 通信错误触发
  • close 连接关闭时触发

三、spring boot 整合 websocket

添加maven 依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

添加配置

@Configuration
@EnableWebSocket
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpoint(){
        return new ServerEndpointExporter();
    }
}

服务端监听

@ServerEndpoint(value = "/socketServer/{userId}",encoders = {ServerEncoder.class})
@Component
@Slf4j
@Data
public class WebServerEndpoint {
    private static ConcurrentHashMap<String, Session> map = new ConcurrentHashMap<>();
    private static CopyOnWriteArraySet<WebServerEndpoint> sessionSet = new CopyOnWriteArraySet<>();
    private Session session;

    @OnOpen
    public void onOpen(@PathParam("userId")String userId, Session session){
        if(userId!=null){
            map.put(userId,session);
        }
        this.session = session;
        sessionSet.add(this);
        try {
            this.sendMessage("连接成功!", session);
        }catch (Exception e){
            System.out.println("ws IO异常");
        }
    }

    @OnClose
    public void onClose(Session session){
        sessionSet.remove(this);
        Collection<Session> values = map.values();
        values.remove(this);
        this.sendMessage("连接关闭:",session);
    }

    @OnMessage
    public void OnMessage(String message,Session session){
        sendBatch(message);
    }

    @OnError
    public void  onError(Throwable error,Session session){
        log.error("服务端错误");
        this.sendMessage("服务端错误:"+error.getMessage(),session);
    }


    public void sendMessage(String msg,Session session){
        log.info("服务端发送消息:"+ msg);
        try {
            session.getBasicRemote().sendText(msg);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 单独发送消息
     */
    public String sendOne(String userId,String message){
        if(map.containsKey(userId) && map.get(userId)!=null){
            sendMessage(message,map.get(userId));
            return "success";
        }else{
            log.info("用户不存在");
            return "用户不存在";
        }
    }

    /**
     * 群发自定义消息
     */
    public void sendBatch(String message){
        log.info("【sendBatch】:"+message);
        sessionSet.stream().forEach(
                item->item.sendMessage(message,item.getSession())
        );
    }

    /**
     * 发送对象
     * @param messageVo
     * @param session
     */
    public void sendObject(MessageVO messageVo,Session session){
        try {
            session.getBasicRemote().sendObject(messageVo);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 发送对象信息
     * @param message
     */
    public void sendBatchObject(MessageVO message){
        sessionSet.stream().forEach(
                item->item.sendObject(message,item.getSession())
        );
    }
}

客户端H5

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <div style="line-height: 50px;">
            <h1 style="color: red;display: inline;">websocket 测试 </h1>
            <h4 id="userId" style="color: darkviolet;display: inline;"></h4>
            
        </div>
        <div>
            <button onclick="connectWs()">建立连接</button>
            <button onclick="closeWs()">断开连接</button>
            <input id="text" type="text"/> 
            <button onclick="send()">客户端发送</button>
            <!--<button onclick="location.href='localhost:8080/sendBatch5'">服务端广播:5条</button>-->
        </div>
        <h4 style="color: cornflowerblue;">操作信息:</h4>
        <div style=" 100%;">
            <div id= "timeDiv" style=" 10%;float: left;"></div>
            <div id="msgDiv" style="color:green; 40%;float: left;"></div>
        </div>
        
    <script>
        var ws = null;
        
        /*打开即建立连接*/
        if ('WebSocket' in window) {
            document.getElementById("userId").innerHTML +=getUuid();
            var userId = document.getElementById("userId").innerHTML;
            ws = new WebSocket("ws://127.0.0.1:8080/socketServer/"+userId);
          }        
           else {
            alert('Not support websocket')
        }
        
        //格式化时间
        function dateFormat(fmt, date) {
            let ret;
            const opt = {
            "Y+": date.getFullYear().toString(),        //
            "m+": (date.getMonth() + 1).toString(),     //
            "d+": date.getDate().toString(),            //
            "H+": date.getHours().toString(),           //
            "M+": date.getMinutes().toString(),         //
            "S+": date.getSeconds().toString()          //
            };
            for (let k in opt) {
                    ret = new RegExp("(" + k + ")").exec(fmt);
                if (ret) {
                    fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
                };
                };
            return fmt;
        }
        
        
        //连接发送错误回调方法
        ws.onerror = function () {
            setMessageInnerHTML("error");
        };
        
        //收到消息调用
        ws.onmessage = function getMsg(event){
            setMessageInnerHTML(event.data);
        }
        
        //建立连接调用
        ws.onopen = function(event){
            setMessageInnerHTML("open");
        }
        
        
        //关闭连接调用
        ws.onclose = function(){
            setMessageInnerHTML("close");
        }
        
        //监听窗口,窗口关闭,主动关闭连接
        window.onbeforeunload = function(){
            ws.close();
        }
          
        //将消息显示在页面
        function setMessageInnerHTML(msg) {
            document.getElementById("timeDiv").innerHTML+=dateFormat("YYYY-mm-dd HH:MM:SS", new Date())+"<br>";
            document.getElementById('msgDiv').innerHTML +=msg + '<br/>';
        }
        
        //关闭连接
        function closeWs(){
            if(ws==null || ws.readyState!=1){
                alert("连接未打开");
                return;
            }
            setMessageInnerHTML("客户端关闭连接");
            document.getElementById("userId").innerHTML ="";
            ws.close();
        }
        
        //建立连接
        function connectWs(){
            if(ws!=null && ws.readyState ==1){
                alert("连接已经建立");
                return;
            }
            document.getElementById("userId").innerHTML =getUuid();
            var userId = document.getElementById("userId").innerHTML;
            ws = new WebSocket("ws://127.0.0.1:8080/socketServer/"+userId);
            setMessageInnerHTML("客户端建立连接");
            
            ws.onclose = function(){
                setMessageInnerHTML("close");
            }
            
            ws.onopen = function(event){
                setMessageInnerHTML("open");
            }
            
            ws.onmessage = function getMsg(event){
                setMessageInnerHTML(event.data);
            }
        }
        
        //发送消息
        function send(){
            if(ws==null || ws.readyState!=1){
                alert("连接未打开!");
                return;
            }
            var message = document.getElementById("text").value;
            var message = document.getElementById("userId").innerHTML+"-"+message;
            ws.send(message);
        }
    
      //生成唯一随机数
      function getUuid() {
        var s = [];
        var hexDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        for (var i = 0; i < 10; i++) {
          s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
        }
        s[14] = "4"
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
        //s[8] = s[13] = s[18] = s[23] = "-"
        let uuid = s.join("")
        return uuid
      }
    </script>
    
    </body>
</html>
View Code

github:https://github.com/baizhuang/springboot-websocket

原文地址:https://www.cnblogs.com/bytecodebuffer/p/13687101.html