websocket的理解,写一个在线聊天室

64 阅读7分钟

对websocket的理解

websocket,也是应用层的网络通信协议,要理解它是什么,就要问为什么会出现这个协议

在没有websocket以前,我们都是用http协议做数据收发,渐渐的,我们发现有一些场景http的特性无法很好地处理。例如,服务端想主动发送给客户端信息的时候怎么办

例如,我们的在线游戏,音视频通讯,都需要即时地和服务器做信息交互。

如果用http来处理这个场景,就涉及到客户端方面的轮询。

但是轮询的逻辑是按照一定的频率,客户端不断地发送请求包,去看服务端是否“有话对我们说”。

这带来了两个主要的问题:

第一,服务端何时发送数据是不确定的,长时间持续地发送请求查询,其中大量的请求是无效的,网络利用率低下。

第二,对于需要实时发送的信息,轮询只能周期性地获取,如果服务端想要发送信息,需要等到下一个轮询周期才可以,会造成信息的延迟。如果是在线游戏,视频聊天什么的,用户当天就会打个差评再卸载掉。

所以,关键是要做到当服务端想发送的时候,就立刻主动地发送信息。

而websocket就能达成这一功能,它的特性是全双工通信,此外还有低延迟,数据包传输效率高的特性。这些特性都是相较于http协议来说的,你可以这么认为,想要专门应对需要高效全双工交互的应用场景,但http表示臣妾做不到啊,所以你新纳一个嫔妃websocket,专门帮你干这些活儿。

如果之前除了http协议以外没有接触过应用层协议,那么一开始是难以接受websocket概念的。为了更好地理解,需要跟http做一些关键的对比。

  1. http是请求-响应模式,必须要有请求,才有回复(且服务端不能主动请求)。websocket是全双工通信的,有信息就主动传达给对方。
  2. http短连接(一次来回后就默认关闭连接)。websocket是长连接。
  3. http是无状态。websocket是有状态的。

websocket是如何工作的

  1. 发出连接请求,客户端发送的第一个请求,是一个http请求,与普通http不同的是
Upgrade: websocket  // upgrade是一个http标准头部,指定希望升级到的协议
Connection: Upgrade // connection设定完成协议升级后是否保持连接

2. 服务器同意握手的响应

Upgrade: websocket          // 确认升级到 WebSocket
Connection: Upgrade         // 核心:确认连接已升级

3. 通信阶段

核心报文数据:

  • FIN:是否是结束帧,FIN=0表示还有后续,FIN=1表示是最后一个数据帧
  • Opcode:帧类型,数据类型(即这个数据包是干什么的)

image.png

  • Mask,是否掩码,1表示是掩码,掩码是将数据报文内容转换,而非是加密
  1. 连接关闭

一端发送关闭帧,另一端接受到关闭帧后,发送响应关闭帧。对话之后TCP断连。

代码实现一下websocket

现在通过java的方式来写一个websocket案例。

纯websocket的实现:

<--依赖包-->
<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.5.4</version> 
</dependency>
public class WebsocketChatRoom extends WebSocketServer {

    public WebsocketChatRoom(int port) {
        super(new InetSocketAddress(port));
    }


    @Override
    public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
        System.out.println("新连接到达:"+ webSocket.getRemoteSocketAddress());
    }

    @Override
    public void onClose(WebSocket webSocket, int i, String s, boolean b) {
        System.out.println("连接关闭:" + webSocket.getRemoteSocketAddress());
    }

    @Override
    public void onMessage(WebSocket webSocket, String s) {
        System.out.println("收到信息:" + s);
        // websocketServer自带的广播实现
        broadcast(s);
    }

    @Override
    public void onError(WebSocket webSocket, Exception e) {
        System.out.println("连接错误: "+e.getMessage());
    }

    @Override
    public void onStart() {

    }

    public static void main(String[] args) {
        int port = 8888;
        WebsocketChatRoom server = new WebsocketChatRoom(port);
        server.start();
        System.out.println("聊天室,启动!: " + port);
    }
}

上述的重点是继承WebSocketServer,它定义多个接口,每个接口望文生义,即各自连接阶段做的事情。最重要的是onMessage,参数为连接信息和报文文本。

作为后端开发,我们再请出deepseek大神帮我们写一个前端聊天窗口。

image.png

重要的是前端连接逻辑:

// 创建连接
socket = new WebSocket(serverUrl);

