STM32 WebSocket Server实现
1. 介绍
具有以太网接口的STM32芯片(STM32F4, STM32F7, STM32H7等),在实现了TCP Server的基础上,可以通过增加协议处理,实现WebSocket Server。
2. TCP Server的实现
基于STM32及DP83848 PHY的TCP Server的实现,可参考: https://blog.csdn.net/hwytree/article/details/103547919
基于STM32及LAN8742A/LAN8270A PHY的TCP Server的实现,可参考 https://blog.csdn.net/hwytree/article/details/119839143
3. WebSocket Server的实现
增加websocket.h文件,编写如下:
/*
Note: currently only for single websocket frame communication, can be expanded if necessary.
*/
#include <string.h>
#include <stdio.h>
#define WS_MIN_LEN_READ 544
#define WS_MAX_LEN_WRITE 256
char g_ws_read_buf[WS_MIN_LEN_READ] = {0};
char g_ws_write_buf[WS_MAX_LEN_WRITE] = {0};
char g_ws_write_buf_t[WS_MAX_LEN_WRITE] = {0};
unsigned short int ws_handshake_done = 0;
unsigned long long payloadLen = 0;
unsigned long long pack_data_length;
#ifdef __cplusplus
extern "C"{
#endif
#define SHA1CircularShift(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))
const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
typedef struct SHA1Context
{
unsigned Message_Digest[5];
unsigned Length_Low;
unsigned Length_High;
unsigned char Message_Block[64];
int Message_Block_Index;
int Computed;
int Corrupted;
}SHA1Context;
int tolower(int c);
int htoi(const char s[],int start,int len);
char fetch_sec_key(void);
char compute_accept_key(void) ;
char shake_hand(void);
void base64_encode(void);
char sha1_hash(void);
void SHA1Reset(SHA1Context * context);
int SHA1Result(SHA1Context * context);
void SHA1Input(SHA1Context * context,const char * message_array,unsigned length);
void SHA1ProcessMessageBlock(SHA1Context *);
void SHA1PadMessage(SHA1Context * context);
char analy_data(void);
void pack_data(char * message, unsigned long long dn);
//if error, return 0, else return 1
char fetch_sec_key(void)
{
const char * flag = "Sec-WebSocket-Key: ";
char * keyBegin = NULL;
int i = 0;
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
keyBegin = strstr(g_ws_read_buf, flag);
if(! keyBegin) //w/o effective request head
{
return 0;
}
//w/ effective request head
keyBegin += strlen(flag);
for(i = 0; i < strlen(g_ws_read_buf); i++)
{
if((keyBegin[i] == 0x0A) || (keyBegin[i] == 0x0D)) //0x0A: new line; 0x0D: return
{
break;
}
g_ws_write_buf[i] = keyBegin[i];
}
return 1;
}
//if error, return 0, else return 1
char compute_accept_key(void)
{
const char * GUID="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
int i = 0, n = 0;
//websocket key
if(! fetch_sec_key())
return 0;
strcat(g_ws_write_buf, GUID);
if(! sha1_hash())
return 0;
n = strlen(g_ws_write_buf);
memset(g_ws_read_buf, '\0', WS_MIN_LEN_READ);
for(i = 0; i < n; i += 2)
{
g_ws_read_buf[i / 2] = htoi(g_ws_write_buf, i, 2);
}
base64_encode();
return 1;
}
//if error, return 0, else return 1
char shake_hand(void)
{
memset(g_ws_read_buf, '\0', WS_MIN_LEN_READ);
sprintf(g_ws_read_buf, "HTTP/1.1 101 Switching Protocols\r\n");
sprintf(g_ws_read_buf, "%sUpgrade: websocket\r\n", g_ws_read_buf);
sprintf(g_ws_read_buf, "%sConnection: Upgrade\r\n", g_ws_read_buf);
sprintf(g_ws_read_buf, "%sSec-WebSocket-Accept: %s\r\n\r\n", g_ws_read_buf, g_ws_write_buf);
return 1;
}
/*base64 function*/
void base64_encode(void)
{
int tmp = 0, i = 0;
int prepare = 0;
int temp = strlen(g_ws_read_buf) % 3;
char * f = NULL;
char changed[4];
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
f = g_ws_write_buf;
while (tmp < strlen(g_ws_read_buf))
{
temp = 0;
prepare = 0;
memset(changed, '\0', 4);
while (temp < 3)
{
if (tmp >= strlen(g_ws_read_buf))
{
break;
}
prepare = ((prepare << 8) | (g_ws_read_buf[tmp] & 0xFF));
tmp++;
temp++;
}
prepare = (prepare << ((3 - temp) * 8));
for (i = 0; i < 4 ;i++ )
{
if (temp < i)
{
changed[i] = 0x40;
}
else
{
changed[i] = (prepare >> ((3 - i) * 6)) & 0x3F;
}
*f = base[changed[i]];
f++;
}
}
*f = '\0';
}
/*SHA1 function*/
void SHA1Reset(SHA1Context * context)
{
context->Length_Low = 0;
context->Length_High = 0;
context->Message_Block_Index = 0;
context->Message_Digest[0] = 0x67452301;
context->Message_Digest[1] = 0xEFCDAB89;
context->Message_Digest[2] = 0x98BADCFE;
context->Message_Digest[3] = 0x10325476;
context->Message_Digest[4] = 0xC3D2E1F0;
context->Computed = 0;
context->Corrupted = 0;
}
int SHA1Result(SHA1Context * context)
{
if (context->Corrupted)
{
return 0;
}
if (!context->Computed)
{
SHA1PadMessage(context);
context->Computed = 1;
}
return 1;
}
void SHA1Input(SHA1Context * context,const char * message_array,unsigned length)
{
if (!length)
return;
if (context->Computed || context->Corrupted)
{
context->Corrupted = 1;
return;
}
while(length-- && !context->Corrupted)
{
context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF);
context->Length_Low += 8;
context->Length_Low &= 0xFFFFFFFF;
if (context->Length_Low == 0)
{
context->Length_High++;
context->Length_High &= 0xFFFFFFFF;
if (context->Length_High == 0) context->Corrupted = 1;
}
if (context->Message_Block_Index == 64)
{
SHA1ProcessMessageBlock(context);
}
message_array++;
}
}
void SHA1ProcessMessageBlock(SHA1Context * context)
{
const unsigned K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
int t;
unsigned temp;
unsigned W[80];
unsigned A, B, C, D, E;
for(t = 0; t < 16; t++)
{
W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
}
for(t = 16; t < 80; t++)
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
A = context->Message_Digest[0];
B = context->Message_Digest[1];
C = context->Message_Digest[2];
D = context->Message_Digest[3];
E = context->Message_Digest[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 40; t < 60; t++)
{
temp = SHA1CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 60; t < 80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
context->Message_Digest[0] = (context->Message_Digest[0] + A) & 0xFFFFFFFF;
context->Message_Digest[1] = (context->Message_Digest[1] + B) & 0xFFFFFFFF;
context->Message_Digest[2] = (context->Message_Digest[2] + C) & 0xFFFFFFFF;
context->Message_Digest[3] = (context->Message_Digest[3] + D) & 0xFFFFFFFF;
context->Message_Digest[4] = (context->Message_Digest[4] + E) & 0xFFFFFFFF;
context->Message_Block_Index = 0;
}
void SHA1PadMessage(SHA1Context *context)
{
if (context->Message_Block_Index > 55)
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 64)
context->Message_Block[context->Message_Block_Index++] = 0;
SHA1ProcessMessageBlock(context);
while(context->Message_Block_Index < 56)
context->Message_Block[context->Message_Block_Index++] = 0;
}
else
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 56)
context->Message_Block[context->Message_Block_Index++] = 0;
}
context->Message_Block[56] = (context->Length_High >> 24 ) & 0xFF;
context->Message_Block[57] = (context->Length_High >> 16 ) & 0xFF;
context->Message_Block[58] = (context->Length_High >> 8 ) & 0xFF;
context->Message_Block[59] = (context->Length_High) & 0xFF;
context->Message_Block[60] = (context->Length_Low >> 24 ) & 0xFF;
context->Message_Block[61] = (context->Length_Low >> 16 ) & 0xFF;
context->Message_Block[62] = (context->Length_Low >> 8 ) & 0xFF;
context->Message_Block[63] = (context->Length_Low) & 0xFF;
SHA1ProcessMessageBlock(context);
}
//if error, return 0, else return 1
char sha1_hash(void)
{
SHA1Context sha = {0};
SHA1Reset(&sha);
SHA1Input(&sha, g_ws_write_buf, strlen(g_ws_write_buf));
if (! SHA1Result(&sha))
{
printf("%s-%d:Could not compute message digest.\n", __func__, __LINE__);
return 0;
}
else
{
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
sprintf(g_ws_write_buf, "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],
sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);
return 1;
}
}
/*convertion function*/
int tolower(int c)
{
if (c >= 'A' && c <= 'Z')
{
return c + 'a' - 'A';
}
else
{
return c;
}
}
int htoi(const char s[],int start,int len)
{
int i,j;
int n = 0;
if (s[0] == '0' && (s[1]=='x' || s[1]=='X'))
{
i = 2;
}
else
{
i = 0;
}
i+=start;
j=0;
for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') || (s[i] >='A' && s[i] <= 'F');++i)
{
if(j>=len)
{
break;
}
if (tolower(s[i]) > '9')
{
n = 16 * n + (10 + tolower(s[i]) - 'a');
}
else
{
n = 16 * n + (tolower(s[i]) - '0');
}
j++;
}
return n;
}
/*
Frame format:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ......
+-+-+-+-+-------+-+-------------+-------------------------------+
|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 ... |
+---------------------------------------------------------------+
*/
char analy_data(void)
{
char fin = 0;
char maskFlag = 0;
char masks[4] = {0};
char temp[8];
unsigned long long i = 0;
payloadLen = 0;
if (strlen(g_ws_read_buf) < 2)
{
//data len error.
return 0;
}
fin = (g_ws_read_buf[0] & 0x80) == 0x80;
if (!fin)
{
//fin error.
return 0;
}
maskFlag = (g_ws_read_buf[1] & 0x80) == 0x80;
if (!maskFlag)
{
//mask flag error.
return 0;
}
payloadLen = g_ws_read_buf[1] & 0x7F;
if (payloadLen == 126)
{
memcpy(masks, g_ws_read_buf + 4, 4);
payloadLen = (((unsigned short)g_ws_read_buf[2] ) << 8) | ((unsigned short)g_ws_read_buf[3]);
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
memcpy(g_ws_write_buf, g_ws_read_buf + 8, payloadLen);
}
else if (payloadLen == 127)
{
memcpy(masks, g_ws_read_buf + 10, 4);
for ( i = 0; i < 8; i++)
{
temp[i] = g_ws_read_buf[2 + i];
}
memcpy(&payloadLen,temp,8);
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
memcpy(g_ws_write_buf, g_ws_read_buf + 14, payloadLen);//toggle error(core dumped) if data is too long.
}
else
{
memcpy(masks, g_ws_read_buf + 2, 4);
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
memcpy(g_ws_write_buf, g_ws_read_buf + 6, payloadLen);
}
memset(g_ws_write_buf_t, '\0', WS_MAX_LEN_WRITE);
for (i = 0; i < payloadLen; i++)
{
g_ws_write_buf_t[i] = (char)(g_ws_write_buf[i] ^ masks[i % 4]);
}
//get data: g_ws_write_buf_t w/ data length: payloadLen
return 1;
}
void pack_data(char * message, unsigned long long dn) // message: input; dn: input; len: output
{
if (dn<=0) pack_data_length = 0;
else if ((dn > 0)&&(dn < 126))
{
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
g_ws_write_buf[0] = 0x81;
g_ws_write_buf[1] = dn;
memcpy(g_ws_write_buf + 2, message, dn);
pack_data_length = dn + 2;
}
else if ((dn >= 126)&&(dn <= 0xFFFF))
{
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
g_ws_write_buf[0] = 0x81;
g_ws_write_buf[1] = 126;
g_ws_write_buf[2] = ((dn >> 8) & 0xFF);
g_ws_write_buf[3] = (dn & 0xFF);
memcpy(g_ws_write_buf + 4, message, dn);
pack_data_length = dn + 4;
}
else
{
memset(g_ws_write_buf, '\0', WS_MAX_LEN_WRITE);
g_ws_write_buf[0] = 0x81;
g_ws_write_buf[1] = 127;
g_ws_write_buf[2] = ((dn >> 56) & 0xFF);
g_ws_write_buf[3] = ((dn >> 48) & 0xFF);
g_ws_write_buf[4] = ((dn >> 40) & 0xFF);
g_ws_write_buf[5] = ((dn >> 32) & 0xFF);
g_ws_write_buf[6] = ((dn >> 24) & 0xFF);
g_ws_write_buf[7] = ((dn >> 16) & 0xFF);
g_ws_write_buf[8] = ((dn >> 8) & 0xFF);
g_ws_write_buf[9] = ((dn >> 0) & 0xFF);
memcpy(g_ws_write_buf + 10, message, dn);
pack_data_length = dn + 10;
}
//get data: g_ws_write_buf w/ length: *len
//*len is essential for byte communication beyond character communication
}
#ifdef __cplusplus
}
#endif
然后将上述已实现TCP Server的代码中的tcpserver.c进行代码升级,如下:
#include <lwip/sockets.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h> //包含需要的所有头文件,(实现socket和标准输入输出)
#include "tcpserver.h"
#include "usart.h"
#include "websocket.h"
#define SERVER_PORT 1030 //配置服务器端口号
extern _Bool TCP_STATUS_UPDATE;
uint8_t data_buffer[WS_MIN_LEN_READ]; //定义接收到的数据Buff大小为100
//static void tcp_server_thread(void *p_arg) //定义TCP服务器线程
void tcp_echoserver_init(void *p_arg)
{
struct sockaddr_in server_addr; //服务器地址
struct sockaddr_in conn_addr; //连接地址
int sock_fd ; //服务器的 socked
int sock_conn; // 请求的 socked
socklen_t addr_len; // 地址长度
int err;
int length;
unsigned int rc;
ws_handshake_done = 0;
sock_fd = socket(AF_INET, SOCK_STREAM, 0); //建立一个新的socket连接
if (sock_fd < 0)
{
printf("tcp socket error\r\n") ;
TCP_STATUS_UPDATE = 0;
return;
}
else printf("tcp socket ok\r\n") ;
memset(&server_addr, 0, sizeof(server_addr)); //将服务器地址清空
server_addr.sin_family = AF_INET; //地址家族
server_addr.sin_addr.s_addr =inet_addr("192.168.1.252"); //注意转化为网络字节序
server_addr.sin_port = htons(SERVER_PORT); //使用SERVER_PORT指定为程序头设定的端口号
memset(server_addr.sin_zero,0,sizeof(server_addr.sin_zero));
err = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); //建立绑定
if (err < 0) //如果绑定失败则关闭套接字
{
closesocket(sock_fd); //关闭套接字
printf("bind error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else printf("tcp socket bind ok\r\n") ;
err = listen(sock_fd, 1); //监听连接请求
if (err < 0) //如果监听失败则关闭套接字
{
closesocket(sock_fd); //关闭套接字
printf("listen error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else printf("tcp socket listen ok\r\n") ;
addr_len = sizeof(struct sockaddr_in); //将链接地址赋值给addr_len
sock_conn = accept(sock_fd, (struct sockaddr *)&conn_addr, &addr_len); //对监听到的请求进行连接,状态赋值给sock_conn
if(sock_conn<0) //状态小于0代表连接故障,此时关闭套接字
{
closesocket(sock_fd);
printf("sock_conn error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else printf("sock_conn ok\r\n");
while (1)
{
memset(data_buffer, 0, sizeof(data_buffer)); //清空接收Buff
length = recv(sock_conn, (unsigned int *)data_buffer, WS_MIN_LEN_READ, 0); //将收到的数据放到接收Buff
if (length>0)
{
memset(g_ws_read_buf, '\0', WS_MIN_LEN_READ); 接收Buff的数据转移到数据处理Buff,防止之后数据混乱
for (rc=0; rc<length; rc++)
{
g_ws_read_buf[rc] = data_buffer[rc];
}
if (ws_handshake_done == 0)
{
if (compute_accept_key()==0) ; //w/o handshaking request
else
{
ws_handshake_done = 1;
shake_hand(); /*respond handshare*/
send(sock_conn, g_ws_read_buf,strlen(g_ws_read_buf), 1);
}
}
else //after correct handshaking
{
analy_data() ;
pack_data(g_ws_write_buf_t, payloadLen) ;
send(sock_conn,g_ws_write_buf,pack_data_length, 1);
}
}
else
{
if (length <0)
{
closesocket(sock_fd);
printf("sock_rev error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else if (errno != EINTR) //(length==0)&&(errno!=EINTR) means socket broke
{
printf("tcp link broke\r\n");
printf("%s\r\n",g_ws_read_buf);
ws_handshake_done = 0;
err = listen(sock_fd, 1); //监听连接请求
if (err < 0) //如果监听失败则关闭套接字
{
closesocket(sock_fd); //关闭套接字
printf("listen error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else printf("tcp socket listen ok\r\n") ;
addr_len = sizeof(struct sockaddr_in); //将链接地址赋值给addr_len
sock_conn = accept(sock_fd, (struct sockaddr *)&conn_addr, &addr_len); //对监听到的请求进行连接,状态赋值给sock_conn
if(sock_conn<0) //状态小于0代表连接故障,此时关闭套接字
{
closesocket(sock_fd);
printf("sock_conn error\r\n");
TCP_STATUS_UPDATE = 0;
return;
}
else printf("sock_conn ok\r\n");
}
}
osDelay(1);
}
}
基于上述代码的实现,上电后STM32 WebSocket Server就运行起来。
4. WebSocket 连接测试
编写测试脚本ws_client_test.html:
<p id="info">Status Here</p>
<button onclick="wbst_open()">connect</button>
<input id="wsinput"/>
<button onclick="wbst_send()">send</button>
<button onclick="wbst_close()">close</button>
<script>
var ws ;
var ws_address = "ws://192.168.1.252:1030";
wbst_open = function (){
ws = new WebSocket(ws_address);
ws.onopen = function(evt) {
console.log("Connection opened");
document.getElementById("info").innerText = "Connection opened"
};
ws.onmessage = function(evt) {
console.log("Received Message: " + evt.data);
document.getElementById("info").innerText = "Received Message: " + evt.data
};
ws.onclose = function(evt) {
console.log("Connection closed.");
document.getElementById("info").innerText = "Connection closed."
window.location.reload();
}
}
wbst_send = function (){
var message;
message = document.getElementById("wsinput").value;
if (message.length != 0) ws.send(message);
}
wbst_close = function (){
document.getElementById("info").innerText = "Connection closed."
ws.close();
window.location.reload();
}
</script>
用Chrome浏览器打开: 点击"connect"后,可以向STM32 Websocket Server发送信息,相关过程跟踪信息在控制台(Console)可以查看到。
–End–
|