首页
/ 基于Java实现WebSocket服务器的完整指南

基于Java实现WebSocket服务器的完整指南

2025-07-07 03:09:07作者:昌雅子Ethen

WebSocket协议为现代Web应用提供了全双工通信能力,本文将详细介绍如何使用Java语言构建一个符合RFC 6455标准的WebSocket服务器。

WebSocket基础概念

WebSocket是一种在单个TCP连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单。与传统的HTTP请求不同,WebSocket连接一旦建立,就会保持打开状态,允许双方随时发送数据。

环境准备

要构建Java WebSocket服务器,你需要:

  1. JDK 8或更高版本
  2. 基本的Java网络编程知识
  3. 了解TCP/IP协议基础

创建基础服务器

首先我们需要创建一个基本的TCP服务器:

import java.net.ServerSocket;
import java.net.Socket;

public class WebSocketServer {
    public static void main(String[] args) throws Exception {
        // 在80端口创建服务器
        ServerSocket server = new ServerSocket(80);
        System.out.println("服务器已启动,等待客户端连接...");
        
        // 等待客户端连接
        Socket client = server.accept();
        System.out.println("客户端已连接");
        
        // 获取输入输出流
        InputStream in = client.getInputStream();
        OutputStream out = client.getOutputStream();
        
        // 处理WebSocket握手...
    }
}

WebSocket握手过程

WebSocket连接始于一个特殊的HTTP升级请求,服务器需要正确处理这个握手过程:

  1. 客户端发送包含Sec-WebSocket-Key的HTTP请求
  2. 服务器需要:
    • 提取Sec-WebSocket-Key值
    • 将其与固定GUID拼接
    • 计算SHA-1哈希
    • 进行Base64编码
    • 返回正确的响应头
// 读取客户端握手请求
Scanner scanner = new Scanner(in, "UTF-8");
String data = scanner.useDelimiter("\\r\\n\\r\\n").next();

// 验证GET请求并提取密钥
if (data.startsWith("GET")) {
    Pattern pattern = Pattern.compile("Sec-WebSocket-Key: (.*)");
    Matcher matcher = pattern.matcher(data);
    matcher.find();
    
    // 生成响应密钥
    String inputKey = matcher.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(inputKey.getBytes("UTF-8"));
    String acceptKey = Base64.getEncoder().encodeToString(sha1);
    
    // 发送握手响应
    String response = "HTTP/1.1 101 Switching Protocols\r\n" +
                     "Connection: Upgrade\r\n" +
                     "Upgrade: websocket\r\n" +
                     "Sec-WebSocket-Accept: " + acceptKey + "\r\n\r\n";
    out.write(response.getBytes("UTF-8"));
}

消息帧解析

WebSocket使用特定的帧格式传输数据。一个典型的消息帧包含:

  1. 第一个字节:

    • FIN位(1bit):表示是否为消息的最后一帧
    • RSV1-3(各1bit):保留位
    • Opcode(4bit):帧类型(文本/二进制/关闭等)
  2. 第二个字节:

    • MASK位(1bit):指示是否使用掩码
    • 负载长度(7bit):实际数据长度
  3. 掩码键(4字节):如果MASK位为1

  4. 实际负载数据

消息解码实现

当客户端发送消息时,消息会被掩码处理。服务器需要按照以下算法解码:

byte[] decodeWebSocketFrame(byte[] encoded, byte[] maskKey) {
    byte[] decoded = new byte[encoded.length];
    for (int i = 0; i < encoded.length; i++) {
        decoded[i] = (byte) (encoded[i] ^ maskKey[i % 4]);
    }
    return decoded;
}

完整消息处理流程

以下是处理WebSocket消息的完整流程:

  1. 读取前两个字节确定帧类型和长度
  2. 如果需要,读取扩展长度字段
  3. 读取掩码键
  4. 读取负载数据并解码
  5. 根据Opcode处理不同类型的帧
// 读取帧头
int opcode = in.read() & 0x0F; // 低4位是opcode
int lengthByte = in.read() & 0x7F; // 低7位是长度
int length = lengthByte;
if (lengthByte == 126) {
    length = (in.read() << 8) | in.read();
} else if (lengthByte == 127) {
    // 处理64位长度(实际通常不需要)
}

// 读取掩码键
byte[] maskKey = new byte[4];
in.read(maskKey);

// 读取并解码数据
byte[] encoded = new byte[length];
in.read(encoded);
byte[] decoded = decodeWebSocketFrame(encoded, maskKey);

// 处理解码后的数据
String message = new String(decoded, "UTF-8");
System.out.println("收到消息: " + message);

发送消息到客户端

服务器发送消息时不需要掩码,但需要构造正确的帧格式:

void sendMessage(Socket socket, String message) throws IOException {
    byte[] data = message.getBytes("UTF-8");
    OutputStream out = socket.getOutputStream();
    
    // 发送帧头(FIN+文本帧)
    out.write(0x81);
    
    // 发送长度
    if (data.length <= 125) {
        out.write(data.length);
    } else if (data.length <= 65535) {
        out.write(126);
        out.write((data.length >> 8) & 0xFF);
        out.write(data.length & 0xFF);
    } else {
        // 处理超长消息(通常不需要)
    }
    
    // 发送数据
    out.write(data);
    out.flush();
}

实际应用建议

  1. 多线程处理:为每个连接创建独立线程
  2. 心跳检测:实现PING/PONG机制保持连接
  3. 错误处理:妥善处理各种异常情况
  4. 性能优化:考虑使用NIO提高并发能力

总结

本文详细介绍了使用Java实现WebSocket服务器的完整过程,包括握手协议、消息编解码等核心内容。通过这个基础实现,你可以进一步扩展功能,如支持多客户端、实现业务逻辑等,构建强大的实时Web应用。