// websocket各阶段的处理器
socket.onopen = (event) => {
    console.log('WebSocket connection opened:', event);
    statusIndicator.className = 'status-indicator connected';
    statusText.textContent = 'Connected to server';
    sendButton.disabled = false;
    
    // Add connection message to chat
    addMessage('System', 'Connected to the WebSocket server', 'received');
};

socket.onmessage = (event) => {
    console.log('Message from server:', event.data);
    
    addMessage('Server', event.data, 'received');
    
    // Auto-scroll to bottom
    chatBox.scrollTop = chatBox.scrollHeight;
};

socket.onclose = (event) => {
    console.log('WebSocket connection closed:', event);
    statusIndicator.className = 'status-indicator';
    statusText.textContent = 'Disconnected from server';
    sendButton.disabled = true;
    
    // Add disconnection message to chat
    addMessage('System', 'Disconnected from the server', 'received');
    
    // Try to reconnect after 5 seconds
    setTimeout(() => {
        if (!socket || socket.readyState === WebSocket.CLOSED) {
            statusText.textContent = 'Reconnecting...';
            initWebSocket();
        }
    }, 5000);
};

socket.onerror = (error) => {
    console.error('WebSocket error:', error);
    statusText.textContent = 'Connection error';
    sendButton.disabled = true;
};

image.png

打开两个窗口,打开时会创建自己的用户名,然后在聊天室发送信息,即可同步信息了。

剖析一下websocket是如何实现的

什么是网络协议?为什么会有不同的网络协议?websocket和tcp的关系是什么呢?

满足好奇心,观物观本质才能举一反三。咱们来探讨一下websocket到底是如何实现的。

想要理解协议,不妨先从基础的http实现开始。

http是应用层协议,其底层逻辑是tcp层面的实现。tcp如何编写呢,即我们最开始学习高级语言时的“网络编程部分”。socket连接,输入流输出流,连接关闭等核心操作。

// 服务器监听端口
        int port = 8888;
        
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("服务器启动,监听端口: " + port);
            
            // 等待客户端连接(阻塞式)
            Socket clientSocket = serverSocket.accept();
            System.out.println("客户端连接成功: " + 
                clientSocket.getInetAddress().getHostAddress() + ":" + 
                clientSocket.getPort());
            
            // 获取输入流,读取客户端数据
            InputStream input = clientSocket.getInputStream();
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(input, "UTF-8"));
            
            String message;
            while ((message = reader.readLine()) != null) {
                System.out.println("收到消息: " + message);
                
                // 简单的回应
                OutputStream output = clientSocket.getOutputStream();
                PrintWriter writer = new PrintWriter(
                    new OutputStreamWriter(output, "UTF-8"), true);
                writer.println("服务器已收到: " + message);
            }
            
            clientSocket.close();
            
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
        }

网络的底层就是socket编码的信息传输。socket编程即计算机网络的tcp层面的抽象。信息沟通的底层就是这样的。那么怎么实现http协议呢?

http协议的编码层面,就是特定规范的文本,根据特定的规则去解析。

你可能已经想到了,我们学过的请求头,请求体,请求行。当http传输到socket的时候,就是这样的文本形式。

POST /api/users HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 45
Authorization: Bearer token123
User-Agent: Mozilla/5.0

{"name":"张三","age":25,"email":"zhangsan@example.com"}

读取到文本数据后,只需要用一行一行读取的逻辑,就可以解析请求行中的请求方式,url,请求头等具体信息了。应用层协议即网络编程 + 协议规范中定义的逻辑处理。

类比一下websocket,也是先读取报文,再用特定的逻辑解析即可。

总思路有了,那么细节上是如何的呢,这里跟http做对比更加直观一些。

  1. http一般经历一次对话后就关闭连接;而websocket会保持socket连接不关闭,这样就实现了长连接。
  2. http接收方只是读一次输入流,写一次输出流就完成这次连接;而websocket,服务端保持长连接,想要主动发送信息的时候,忘输出流中写数据发送即可。
  3. 判断是否是websocket连接,有一个检测逻辑,查看请求头的中是否有 Upgrade: websocket,那么就是我们的websocket了,随即返还升级响应,开始websocket连接。
  4. websocket的数据帧格式是不同的,在写入的时候,需要按照要求处理成对应的数据格式。
  5. http服务器接收请求后,创建新线程,专门处理请求。websocket服务器则是长期保持线程(会话),时刻准备响应信息。

现代前端环境中的websocket包,或者后端的实现,本质大致是这样。其实要跟深一步,想要优化网络,需要使用NIO来替代我们的BIO网络编程。但是NIO就放到后面的专题专门来讲吧。