websocket(即时通讯)

文章目录

什么是websocket?

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

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

为什么有了HTTP协议还要WebSocket

HTTP协议采用的是客户端(浏览器)轮询的方式,即客户端发送请求,服务端做出响应,为了获取最新的数据,需要不断的轮询发出HTTP请求,占用大量带宽。

WebSocket采用了一些特殊的报头,使得浏览器和服务器只需要通过"握手"建立一条连接通道后,此链接保持活跃状态,之后的客户端和服务器的通信都使用这个连接,解决了Web实时性的问题,相比于HTTP有以下好处:

  • 一个Web客户端只建立一个TCP连接
  • WebSocket服务端可以主动推送(push)数据到Web客户端
  • 有更加轻量级的头,减少了数据传输量

特点

  1. 建立在TCP协议只上,服务端比较容易实现
  2. 于HTTP协议有良好的兼容性,默认端口也是80和443,握手阶段使用HTTP协议,因此握手时不容
    易屏蔽,能通过各种HTTP代理服务器
  3. 数据格式轻量,通信高效且节省带宽
  4. 支持传输文本数据和二进制数据
  5. 没有同源限制,客户端可以与任意服务器通信
  6. 也支持加密传输,WS+SSL,URL形如wss://

先建一个springboot项目

代码演示

引入pom文件

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version> <!-- 请使用当前最新稳定版 -->
    <scope>provided</scope>
</dependency>

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

新建spring文件夹

com/hsh下新建

创建MyWsConfig

java 复制代码
package com.mc.wsdemo.spring;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import javax.annotation.Resource;

@Configuration
@EnableWebSocket
public class MyWsConfig implements WebSocketConfigurer {
    @Resource
    MyWsHandler myWsHandler;
    @Resource
    MyWsInterceptor myWsInterceptor;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWsHandler,"/myWs1").addInterceptors(myWsInterceptor).setAllowedOrigins("*");
    }
}

创建SessionBean

java 复制代码
package com.mc.wsdemo.spring;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.web.socket.WebSocketSession;

@AllArgsConstructor
@Data
public class SessionBean {
    private WebSocketSession webSocketSession;
    private Integer clientId;
}

创建MyWsHandler

java 复制代码
package com.mc.wsdemo.spring;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * web socket 主处理程序
 */
@Slf4j
@Component
public class MyWsHandler extends AbstractWebSocketHandler {
    private static Map<String,SessionBean> sessionBeanMap ;
    private static AtomicInteger clientIdMaker;
    private static StringBuffer stringBuffer;
    static {
        sessionBeanMap = new ConcurrentHashMap<>();
        clientIdMaker = new AtomicInteger(0);
        stringBuffer = new StringBuffer();
    }
    //连接建立
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // (会话,id)
        SessionBean sessionBean = new SessionBean(session,clientIdMaker.getAndIncrement());
        // 加入map集合
        sessionBeanMap.put(session.getId(),sessionBean);
        // 打印日志
        log.info(sessionBeanMap.get(session.getId()).getClientId()+"建立了连接");
        // 谁进入群聊  String message = id+进入了群聊
        stringBuffer.append(sessionBeanMap.get(session.getId()).getClientId()+"进入了群聊<br/>");
        // 调用群发的方法  sendMessage(message)
        sendMessage(sessionBeanMap);
    }
    //收到消息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        log.info(sessionBeanMap.get(session.getId()).getClientId()+":"+message.getPayload());
        // String message = id+"前端发送的消息"
        stringBuffer.append(sessionBeanMap.get(session.getId()).getClientId()+":"+message.getPayload()+"<br/>");
        // 调用群发方法
        sendMessage(sessionBeanMap);
    }
    //传输异常
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        super.handleTransportError(session, exception);
        if(session.isOpen()){
            session.close();
        }
        sessionBeanMap.remove(session.getId());
    }
    //连接关闭
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        int clientId = sessionBeanMap.get(session.getId()).getClientId();
        sessionBeanMap.remove(session);
        log.info(clientId+"关闭了连接");
        stringBuffer.append(clientId+"退出了群聊<br/>");
        sendMessage(sessionBeanMap);
    }

//    //每2s发送给客户端心跳消息
//    @Scheduled(fixedRate = 2000)
//    public void sendMsg() throws IOException {
//        for(String key:sessionBeanMap.keySet()){
//            sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage("心跳"));
//        }
//    }

    /**
     * 群发方法(包括谁进入聊天室,谁发送了信息,谁退出了聊天室)
     * @param sessionBeanMap
     */
    public void sendMessage(Map<String,SessionBean> sessionBeanMap){
        for(String key:sessionBeanMap.keySet()){
            try {
                sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage(stringBuffer.toString()));
            } catch (IOException e) {
//                e.printStackTrace();
                log.error(e.getMessage());
            }
        }
    }
}

创建MyWsInterceptor

java 复制代码
package com.mc.wsdemo.spring;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Map;

/**
 * 握手拦截器
 */
@Component
@Slf4j
public class MyWsInterceptor extends HttpSessionHandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        log.info(request.getRemoteAddress().toString()+"开始握手");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
        log.info(request.getRemoteAddress().toString()+"完成握手");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

在static文件夹下新建ws-client.html

resources/static文件夹下新建ws-client.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws client</title>
</head>
<body>
<p style="border:1px solid black;width: 600px;height: 500px" id="talkMsg"></p>
<input id="message" /><button id="sendBtn" onclick="sendMsg()">发送</button>
</body>
<script>
    // 给后端发消息说我建立连接
    let ws = new WebSocket("ws://localhost:8080/myWs1")
    // ws.onopen=function () {
    // }
    // 后端给前端发消息(相当于是前端监听作用,有消息进来就在P标签中  展示)
    ws.onmessage=function (message) {
        document.getElementById("talkMsg").innerHTML = message.data
    }
    // 前端给后端发消息(点击发送按钮)
    function sendMsg() {
        ws.send(document.getElementById("message").value)
        document.getElementById("message").value=""
    }
</script>
</html>

测试

输入http://localhost:8080/ws-client.html访问

在开一个浏览器窗口

相关推荐
molaifeng8 小时前
Go 语言如何实现高性能网络 I/O:Netpoller 模型揭秘
开发语言·网络·golang
知乎的哥廷根数学学派9 小时前
基于多模态特征融合和可解释性深度学习的工业压缩机异常分类与预测性维护智能诊断(Python)
网络·人工智能·pytorch·python·深度学习·机器学习·分类
网络工程师_ling9 小时前
【 Elastiflow (ELK) 网络流量分析系统 部署教程】
网络·elk
2301_7807896610 小时前
高防 IP 的选择与配置确保业务稳定性
网络·网络协议·tcp/ip
willhuo10 小时前
基于xray的匿名、授权、IP白名单代理访问研究
服务器·网络·tcp/ip
qiuqyue10 小时前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
linux·运维·网络
无名38710 小时前
关于 VRF
网络·通信
YounGp_oo11 小时前
一次内网开发环境访问方式的改进实践:使用 FRP 替代远程桌面
网络·ssh·frp·内网穿透·开发环境
云安全干货局12 小时前
服务器被攻击后如何快速恢复?数据备份 + 应急响应手册
网络·网络安全·云服务器·弹性云服务器
猿饵块12 小时前
tcp--抓包--wireshark
网络·测试工具·wireshark