MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

WebSocket技术及其应用场景

2024-08-133.8k 阅读

WebSocket技术基础

WebSocket协议概述

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务器之间可以进行实时、双向的数据传输,打破了传统HTTP协议请求 - 响应模式的局限性。在传统HTTP协议下,客户端发起请求,服务器响应请求,完成交互后连接就关闭。如果客户端想要获取最新数据,需要不断发起新的请求,这就是轮询(Polling)方式,会带来额外的网络开销和延迟。而WebSocket协议建立的连接是持久的,双方可以随时主动发送数据,大大提高了实时通信的效率。

WebSocket协议的核心是通过HTTP的Upgrade头来进行握手,从HTTP协议升级到WebSocket协议。具体过程如下:

  1. 客户端发起握手请求:客户端发送一个HTTP请求,其中包含Upgrade: websocketConnection: Upgrade头,表明想要升级到WebSocket协议。例如:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Key: dGhlIHNhbXBsZSBub25jZQ==
Sec - WebSocket - Version: 13
  1. 服务器响应握手:服务器收到请求后,如果支持WebSocket协议,会返回一个HTTP响应,同样包含Upgrade: websocketConnection: Upgrade头,并且会返回一个Sec - WebSocket - Accept头,其值是根据客户端发送的Sec - WebSocket - Key经过特定算法计算得出的。例如:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  1. 连接建立:握手成功后,客户端和服务器之间就建立了WebSocket连接,后续的数据传输就不再遵循HTTP协议格式,而是采用WebSocket协议特有的帧格式。

WebSocket帧格式

WebSocket协议通过帧(Frame)来传输数据。每个帧由以下几部分组成:

  1. 起始位(1字节):第一位(FIN)表示这是否是消息的最后一帧,如果是1则表示是最后一帧;第2 - 3位(RSV1、RSV2、RSV3)一般为0,除非使用扩展协议;第4 - 8位(Opcode)表示帧的类型,常见的类型有:
    • 0x0:表示这是一个延续帧,用于和前面的帧组成一个完整的消息。
    • 0x1:文本帧,数据是UTF - 8编码的文本。
    • 0x2:二进制帧,数据是二进制格式。
    • 0x8:关闭连接帧。
    • 0x9:ping帧。
    • 0xA:pong帧。
  2. 数据长度(1 - 9字节):如果值小于126,那么这个字节就是数据的长度;如果值为126,那么接下来2个字节表示数据长度;如果值为127,那么接下来8个字节表示数据长度。
  3. 掩码(4字节):如果是客户端发送给服务器的帧,掩码位(Mask)为1,并且会携带4字节的掩码密钥。服务器发送给客户端的帧掩码位为0。
  4. 数据:实际传输的数据部分,长度由前面的数据长度字段决定。

例如,一个简单的文本帧可能如下(以十六进制表示):

81 05 48 65 6C 6C 6F
  • 818表示这是最后一帧,1表示是文本帧。
  • 05:表示数据长度为5字节。
  • 48 65 6C 6C 6F:即Hello的UTF - 8编码。

WebSocket在后端开发中的实现

使用Python的Tornado框架实现WebSocket

Tornado是一个Python的高性能网络框架,对WebSocket有很好的支持。首先,需要安装Tornado库:

pip install tornado

下面是一个简单的WebSocket服务器示例:

import tornado.ioloop
import tornado.web
import tornado.websocket


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print('WebSocket opened')

    def on_message(self, message):
        self.write_message('You sent: {}'.format(message))

    def on_close(self):
        print('WebSocket closed')


