简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许客户端和服务器之间进行实时数据传输。相比传统的HTTP协议,WebSocket具有更低的延迟和更高的效率,特别适用于需要实时性的应用场景,比如在线游戏、即时通讯、股票行情等。

WebSocket协议通过在HTTP握手阶段升级到WebSocket连接,然后在建立的TCP连接上进行数据传输。

WebSocket协议的特点包括:

  1. 实时性:WebSocket允许服务器主动向客户端推送数据,实现了实时通信。
  2. 高效性:相比传统的HTTP轮询方式,WebSocket减少了通信的开销,提高了效率。
  3. 双向通信:WebSocket连接是全双工的,客户端和服务器可以同时进行数据传输。
  4. 轻量级:WebSocket协议本身相对轻量,减少了通信的开销。

通信过程

  1. 首先WebSocket是通过建立在tcp上的通信协议,所以第一步是建立TCP连接
  2. TCP连接建立完之后,如果使用到SSL/TLS,则建立SSL/TLS连接
  3. 上述连接建立完之后,开始发起请求,客户端会先发送一个Http请求,携带Upgrade和Connection消息头,用于通知客户端升级为WebSocket协议,另外还会携带Sec-Websocket-Key以及Sec-Websocket-Extensions,前者用于和服务端校验连接,后者用于与服务端协商解压算法以及其他一些配置。通过下图可以看出进行WebSocket通信首次请求也是Http请求,而请求时前缀为ws/wss是告诉浏览器这是WebSocket协议,不是普通的Http请求。因为第一步发送的Http请求,所以可以通过Nginx Http模块进行代理
  4. 服务端收到客户端发出第一个Http请求后,根据Upgrade和Connection,知道客户端需要使用WebSocket协议进行通信,然后根据Sec-Websocket-Key计算出Sec-WebSocket-Accept并且根据配置构造出Sec-WebSocket-Extensions,最后返回响应。通过下图看到,状态为101代表服务端通过了协议升级
  5. 客户端收到响应后,验证Sec-WebSocket-Accept,验证通过后,即开始后面的通信,后续就通过WebSocket协议进行消息通信,WebSocket具体内容简单来讲就是掩码以及压缩

协议内容

WebSocket协议有上图红框组成

  1. Fin:代表是否结束,WebSocket可以一次发送多个消息,Fin为true时代表本次最后一个消息
  2. Reserved:WebSocket协议保留位,为未来的协议扩展提供了一定的灵活性和可扩展性
  3. Per-Message Compressed:是否采用压缩,具体的压缩算法,在首次Http请求时确认
  4. Opcode:有一个字段称为Opcode(操作码),用于指示帧的类型和用途。Opcode的取值范围是0x0至0xF,其中一些常见的Opcode包括:
    1. 0x0:表示帧是一个连续的数据帧(Continuation Frame),用于分片传输的消息中的后续片段。
    2. 0x1:表示帧是一个文本帧(Text Frame),用于传输文本数据。
    3. 0x2:表示帧是一个二进制帧(Binary Frame),用于传输二进制数据。
    4. 0x8:表示帧是一个连接关闭帧(Connection Close Frame),用于关闭WebSocket连接。
    5. 0x9:表示帧是一个Ping帧,用于检测连接是否仍然活动。
    6. 0xA:表示帧是一个Pong帧,用于对收到的Ping帧进行响应。
  5. Mask:代表是否使用掩码,客户端向服务端发送数据是需要掩码的,而服务端向客户端发送数据是不需要掩码的,可以用来防止缓存中毒,参考地址
  6. Payload length:内容的长度,这里的内容指的是使用掩码后的内容的长度,具体规则:如果长度小于125,则只需要一个字节,如果大长度于125,则将该部分置为126,将长度写入后两个字节,如果长度大于65535,则将该部分置为127,将长度写入后8个字节
  7. Masking-Key:客户端自动生成的掩码
  8. Masked payload:使用掩码后的内容

红框下面的Payload是装包软件通过掩码解析出来的内容,Line-based text data是抓包软件解压出来的内容

使用

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

