Websocket——化神篇

WebSocket机制

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;

WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

和socket区别

Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。

WebSocket则是一个典型的应用层协议。

区别

Socket是传输控制层协议,WebSocket是应用层协议。

前世今生

众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现,这种机制对于信息变化不是特别频繁的应用尚可,但对于实时要求高、海量并发的应用来说显得捉襟见肘,尤其在当前业界移动互联网蓬勃发展的趋势下,高并发与用户实时响应是 Web 应用经常面临的问题,比如金融证券的实时信息,Web 导航应用中的地理位置获取,社交网络的实时消息推送等。

消息推送常用方式

轮询方式

1.轮询 是一种客户端与服务器之间实时通信的技术手段。客户端定期发送请求来查询服务器是否有新数据或事件,并将响应返 回给客户端。如果服务器有新的数据或事件,则将其返回给客户端;如果没有,则返回一个空响应。客户端收到响应后,可 以处理数据或事件,并根据需要继续发送下一个请求。

2.长轮询 是一种改进的轮询技术,其主要目的是降低轮询过程中的资源消耗和延迟。长轮询的基本原理是客户端发送一个 HTTP请求给服务器,并保持连接打开,直到服务器有新的数据或事件时才返回响应给客户端。在这期间,服务器会一直保持连接打开,直到超时或有新数据或事件(HTTP1.1版本就是这个性质长连接)
SSE(服务器发送事件)

SSE在服务器和客户端之间打开一个单向通道

服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息

服务器有数据变更时将数据流式传输到客户端

WebSocket


客户端API

服务端 API



Endpoint示例

java 复制代码
@ServerEndpoint("/chat")
@Component
public class ChatEndpoint {
    @OnOpen
    //连接建立时被调用
    public void onOpen(Session session, EndpointConfig config){}
    @OnMessage
    //接收到客户端发送的数据时被调用
    public void onMessage(String message){}
    @OnClose
    //连接关闭时被调用
    public void onClose(Session session){}
}

ChatEndpoint类通过实现WebSocket协议,用于处理客户端的连接、消息传递和关闭事件。

在线聊天室实现

流程分析

消息格式

引入坐标

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

编写配置类

java 复制代码
@Configuration
public class WebsocketConfig {

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

通过在配置类中定义一个 ServerEndpointExporter 的 @Bean 方法,Spring 会自动创建一个 ServerEndpointExporter 实例,并将其加入到 Spring 容器中。Spring框架会在启动时通过这个实例自动扫描项目中所有使用@ServerEndpoint注解的类,并将它们注册为WebSocket端点。
编写配置类,用于获取 HttpSession 对象

java 复制代码
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取HttpSession对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        //将httpSession对象保存起来
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}

GetHttpSessionConfig类用于在WebSocket握手过程中获取HTTP会话(HttpSession)对象,并将其保存到用户属性中,以便在WebSocket会话中使用。

ChatEndpoint类

java 复制代码
package com.itheima.ws;