def make_app():
    return tornado.web.Application([
        (r"/ws", WebSocketHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在这个示例中:

  1. 定义了一个WebSocketHandler类,继承自tornado.websocket.WebSocketHandler
  2. open方法在WebSocket连接建立时被调用,这里只是简单打印一条信息。
  3. on_message方法在接收到客户端发送的消息时被调用,它将接收到的消息回显给客户端,前面加上You sent:
  4. on_close方法在WebSocket连接关闭时被调用,同样只是打印一条信息。
  5. make_app函数创建一个Tornado应用,并将/ws路径映射到WebSocketHandler
  6. 最后,应用监听在8888端口,并启动Tornado的I/O循环。

使用Java的Spring Boot实现WebSocket

Spring Boot是Java开发中常用的框架,通过spring - websocket依赖可以方便地实现WebSocket功能。首先,在pom.xml中添加依赖:

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

然后,创建一个WebSocket配置类:

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;


@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
        config.enableSimpleBroker("/topic", "/queue");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket - endpoint").withSockJS();
    }
}

这里:

  1. @EnableWebSocketMessageBroker注解开启WebSocket消息代理功能。
  2. configureMessageBroker方法配置消息代理的前缀和目的地。/app前缀用于应用到服务器的消息,/user前缀用于用户特定的消息,/topic/queue用于广播和点对点消息。
  3. registerStompEndpoints方法注册一个STOMP端点,这里使用了SockJS作为WebSocket的后备方案,以支持不支持WebSocket的浏览器。

接下来,创建一个消息处理类:

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;


@Controller
public class WebSocketController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public String handleHelloMessage(String message) {
        return "Hello, " + message;
    }
}

在这个类中:

  1. @MessageMapping("/hello")注解表示处理发送到/app/hello目的地的消息。
  2. @SendTo("/topic/greetings")注解表示将处理后的消息发送到/topic/greetings目的地,这样所有订阅了该主题的客户端都能收到消息。

WebSocket应用场景

实时聊天应用

实时聊天是WebSocket最典型的应用场景之一。无论是一对一聊天还是群聊,都需要实时地将消息从一方发送到另一方。使用WebSocket可以实现即时的消息推送,无需客户端不断轮询服务器获取新消息。

以一个简单的Web聊天应用为例,前端页面使用JavaScript的WebSocket API与后端建立连接。后端使用上述Python Tornado示例的方式接收和转发消息。当一个用户发送消息时,后端的WebSocketHandler接收到消息,然后将消息广播给所有连接的客户端。

import tornado.ioloop
import tornado.web
import tornado.websocket


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    clients = []

    def open(self):
        self.clients.append(self)
        print('WebSocket opened')

    def on_message(self, message):
        for client in self.clients:
            if client != self:
                client.write_message(message)

    def on_close(self):
        self.clients.remove(self)
        print('WebSocket closed')