代码

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
@Component
@ServerEndpoint(value = "/connectWebSocket/{userId}")
public class WebSocket {

private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在线人数
*/
public static int onlineNumber = 0;
/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
public static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
/**
* 会话
*/
private Session session;
/**
* 用户名称
*/
private String userId;

/**
* 建立连接
*
* @param session
*/
@OnOpen
public void onOpen(@PathParam("userId") String userId, Session session) {
onlineNumber++;
logger.info("现在来连接的客户id:" + session.getId() + "用户名:" + userId);
this.userId = userId;
this.session = session;
//把自己的信息加入到map当中去
clients.put(userId, this);
logger.info("有连接打开! 当前在线人数" + clients.size());
}

/**
* 连接出错
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
logger.info("服务端发生了错误" + error.getMessage());
error.printStackTrace();
}

/**
* 连接关闭
*/
@OnClose
public void onClose() {
onlineNumber--;
clients.remove(userId);
logger.info("有连接关闭! 当前在线人数" + clients.size());
}

/**
* 收到客户端的消息
*
* @param jsonMessage json格式的消息
* @param session 会话
*/
@OnMessage
public void onMessage(String jsonMessage, Session session) {
try {
logger.info("来自客户端消息:" + jsonMessage + "客户端的id是:" + session.getId());
ChatRecord chatRecord = JSONObject.parseObject(jsonMessage, ChatRecord.class);
String toUserId = chatRecord.getToUserId();
if (WebSocket.isLogin(toUserId)){
this.sendMessageTo(chatRecord.getContent(), toUserId);
}
} catch (Exception e) {
e.printStackTrace();
logger.info("发生了错误了");
}

}


public void sendMessageTo(String message, String toUserId) {
WebSocket.getWebSocket(toUserId).session.getAsyncRemote().sendText(message);
}

public void sendMessage(String message) {
this.session.getAsyncRemote().sendText(message);
}

public static synchronized WebSocket getWebSocket(String userId) {
return WebSocket.clients.get(userId);
}

public static boolean isLogin(String userId){
return StringUtils.isNotBlank(userId) && clients.containsKey(userId);
}
}
1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebSocketStompConfig {
//这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket ,如果你使用外置的tomcat就不需要该配置文件
@Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}

}

服务端实现代码

服务端底层对消息的读取、写入处理可以参考以下两个方法: org.apache.tomcat.websocket.PerMessageDeflate#getMoreData

getMoreData主要实现了对消息的处理以及解压缩,掩码相关功能由专门的类处理

org.apache.tomcat.websocket.PerMessageDeflate#sendMessagePart

sendMessagePart主要实现了消息的处理以及压缩,掩码相关功能由专门的类处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Override
public TransformationResult getMoreData(byte opCode, boolean fin, int rsv, ByteBuffer dest)
throws IOException {
// Control frames are never compressed and may appear in the middle of
// a WebSocket method. Pass them straight through.
if (Util.isControl(opCode)) {
return next.getMoreData(opCode, fin, rsv, dest);
}

if (!Util.isContinuation(opCode)) {
// First frame in new message
skipDecompression = (rsv & RSV_BITMASK) == 0;
}

// Pass uncompressed frames straight through.
if (skipDecompression) {
return next.getMoreData(opCode, fin, rsv, dest);
}

int written;
boolean usedEomBytes = false;

while (dest.remaining() > 0) {
// Space available in destination. Try and fill it.
try {
written = inflater.inflate(
dest.array(), dest.arrayOffset() + dest.position(), dest.remaining());
} catch (DataFormatException e) {
throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e);
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}
dest.position(dest.position() + written);

if (inflater.needsInput() && !usedEomBytes ) {
if (dest.hasRemaining()) {
readBuffer.clear();
TransformationResult nextResult =
next.getMoreData(opCode, fin, (rsv ^ RSV_BITMASK), readBuffer);
inflater.setInput(
readBuffer.array(), readBuffer.arrayOffset(), readBuffer.position());
if (TransformationResult.UNDERFLOW.equals(nextResult)) {
return nextResult;
} else if (TransformationResult.END_OF_FRAME.equals(nextResult) &&
readBuffer.position() == 0) {
if (fin) {
inflater.setInput(EOM_BYTES);
usedEomBytes = true;
} else {
return TransformationResult.END_OF_FRAME;
}
}
}
} else if (written == 0) {
if (fin && (isServer && !clientContextTakeover ||
!isServer && !serverContextTakeover)) {
try {
inflater.reset();
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}
}
return TransformationResult.END_OF_FRAME;
}
}

