第十八章 ESP32的WebSocket服务器

学习目的及目标

掌握Websocket原理和工作过程

掌握乐鑫ESP32的WebSocket的程序设计

WebSocket原理

WebSocket 是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

为什么需要 WebSocket ?

了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

Websocket特点

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443 ,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。

协议标识符是ws(如果加密,则为wss),服务器网址就是 URL,如下图。

1

2

3

4

5

6

7

8

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

请求握手包

1

2

3

4

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat

接收请求包

注:WebSocket握手详解

Websocket和HTTP连接过程

Websocket工作过程

软件设计

ESP32的Websocket详细过程

 

ESP32的Websocket接口介绍

  • 连接函数:netconn_new();
  • 绑定函数:netconn_bind();
  • 监听函数:netconn_listen();
  • 获取连接函数:netconn_accept();
  • 接收数据函数:netconn_recv();
  • 发送数据函数:netconn_write();
  • 关闭连接函数:netconn_close();
  • 删除连接函数:netconn_delete();

更多更详细接口请参考官方指南

Websocket新建任务编写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

void ws_server(void *pvParameters)

{

struct netconn *conn, *newconn;

//获取tcp socket connect

conn = netconn_new(NETCONN_TCP);

//绑定port

netconn_bind(conn, NULL, WS_PORT);

//监听

netconn_listen(conn);

//等待client连接

while (netconn_accept(conn, &newconn) == ERR_OK)

{

//新连接:等待连接、连接过程、数据读取

ws_server_netconn_serve(newconn);

}

//关闭websocket server connect

netconn_close(conn);

netconn_delete(conn);

}

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

static void ws_server_netconn_serve(struct netconn *conn) {

//申请SHA1

p_SHA1_Inp = pvPortMallocCaps(WS_CLIENT_KEY_L + sizeof(WS_sec_conKey),

MALLOC_CAP_8BIT);

//申请SHA1 result

p_SHA1_result = pvPortMallocCaps(SHA1_RES_L, MALLOC_CAP_8BIT);

//Check if malloc suceeded

if ((p_SHA1_Inp != NULL) && (p_SHA1_result != NULL)) {

//接收“连接”过程的数据

if (netconn_recv(conn, &inbuf) == ERR_OK) {

//读取“连接”过程的数据到buf

netbuf_data(inbuf, (void**) &buf, &i);

//把server的key传给SHA1

for (i = 0; i < sizeof(WS_sec_conKey); i++)

{

//放在后24字节

p_SHA1_Inp[i + WS_CLIENT_KEY_L] = WS_sec_conKey[i];

}

//搜索client的key

p_buf = strstr(buf, WS_sec_WS_keys);

//找到key

if (p_buf != NULL) {

//get Client Key

for (i = 0; i < WS_CLIENT_KEY_L; i++)

{

//放在前24字节

p_SHA1_Inp[i] = *(p_buf + sizeof(WS_sec_WS_keys) + i);

}

// 计算 hash

esp_sha(SHA1, (unsigned char*) p_SHA1_Inp, strlen(p_SHA1_Inp),

(unsigned char*) p_SHA1_result);

//转base64

p_buf = (char*) _base64_encode((unsigned char*) p_SHA1_result,

SHA1_RES_L, (size_t*) &i);

//free SHA1 input

free(p_SHA1_Inp);

//free SHA1 result

free(p_SHA1_result);

if (p_payload != NULL) {

//准备“握手”帧

sprintf(p_payload, WS_srv_hs, i - 1, p_buf);

//发送“握手”帧

netconn_write(conn, p_payload, strlen(p_payload),NETCONN_COPY);

//free base64

free(p_buf);

//free “握手”内存

free(p_payload);

//websocket连接成功

WS_conn = conn;

//“接收数据”

while (netconn_recv(conn, &inbuf) == ERR_OK) {

。。。。。。

Websocket发送代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

err_t WS_write_data(char* p_data, size_t length) {

//websocket未连接,直接退出

if (WS_conn == NULL)

return ERR_CONN;

//数据帧长度溢出,直接退出

if (length > WS_STD_LEN)

return ERR_VAL;

err_t result;

//报头

WS_frame_header_t hdr;

hdr.FIN = 0x1;

hdr.payload_length = length;

hdr.mask = 0;

hdr.reserved = 0;

hdr.opcode = WS_OP_TXT;

//发送报头

result = netconn_write(WS_conn, &hdr, sizeof(WS_frame_header_t), NETCONN_COPY);

if (result != ERR_OK)

return result;

//发送数据

return netconn_write(WS_conn, p_data, length, NETCONN_COPY);

}

代码有全部中文注释

测试流程和效果展示

测试流程

  1. 修改STA的账号密码
  2. 使用电脑助手工具进行WebSocket测试

效果展示

连接

发送

控灯

断开连接

WebSocket总结

点我->更多ESP32开发指南系列目录

发布了45 篇原创文章 · 获赞 48 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/qq_24550925/article/details/85855867