基于Java实现WebSocket服务器的完整指南
2025-07-07 03:09:07作者:昌雅子Ethen
WebSocket协议为现代Web应用提供了全双工通信能力,本文将详细介绍如何使用Java语言构建一个符合RFC 6455标准的WebSocket服务器。
WebSocket基础概念
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单。与传统的HTTP请求不同,WebSocket连接一旦建立,就会保持打开状态,允许双方随时发送数据。
环境准备
要构建Java WebSocket服务器,你需要:
- JDK 8或更高版本
- 基本的Java网络编程知识
- 了解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升级请求,服务器需要正确处理这个握手过程:
- 客户端发送包含
Sec-WebSocket-Key
的HTTP请求 - 服务器需要:
- 提取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使用特定的帧格式传输数据。一个典型的消息帧包含:
-
第一个字节:
- FIN位(1bit):表示是否为消息的最后一帧
- RSV1-3(各1bit):保留位
- Opcode(4bit):帧类型(文本/二进制/关闭等)
-
第二个字节:
- MASK位(1bit):指示是否使用掩码
- 负载长度(7bit):实际数据长度
-
掩码键(4字节):如果MASK位为1
-
实际负载数据
消息解码实现
当客户端发送消息时,消息会被掩码处理。服务器需要按照以下算法解码:
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消息的完整流程:
- 读取前两个字节确定帧类型和长度
- 如果需要,读取扩展长度字段
- 读取掩码键
- 读取负载数据并解码
- 根据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();
}
实际应用建议
- 多线程处理:为每个连接创建独立线程
- 心跳检测:实现PING/PONG机制保持连接
- 错误处理:妥善处理各种异常情况
- 性能优化:考虑使用NIO提高并发能力
总结
本文详细介绍了使用Java实现WebSocket服务器的完整过程,包括握手协议、消息编解码等核心内容。通过这个基础实现,你可以进一步扩展功能,如支持多客户端、实现业务逻辑等,构建强大的实时Web应用。