return TransformationResult.OVERFLOW;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@Override
public List<MessagePart> sendMessagePart(List<MessagePart> uncompressedParts) throws IOException {
List<MessagePart> allCompressedParts = new ArrayList<>();

for (MessagePart uncompressedPart : uncompressedParts) {
byte opCode = uncompressedPart.getOpCode();
boolean emptyPart = uncompressedPart.getPayload().limit() == 0;
emptyMessage = emptyMessage && emptyPart;
if (Util.isControl(opCode)) {
// Control messages can appear in the middle of other messages
// and must not be compressed. Pass it straight through
allCompressedParts.add(uncompressedPart);
} else if (emptyMessage && uncompressedPart.isFin()) {
// Zero length messages can't be compressed so pass the
// final (empty) part straight through.
allCompressedParts.add(uncompressedPart);
} else {
List<MessagePart> compressedParts = new ArrayList<>();
ByteBuffer uncompressedPayload = uncompressedPart.getPayload();
SendHandler uncompressedIntermediateHandler =
uncompressedPart.getIntermediateHandler();

deflater.setInput(uncompressedPayload.array(),
uncompressedPayload.arrayOffset() + uncompressedPayload.position(),
uncompressedPayload.remaining());

int flush = (uncompressedPart.isFin() ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH);
boolean deflateRequired = true;

while (deflateRequired) {
ByteBuffer compressedPayload = writeBuffer;

try {
int written = deflater.deflate(compressedPayload.array(),
compressedPayload.arrayOffset() + compressedPayload.position(),
compressedPayload.remaining(), flush);
compressedPayload.position(compressedPayload.position() + written);
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}

if (!uncompressedPart.isFin() && compressedPayload.hasRemaining() && deflater.needsInput()) {
// This message part has been fully processed by the
// deflater. Fire the send handler for this message part
// and move on to the next message part.
break;
}

// If this point is reached, a new compressed message part
// will be created...
MessagePart compressedPart;

// .. and a new writeBuffer will be required.
writeBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);

// Flip the compressed payload ready for writing
compressedPayload.flip();

boolean fin = uncompressedPart.isFin();
boolean full = compressedPayload.limit() == compressedPayload.capacity();
boolean needsInput = deflater.needsInput();
long blockingWriteTimeoutExpiry = uncompressedPart.getBlockingWriteTimeoutExpiry();

if (fin && !full && needsInput) {
// End of compressed message. Drop EOM bytes and output.
compressedPayload.limit(compressedPayload.limit() - EOM_BYTES.length);
compressedPart = new MessagePart(true, getRsv(uncompressedPart),
opCode, compressedPayload, uncompressedIntermediateHandler,
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
deflateRequired = false;
startNewMessage();
} else if (full && !needsInput) {
// Write buffer full and input message not fully read.
// Output and start new compressed part.
compressedPart = new MessagePart(false, getRsv(uncompressedPart),
opCode, compressedPayload, uncompressedIntermediateHandler,
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
} else if (!fin && full && needsInput) {
// Write buffer full and input message not fully read.
// Output and get more data.
compressedPart = new MessagePart(false, getRsv(uncompressedPart),
opCode, compressedPayload, uncompressedIntermediateHandler,
uncompressedIntermediateHandler, blockingWriteTimeoutExpiry);
deflateRequired = false;
} else if (fin && full && needsInput) {
// Write buffer full. Input fully read. Deflater may be
// in one of four states:
// - output complete (just happened to align with end of
// buffer
// - in middle of EOM bytes
// - about to write EOM bytes
// - more data to write
int eomBufferWritten;
try {
eomBufferWritten = deflater.deflate(EOM_BUFFER, 0, EOM_BUFFER.length, Deflater.SYNC_FLUSH);
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}
if (eomBufferWritten < EOM_BUFFER.length) {
// EOM has just been completed
compressedPayload.limit(compressedPayload.limit() - EOM_BYTES.length + eomBufferWritten);
compressedPart = new MessagePart(true,
getRsv(uncompressedPart), opCode, compressedPayload,
uncompressedIntermediateHandler, uncompressedIntermediateHandler,
blockingWriteTimeoutExpiry);
deflateRequired = false;
startNewMessage();
} else {
// More data to write
// Copy bytes to new write buffer
writeBuffer.put(EOM_BUFFER, 0, eomBufferWritten);
compressedPart = new MessagePart(false,
getRsv(uncompressedPart), opCode, compressedPayload,
uncompressedIntermediateHandler, uncompressedIntermediateHandler,
blockingWriteTimeoutExpiry);
}
} else {
throw new IllegalStateException(sm.getString("perMessageDeflate.invalidState"));
}

// Add the newly created compressed part to the set of parts
// to pass on to the next transformation.
compressedParts.add(compressedPart);
}

SendHandler uncompressedEndHandler = uncompressedPart.getEndHandler();
int size = compressedParts.size();
if (size > 0) {
compressedParts.get(size - 1).setEndHandler(uncompressedEndHandler);
}

allCompressedParts.addAll(compressedParts);
}
}

if (next == null) {
return allCompressedParts;
} else {
return next.sendMessagePart(allCompressedParts);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
var websocket = null;


//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点,这里的new WebSocket是浏览器自带的
websocket = new WebSocket("ws://localhost:8081/connectWebSocket/001");
}
else{
alert('Not support websocket')
}


//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};


//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
};


//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
};


