C语言简单实现WebSocket协议


如果不了解什么是 WebSocket 协议,请先阅读这篇帖子WebSocket 协议

创建 WebSocket 连接

通过阅读WebSocket 协议这篇帖子我们可以得知:

  • WebSocket 是建立在 TCP 连接上的协议
  • WebSocket 需要发送一次基于 HTTP 的握手请求

那我们可以编写这样的C语言代码(环境Windows10,编译器TDM-GCC,编译时需加入参数 -lwsock32)

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
#include <WinSock2.h>

// 创建 WebSocket 连接
SOCKET ws_init(const char *host, u_short port)
{

// 创建 TCP Socekt
WSADATA data;
WORD sockVersion = MAKEWORD(2, 2);
WSAStartup(sockVersion, &data);
SOCKET ws_sock = socket(AF_INET, SOCK_STREAM, 0);

// 向指定 Server 端发起连接
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(host);
addr.sin_port = htons(port);
connect(ws_sock, (struct sockaddr *)&addr, sizeof(struct sockaddr));

// 发送 WebSocket 握手请求
char handShake[] = "GET /ws HTTP/1.1\n\
Host: 127.0.0.1:6700\n\
Connection: Upgrade\n\
Upgrade: websocket\n\
Sec-WebSocket-Version: 13\n\
Sec-WebSocket-Key: Bt4+Nfq12qxyxHslV2iFFg==\n\n";
send(ws_sock, handShake, strlen(handShake), 0);

// 忽略 Server 端响应数据
char response[1024 * 2];
recv(ws_sock, response, 1024 * 2, 0);

return ws_sock;
}

收发 WebSocket 数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
                     WebSocket 数据包解析

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+

我们知道通过位运算可以进行bit位的操作,结合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
// 发送 WebSocket 数据包
void ws_send(SOCKET ws_sock, char data[])
{
short head = 0;

head = head | 1; // 设置 FIN 值为 1
head = head << 3; // 设置 RSV1/2/3 默认值均为 0

// 设置 opcode 值为 OPCODE_TEXT
head = head << 4;
head = head | (1 << 0);

// 设置 MASK 值为 1 代表需要掩码计算
head = head << 1;
head = head | (1 << 0);

// 设置 Payload len 和 Extended payload length
head = head << 7;
size_t size = strlen(data);

if (size < 126)
{
head = head | size;
char headlen[2];
char payload[size + 6];

memcpy(headlen, (char *)&head, sizeof(short));
payload[0] = headlen[1];
payload[1] = headlen[0];

// Masking-key
// 设成重复的值编码起来比较方便
payload[2] = 33;
payload[3] = 33;
payload[4] = 33;
payload[5] = 33;

for (size_t i = 0; i < size; i++)
{
payload[i + 6] = data[i] ^ 33;
}
send(ws_sock, payload, size + 6, 0);
}
else if (size < 65536) // 1 << 16
{
head = head | 126;
char headlen[2];
char datalen[2];
char payload[size + 8];

memcpy(headlen, (char *)&head, sizeof(short));
payload[0] = headlen[1];
payload[1] = headlen[0];

memcpy(datalen, (char *)&size, sizeof(short));
payload[2] = datalen[1];
payload[3] = datalen[0];

// Masking-key
// 设成重复的值编码起来比较方便
payload[4] = 33;
payload[5] = 33;
payload[6] = 33;
payload[7] = 33;

for (size_t i = 0; i < size; i++)
{
payload[i + 8] = data[i] ^ 33;
}
send(ws_sock, payload, size + 8, 0);
}
else // 1 << 64
{
head = head | 127;
char headlen[2];
char datalen[8];
char payload[size + 14];

memcpy(headlen, (char *)&head, sizeof(short));
payload[0] = headlen[1];
payload[1] = headlen[0];

memcpy(datalen, (char *)&size, sizeof(long long));
for (size_t i = 0; i < 8; i++)
{
payload[i + 2] = datalen[7 - i];
}

// Masking-key
// 设成重复的值编码起来比较方便
payload[10] = 33;
payload[11] = 33;
payload[12] = 33;
payload[13] = 33;

for (size_t i = 0; i < size; i++)
{
payload[i + 14] = data[i] ^ 33;
}
send(ws_sock, payload, size + 14, 0);
}
}

// 接收 WebSocket 数据包
void ws_recv(SOCKET ws_sock, char *data, int datalen)
{
size_t size = recv(ws_sock, data, datalen, 0);

if (size < 126)
{
for (size_t i = 0; i < size; i++)
{
data[i] = data[i + 2];
}
}
else if (size < 65536) // 1 << 16
{
for (size_t i = 0; i < size; i++)
{
data[i] = data[i + 4];
}
}
else // 1 << 64
{
for (size_t i = 0; i < size; i++)
{
data[i] = data[i + 10];
}
}
}

Author: DawnNights
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source DawnNights !
  TOC