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访问

在开一个浏览器窗口

相关推荐
im_AMBER1 小时前
HTTP概述 01
javascript·网络·笔记·网络协议·学习·http
John_Rey2 小时前
API 设计哲学:构建健壮、易用且符合惯用语的 Rust 库
网络·算法·rust
大公产经晚间消息2 小时前
网易云音乐回应“不适配鸿蒙”:推动相关部门加快步伐
网络
这个人需要休息3 小时前
TCP/IP 协议栈
服务器·网络·网络协议·tcp/ip
kkce3 小时前
快快测(KKCE)TCping 检测全面升级:IPv6 深度覆盖 + 多维度可视化,重构网络性能监测新体验
服务器·网络·重构
NiKo_W4 小时前
Linux 传输层协议
linux·运维·网络·tcp协议·传输层·udp协议
xixixi777774 小时前
攻击链重构的技术框架
网络·安全·重构
cxr8285 小时前
深度解析顶级 Doc Agent System Prompt 的架构与实践
网络·人工智能·架构·prompt·ai智能体·ai赋能·上下文工程
weixin_417190555 小时前
一、UDP以太网帧格式
网络·网络协议·udp