def make_app():
    return tornado.web.Application([
        (r"/ws", WebSocketHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在这个改进的示例中,clients列表存储了所有连接的客户端。当接收到消息时,会将消息发送给除发送者之外的所有客户端,实现简单的群聊功能。

实时数据监控与仪表盘

在许多业务场景中,需要实时监控系统的各种指标,如服务器的CPU使用率、内存使用率、网络流量等,并通过仪表盘实时展示。WebSocket可以很好地满足这一需求。

后端可以通过系统监控工具获取这些实时数据,然后通过WebSocket将数据推送给前端页面。前端页面使用JavaScript的WebSocket API接收数据,并更新仪表盘的显示。例如,使用Echarts等图表库,根据接收到的数据实时绘制图表。

以下是一个简单的Python后端示例,模拟获取CPU使用率数据并通过WebSocket发送给客户端:

import psutil
import tornado.ioloop
import tornado.web
import tornado.websocket


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print('WebSocket opened')
        self.send_cpu_data()

    def send_cpu_data(self):
        cpu_percent = psutil.cpu_percent(interval = 1)
        self.write_message({'cpu_percent': cpu_percent})
        tornado.ioloop.IOLoop.current().call_later(1, self.send_cpu_data)

    def on_close(self):
        print('WebSocket closed')


def make_app():
    return tornado.web.Application([
        (r"/ws", WebSocketHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在这个示例中:

  1. send_cpu_data方法获取CPU使用率数据,并通过WebSocket发送给客户端。
  2. 使用tornado.ioloop.IOLoop.current().call_later方法每秒调用一次send_cpu_data,实现数据的定时推送。

协同编辑应用

协同编辑应用允许多个用户同时在一个文档或项目上进行编辑,实时看到其他用户的修改。WebSocket在这种场景中起着关键作用,用于实时同步用户的编辑操作。

以一个简单的文本协同编辑为例,后端使用WebSocket接收用户的编辑操作(如插入文本、删除文本等),然后将这些操作广播给其他所有正在编辑的用户。每个用户的前端页面接收到操作后,在本地文档上执行相同的操作,从而实现实时同步。

以下是一个简化的后端实现示例(使用Python和Tornado):

import tornado.ioloop
import tornado.web
import tornado.websocket


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    clients = []

    def open(self):
        self.clients.append(self)
        print('WebSocket opened')

    def on_message(self, message):
        for client in self.clients:
            if client != self:
                client.write_message(message)

    def on_close(self):
        self.clients.remove(self)
        print('WebSocket closed')


def make_app():
    return tornado.web.Application([
        (r"/ws", WebSocketHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在实际应用中,需要更复杂的操作处理逻辑,例如对操作进行合并、冲突检测与解决等,但基本的消息广播机制是通过WebSocket实现的。

游戏开发中的实时通信

在在线游戏中,实时通信至关重要。例如,多人在线对战游戏需要实时同步玩家的位置、动作、状态等信息。WebSocket可以为游戏提供高效的实时通信通道。

以一个简单的网页版多人坦克对战游戏为例,每个玩家的浏览器通过WebSocket与游戏服务器建立连接。当玩家操作坦克移动、发射炮弹等时,客户端将这些操作通过WebSocket发送给服务器。服务器接收到操作后,更新游戏状态,并将新的游戏状态通过WebSocket广播给所有玩家的客户端,客户端根据接收到的游戏状态更新本地画面。

以下是一个简单的游戏服务器示例(使用Python和Tornado),用于接收和广播玩家的操作:

import tornado.ioloop
import tornado.web
import tornado.websocket


class WebSocketHandler(tornado.websocket.WebSocketHandler):
    clients = []

    def open(self):
        self.clients.append(self)
        print('WebSocket opened')

    def on_message(self, message):
        for client in self.clients:
            if client != self:
                client.write_message(message)

    def on_close(self):
        self.clients.remove(self)
        print('WebSocket closed')


def make_app():
    return tornado.web.Application([
        (r"/ws", WebSocketHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在实际游戏开发中,还需要处理游戏逻辑、碰撞检测、玩家身份验证等复杂功能,但WebSocket为实时通信提供了基础。

WebSocket的优势与挑战

优势

  1. 实时性强:全双工通信模式使得服务器可以主动推送数据给客户端,无需客户端频繁请求,大大提高了实时性,适用于对实时性要求高的应用场景,如实时聊天、实时监控等。
  2. 减少网络开销:相比轮询方式,WebSocket减少了不必要的HTTP请求和响应头,降低了网络流量和带宽消耗,提高了通信效率。
  3. 支持双向通信:客户端和服务器都可以主动发起通信,这使得应用的交互性更强,能够实现更多复杂的功能,如协同编辑、游戏实时通信等。
  4. 协议简单:WebSocket协议相对简单,易于理解和实现,无论是在前端还是后端开发中,都有丰富的库和框架支持,降低了开发难度。

挑战

  1. 兼容性问题:虽然现代浏览器基本都支持WebSocket协议,但在一些老旧浏览器中可能不支持。为了解决这个问题,可以使用SockJS等库,它可以在不支持WebSocket的浏览器中自动降级使用其他技术(如XHR Polling、JSONP Polling等)来模拟WebSocket的功能。
  2. 安全性问题:WebSocket连接建立在TCP之上,与HTTP协议类似,也面临一些安全风险,如跨站WebSocket劫持(CSWSH)、注入攻击等。开发中需要采取相应的安全措施,如进行身份验证、对输入数据进行验证和过滤、使用安全的传输层协议(如HTTPS)等。
  3. 服务器负载:由于WebSocket连接是持久化的,大量的并发连接可能会对服务器造成较大的负载压力。需要合理设计服务器架构,采用分布式部署、负载均衡等技术来应对高并发场景。同时,对连接进行有效的管理,如设置合理的心跳机制来检测和清理无效连接,也可以减轻服务器负载。

在实际应用中,需要综合考虑这些优势和挑战,合理运用WebSocket技术来构建高效、稳定、安全的实时应用。通过充分发挥其优势,并有效应对挑战,可以为用户带来更好的实时交互体验。