什么是WebSocket?

因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端
在这里插入图片描述

快速上手

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

WebSocketConfig启用WebSocket的支持

/**
 * 开启WebSocket支持
 * @author zhucan
 */
@Configuration
public class WebSocketConfig {

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

}

WebSocketServer
因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

直接@ServerEndpoint("/imserver/{userId}") 、@Component启用即可,然后在里面实现@OnOpen开启连接,@onClose关闭连接,@onMessage接收消息等方法。

新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便IM之间对userId进行推送消息。单机版实现到这里就可以。

集群版(多个ws节点)还需要借助mysql或者redis等进行处理,改造对应的sendMessage方法即可。

/**
 * @author
 */
@ServerEndpoint("/imserver/{userId}")
@Component
public class WebSocketService {


    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketService> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收userId
     */
    private String userId = "";

    /**
     * 连接建立成功调用的方法
     * <p>
     * 1.用map存 每个客户端对应的MyWebSocket对象
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            webSocketMap.put(userId, this);
            //加入set中
        } else {
            webSocketMap.put(userId, this);
            //加入set中
        }
        //服务端向客户端发送信息
        sendMessage("用户" + userId + "上线了,当前在线人数为:" + webSocketMap.size() + "。");
    }


    /**
     * 报错
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 实现服务器推送到对应的客户端
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 自定义 指定的userId服务端向客户端发送消息
     */
    public static void sendInfo(String message, String userId, String toUserId) {
        //log.info("发送消息到:"+userId+",报文:"+message);
        if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
            webSocketMap.get(toUserId).sendMessage(message);
        } else {
            webSocketMap.get(userId).sendMessage(toUserId + "已经下线。");
        }
    }

    /**
     * 自定义关闭
     *
     * @param userId
     */
    public static void close(String userId) {
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
        }
    }

    /**
     * 获取在线用户信息
     *
     * @return
     */
    public static Map getOnlineUser() {
        return webSocketMap;
    }

}

controller 对外提供发送 关闭websocket方法

@RestController
public class DemoController {



    @PostMapping("/push")
    public ResponseEntity<String> pushToWeb(String message, String toUserId,String userId) throws IOException {
        WebSocketService.sendInfo(message,userId,toUserId);
        return ResponseEntity.ok("MSG SEND SUCCESS");
    }

    @GetMapping("/close")
    public String close(String userId){
        WebSocketService.close(userId);
        return "ok";
    }


    @GetMapping("/getOnlineUser")
    public Map getOnlineUser(){
        return WebSocketService.getOnlineUser();
    }

}

html 前端页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    var socket;
    
    function openSocket(flag) {
        var socketUrl = "http://localhost:8449/imserver/" + $("#userId").val();
        socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
        if (socket != null) {
            socket.close();
            socket = null;
        }
        socket = new WebSocket(socketUrl);
        //打开事件
        socket.onopen = function () {
            //获得消息事件
            socket.onmessage = function (msg) {
                $('#contain').html(msg.data);
            };
            //发生了错误事件
            socket.onerror = function () {
                alert("websocket发生了错误");
            }
            $("#userId").attr("disabled", true);
        }
    }
    
    
    function sendMessage() {
        $.ajax({
            url:"http://localhost:8449/push",
            type:"POST",
            data:{
                'message':$("#contentText").val(),
                'toUserId':$("#toUserId").val(),
                'userId':$("#userId").val()
            }
        })
    }


    function closeSocket() {
        var userId =$("#userId").val()
        $.ajax({
            type:"get",
            url:"http://localhost:8449/close?userId="+userId,
            success:function(res) {
                alert('111');

            }
        });
        $("#userId").attr("disabled", false);
        $('#contain').html("下线成功!");
    }



</script>
<body>
<p>【内容】:<span id="contain"></span></p>
<p>【当前用户】:<div><input id="userId" name="userId" type="text" value="10"></div>
<p>【发送用户】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
<p>【发送的内容】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>【操作】:<div><button onclick="openSocket()">登录socket</button></div>
<p>【操作】:<div><button onclick="closeSocket()">关闭socket</button></div>
<p>【操作】:<div><button onclick="sendMessage()">发送消息</button></div>
</body>

</html>
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