迅速掌握常见通信协议

54 阅读3分钟

1 HTTP

传统的请求响应模式,客户端向服务端发请求,服务端才会返回响应,并且做完这一对操作,连接断开,需要频繁建立连接

优势:实现简单、兼容性好

缺点:服务端无法主动向客户端推送数据,部分场景需要客户端不断地轮训请求数据

image.png

2 WebSocket

全双工通信协议,允许建立一个持久连接,客户端和服务端都可以随意发送数据

优点:实时性强

缺点:需要处理一些握手、链接维护等问题,每个连接建立的资源较多

image.png

3 SSE

又叫Server Send Events,客户端需要先和服务端建立一个连接,一旦这个连接建立起来,服务端即可不断地向客户端推送数据

当然只适合于频繁单向通信的情况,在某些情况下,浏览器可能对SSE连接数有限制,这可能会影响大规模应用的性能

image.png

3.1 SSE基本使用

1 定义SSESever

在这里我们需要定义一些方法:例如连接建立、发送消息等等,每个用户的连接都会创建一个单独的sse对象,我们可以缓存起来;同时SseEmitter提供了很多编程型方法实现来处理注册调的结果

public class SSESever {

    private static final Map<String, SseEmitter> sceClinents = new ConcurrentHashMap<>();

    public static SseEmitter connect(String userId){
        SseEmitter sseEmitter = new SseEmitter(0L);

        //注册回调
        sseEmitter.onTimeout(timeOutCallback(userId));
        sseEmitter.onCompletion(completeCallback(userId));
        sseEmitter.onError(errorCallback(userId));

        sceClinents.put(userId, sseEmitter);
        log.info("用户{}建立SSE连接", userId);
        return sseEmitter;
    }

    public static Runnable timeOutCallback(String userId){
        return () -> {
            sceClinents.remove(userId);
            log.info("用户{}移除SSE连接", userId);
        };
    }

    public static Runnable completeCallback(String userId){
        return () -> {
            sceClinents.remove(userId);
            log.info("用户{}移除SSE连接", userId);
        };
    }

    public static Consumer<Throwable> errorCallback(String userId){
        return throwable -> {
            sceClinents.remove(userId);
            log.info("用户{}移除SSE连接", userId);
        };
    }

    public static void sendMessage(String userId, String message, SSEMsgType msgType){
        if (CollectionUtils.isEmpty(sceClinents)){
            return;
        }
        SseEmitter sseEmitter = sceClinents.get(userId);
        if(sseEmitter != null){
            sendEmitterMessage(sseEmitter, userId, message, msgType);
        }
    }

    private static void sendEmitterMessage(SseEmitter sseEmitter, String userId, String message, SSEMsgType msgType){
        try {
            SseEmitter.SseEventBuilder event = SseEmitter.event()
                    .id(userId)
                    .data(message)
                    .name(msgType.type);
            sseEmitter.send(event);
        } catch (Exception e){
            log.error("用户{}发送SSE消息失败", userId);
            sceClinents.remove(userId);
        }
    }

2 前后端交互 后端实现controller(注意:Accept需要传入text/event-stream)

@GetMapping(path = "connect",produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public SseEmitter connect(@RequestParam String userId) {
    return SSESever.connect(userId);
}

@GetMapping(path = "sendMessage")
public Object connect(@RequestParam String userId,@RequestParam String msg) {
    SSESever.sendMessage(userId,msg, SSEMsgType.MESSAGE);
    return "ok";
}

前端使用EventSource即可完成消息接受和连接建立

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE 客户端示例</title>
</head>
<body>
    <h1>SSE 客户端示例1</h1>
    <div id="messages"></div>

</body>
</html>

<script>

    let source = null;
    let userId = Math.random().toString(36).substr(2, 9); // 生成一个随机字符串作为 userId
    if (window.EventSource) {
        // 创建一个EventSource对象,建立SSE连接
        source = new EventSource('http://10.1.74.152:9999/sse/connect?userId=' + userId);
                                  // http://127.0.0.1:5501/

        // 建立连接成功,则会触发open事件
        source.addEventListener('open', function(e) {
            console.log('建立连接成功...');

            var text = document.getElementById('messages').innerHTML;
            text += '<br> 建立连接成功...';
            document.getElementById('messages').innerHTML = text;

        }, false);

        // 客户端接收到服务器发来的消息时,会触发message事件
        source.addEventListener('message', function(e) {
            var msg = e.data;

            var text = document.getElementById('messages').innerHTML;
            text += '<br>' + msg;
            document.getElementById('messages').innerHTML = text;

        }, false);

        // add 事件,stream 流式推送
        source.addEventListener('add', function(e) {
            var msg = e.data;

            var text = document.getElementById('messages').innerHTML;
            text += '<br>' + msg;
            document.getElementById('messages').innerHTML = text;

        }, false);

        // 用户自定义事件
        source.addEventListener('custom_event', function(e) {
            var msg = e.data;

            var text = document.getElementById('messages').innerHTML;
            text += '<br>' + "自定义事件:" + msg;
            document.getElementById('messages').innerHTML = text;

        }, false);

        // SSE完成时调用的事件
        source.addEventListener('finish', function(e) {
            console.log('当前SSE事件推送完毕...');
        }, false);

        // 连接发生错误时,会触发error事件
        source.addEventListener('error', function(e) {
            if (e.readyState == EventSource.CLOSED) {
                console.log('连接已关闭...');

                var text = document.getElementById('messages').innerHTML;
                text += '<br> 连接已关闭...';
                document.getElementById('messages').innerHTML = text;

            } else {
                console.log('发生错误...');
            }
        })

    } else {
        console.log('您的浏览器不支持SSE');
        // closeSSE();
    }

    function closeSSE() {
        source.close();
    }

</script>