//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
};


//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
};


//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}


//关闭连接
function closeWebSocket(){
websocket.close();
}


//发送消息
function send(){
var text = document.getElementById('text').value;
var message = {
content:text
}
websocket.send(JSON.stringify(message));
}

自定义实现服务端

下面是对WebSocket的简单实现,主要实现了对掩码、压缩、解压等的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
public class WebSocket {

private final ServerSocket serverSocket = new ServerSocket(8081);

@PreDestroy
public void destroy() throws IOException {
serverSocket.close();
}

public static void main(String[] args) throws IOException {
WebSocket webSocket = new WebSocket();
}

public WebSocket() throws IOException {
while (true) {
Socket socket = serverSocket.accept();
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
Inflater inflater = new Inflater(true);
Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
new Thread(() -> {

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

new Thread(() -> {
try {
byte[] bytes = new byte[1024];
int read;
while (-1 != (read = inputStream.read(bytes))) {
byteArrayOutputStream.write(bytes, 0, read);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();

try {
boolean first = true;
while (true) {
ThreadUtil.sleep(10);
if (byteArrayOutputStream.size() < 1) {
continue;
}
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.reset();
if (first) {
String request = new String(bytes);
System.out.println(request);
Map<String, String> headers = getHeaders(request);
String secWebsocketKey = headers.get("Sec-WebSocket-Key");
String secWebsocketAccept = calculateWebSocketAccept(secWebsocketKey);
String response = "HTTP/1.1 101 \r\n" +
"Connection: upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: " + secWebsocketAccept + "\r\n" +
"Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15\r\n" +
"\r\n";
IoUtil.write(outputStream, CharsetUtil.UTF_8, false, response);
first = false;
} else {
if (isMask(bytes)) {
int payloadLength = getPayloadLength(bytes);
byte[] unMaskPayload = getUnMaskPayload(bytes, payloadLength);
System.out.println(new String(decompressData(inflater, unMaskPayload, 0, payloadLength)));
}
String message = "hello";
byte[] compressData = compressData(deflater, message.getBytes());
byte[] data = createData(compressData.length, compressData);
IoUtil.write(outputStream, false, data);
}
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}).start();
}
}

public static String calculateWebSocketAccept(String webSocketKey) throws NoSuchAlgorithmException {
String magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String concatenated = webSocketKey + magicString;
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] hashedBytes = digest.digest(concatenated.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashedBytes);
}

public static Map<String, String> getHeaders(String request) {
Map<String, String> map = new HashMap<>();
String[] lines = request.split("\r\n");
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
if (StringUtils.isBlank(line)) {
break;
}
String[] split = line.split(": ");
map.put(split[0], split[1]);
}
return map;
}

public static byte[] decompressData(Inflater inflater, byte[] input, int offset, int position) {
inflater.setInput(input, offset, position);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];

try {
while (!inflater.needsInput()){//java.util.zip.Inflater.needsInput方法可以理解为当前input的内容是否已解压完,如果返回true,代表已解压完,需要新的input
int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
inflater.setInput(new byte[]{0, 0, -1, -1});//这是因为WebSocket协议在压缩完后会将代表压缩结束的后四位给去掉,所以在解压时,要额外解压这四位才能执行后续解压
inflater.inflate(buffer);
} catch (DataFormatException e) {
e.printStackTrace();
}

return outputStream.toByteArray();
}

public boolean isMask(byte[] bytes) {
return (bytes[1] & 128) == 128;
}

public int getPayloadLength(byte[] bytes) {
int b = bytes[1] & 127;
if (b == 126) {
int result = 0;
for (int i = 2; i < 4; i++) {
//这里的bytes[i] & 255是因为,Java的byte的表示范围是-128~127,byte最高位为1时,将取反变成负数,但是WebSocket的字节表示范围是0~255
//而byte[i]&255可以将负数取反转为大于127的正数,而正数不变,这么做的原理是Java在进行运算时,byte、short都会转为int后再进行运算,并且结果也是int类型
//当byte负数与上255时,byte先变成32位int类型,最高24位全为1,而255最高24位全为0,可以抵消byte负数转为int后带来的影响,而最低8位不变,所以实际值不变
//举个例子,(byte)-2二进制表示为11111110,转为int后变成11111111111111111111111111111110,255默认为int类型,转为而二进制为00000000000000000000000011111111
//两者进行与运算后,结果为00000000000000000000000011111110,十进制值为254
result += (bytes[i] & 255) << (8 * (3 - i));
}
return result;
} else if (b == 127) {
int result = 0;
for (int i = 2; i < 10; i++) {
result += (bytes[i] & 255) << (8 * (9 - i));
}
return result;
} else {
return b;
}
}

public byte[] getUnMaskPayload(byte[] bytes, int payloadLength) {
int position = payloadLength > 65535 ? 10 : payloadLength > 125 ? 4 : 2;
byte[] maskingKeyBytes = ArrayUtil.sub(bytes, position, position + 4); //获取masking-key
byte[] maskPayloadBytes = ArrayUtil.sub(bytes, position + 4, position + 4 + payloadLength); //获取masked-payload

byte[] payload = new byte[payloadLength];

// 对每个字节进行异或运算
for (int i = 0; i < payloadLength; i++) {
payload[i] = (byte) (maskPayloadBytes[i] ^ maskingKeyBytes[i % 4]);
}

return payload;
}

public static byte[] compressData(Deflater deflater, byte[] input) {
byte[] bytes = new byte[1024];

deflater.setInput(input);
int deflate = deflater.deflate(bytes, 0, bytes.length, Deflater.SYNC_FLUSH);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(bytes, 0, deflate - 4);//去掉代表解压结尾的后四位

return byteArrayOutputStream.toByteArray();
}

public static byte[] createData(int payloadLength, byte[] maskData) {
byte[] one = new byte[]{(byte) 193};
byte[] two;
if (payloadLength > 65535) {
two = new byte[8];
two[0] = 127;//不使用mask
for (int i = 1; i < 9; i++) {
two[i] = (byte) (payloadLength & (8 * (9 - i)) >> (8 * (8 - i)));
}
} else if (payloadLength > 125) {
two = new byte[3];
two[0] = 126;//不使用mask
two[1] = (byte) (payloadLength >> 8);
two[2] = (byte) (payloadLength & 255);
} else {
two = new byte[]{(byte) payloadLength};//不使用mask
}
return ArrayUtil.addAll(one, two, maskData);
}
}

本站总访问量:次本站总访问数:人
本站由 Peng Tong 使用 Stellar 1.29.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。