import com.alibaba.fastjson.JSON;
import com.itheima.config.GetHttpSessionConfig;
import com.itheima.utils.MessageUtils;
import com.itheima.ws.pojo.Message;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @version v1.0
 * @ClassName: ChatEndpoint
 * @Description: TODO(一句话描述该类的功能)
 * @Author: 黑马程序员
 */
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {

    private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();

    private HttpSession httpSession;

    /**
     * 建立websocket连接后,被调用
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        //1,将session进行保存
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.put(user,session);
        //2,广播消息。需要将登陆的所有的用户推送给所有的用户
        String message = MessageUtils.getMessage(true,null,getFriends());
        broadcastAllUsers(message);
    }

    public Set getFriends() {
        Set<String> set = onlineUsers.keySet();
        return set;
    }

    private void broadcastAllUsers(String message) {
        try {
            //遍历map集合
            Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();
            for (Map.Entry<String, Session> entry : entries) {
                //获取到所有用户对应的session对象
                Session session = entry.getValue();
                //发送消息
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            //记录日志
        }
    }

    /**
     * 浏览器发送消息到服务端,该方法被调用
     *
     * 张三  -->  李四
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        try {
            //将消息推送给指定的用户
            Message msg = JSON.parseObject(message, Message.class);
            //获取 消息接收方的用户名
            String toName = msg.getToName();
            String mess = msg.getMessage();
            //获取消息接收方用户对象的session对象
            Session session = onlineUsers.get(toName);
            String user = (String) this.httpSession.getAttribute("user");
            String msg1 = MessageUtils.getMessage(false, user, mess);
            session.getBasicRemote().sendText(msg1);
        } catch (Exception e) {
            //记录日志
        }
    }

    /**
     * 断开 websocket 连接时被调用
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        //1,从onlineUsers中剔除当前用户的session对象
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.remove(user);
        //2,通知其他所有的用户,当前用户下线了
        String message = MessageUtils.getMessage(true,null,getFriends());
        broadcastAllUsers(message);
    }
}

@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class) 这行代码声明了一个 WebSocket 端点,客户端可以通过 /chat 路径与之建立连接,并且在握手阶段使用 GetHttpSessionConfig 类来进行自定义配置。

该类是Spring Boot应用中的一个WebSocket端点,它用于处理聊天功能。主要功能包括:

  • 在用户建立WebSocket连接时保存用户信息和会话对象。
  • 当用户发送消息时,将消息转发给指定的接收用户。
  • 当用户断开连接时,从在线用户列表中移除用户并通知其他用户。
  • 使用GetHttpSessionConfig配置器来获取HTTP会话中的用户信息。
    userController
java 复制代码
package com.itheima.controller;

import com.itheima.pojo.Result;
import com.itheima.pojo.User;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;


@RestController
@RequestMapping("user")
public class UserController {

    /**
     * 登陆
     * @param user 提交的用户数据,包含用户名和密码
     * @param session
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpSession session) {
        Result result = new Result();
        if(user != null && "123".equals(user.getPassword())) {
            result.setFlag(true);
            //将数据存储到session对象中
            session.setAttribute("user",user.getUsername());
        } else {
            result.setFlag(false);
            result.setMessage("登陆失败");
        }
        return result;
    }

    /**
     * 获取用户名
     * @param session
     * @return
     */
    @GetMapping("/getUsername")
    public String getUsername(HttpSession session) {

        String username = (String) session.getAttribute("user");
        return username;
    }
}

Result

java 复制代码
package com.itheima.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: Result
 * @Description: 用来封装http请求的响应数据
 * @Author: 黑马程序员
 */
@Data
public class Result {
    private boolean flag;
    private String message;
}

User

java 复制代码
package com.itheima.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: User
 * @Description: 接收登录请求的数据
 * @Author: 黑马程序员
 */
@Data
public class User {

    private String userId;
    private String username;
    private String password;
}

MessageUtils

java 复制代码
package com.itheima.utils;

import com.alibaba.fastjson.JSON;
import com.itheima.ws.pojo.ResultMessage;

/**
 * @version v1.0
 * @ClassName: MessageUtils
 * @Description: 封装json格式消息的工具类
 * @Author: 黑马程序员
 */
public class MessageUtils {

    public static String getMessage(boolean isSystemMessage,String fromName, Object message) {

        ResultMessage result = new ResultMessage();
        result.setSystem(isSystemMessage);
        result.setMessage(message);
        if(fromName != null) {
            result.setFromName(fromName);
        }
        return JSON.toJSONString(result);
    }
}

Message

java 复制代码
package com.itheima.ws.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: Message
 * @Description: 用于封装浏览器发送给服务端的消息数据
 * @Author: 黑马程序员
 */
@Data
public class Message {
    private String toName;
    private String message;
}

ResultMessage

java 复制代码
package com.itheima.ws.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: ResultMessage
 * @Description: 用来封装服务端给浏览器发送的消息数据
 * @Author: 黑马程序员
 */
@Data
public class ResultMessage {

    private boolean isSystem;
    private String fromName;
    private Object message;//如果是系统消息是数组
}
相关推荐
神一样的老师1 分钟前
基于TSN的混合5G网络中的确定性通信研究需求与综述
网络
Feng.Lee7 分钟前
性能测试中CPU风险诊断方法有哪些
服务器·网络·性能优化
黑客老陈30 分钟前
区块链 智能合约安全 | 回滚攻击
运维·服务器·网络·安全·区块链·php·智能合约
ICT技术最前线1 小时前
路由器缓冲区如何调节的指南说明
网络·智能路由器
北'辰1 小时前
使用EVE-NG-锐捷实现静态路由
运维·网络
小筱在线2 小时前
Spring Boot安全加固:基于Spring Security的权限管理
spring boot·安全·spring·spring security
昔我往昔2 小时前
Spring Boot中如何自定义属性配置
java·spring boot·后端
coding侠客2 小时前
Spring Boot Starter探秘:全面了解 spring-boot-starter-web
spring boot·后端
一只淡水鱼662 小时前
【spring boot统一功能处理】拦截器
java·spring boot·后端·拦截器
我真不会起名字啊4 小时前
“深入浅出”系列之数通篇:(5)TCP的三次握手和四次挥手
网络·网络协议·tcp/ip