单片机开发板sv32wb0x,C语言,创建websocket客户端

本人参考大佬的代码移植进单片机,调试BUG后并且成功跑通,如果你不了解websocket协议建议参考Linux下c语言实验Websocket通讯 含客户端和服务器测试代码 我只是把这位大佬写的提取出我需要的
基于单片机lwip网络编程,本代码中所有网络编程的函数前面都可以加lwip,本人懒得换,例如lwip_socket(); STM32应该修改一下也能使用
本菜鸟也仅仅是移植后成功跑通,如果有什么bug建议欢迎探讨,或者和上面链接原文博主交流.本人也仅仅是移植成功并且调试完bug能基本使用.
代码中用了OS_MemFree();OS_MemCalloc(); OS_MsDelay();都说freertos库函数,你的单片机要是没有移植freertos就自己改回去
测试工具:下载链接
如果本文章对你有作用给我点个赞!!!还有你借助此文章完成你的需求后希望能跟我分享一下
测试工具测试
测试工具测试截图

main函数

int main(int argc,char** argv)
{
		int fd, pid;
		int ret;
		int heart = 0;
		Ws_DataType retPkgType;


		char recv_buff[RECV_PKG_MAX];
		char send_buff[SEND_PKG_MAX];

		int port = SERVER_PORT;
		char ip[32] = SERVER_IP;
		char path[64] = SERVER_PATH;

		//传参接收,根据你的串口调试助手判断写或者在linux ./client 192.168.1.1 9999
		//根据所需要连接的服务器ip和端口配置,或者也可以忽略直接写死ip端口
		if (argc > 2) {
			memset(ip, 0, sizeof(ip));
			strcpy(ip, argv[2]);
		}
		if (argc > 3) {
			sscanf(argv[3], "%d", &port);
		}
		if (argc > 4) {
		memset(path, 0, sizeof(path));
		strcpy(path, argv[4]);
		}
		//用本进程pid作为唯一标识
		pid = getpid();
		printf("client ws://%s:%d%s pid/%d\r\n", ip, port, path, pid);
		//3秒超时连接服务器
		//同时大量接入时,服务器不能及时响应,可以加大超时时间
		if ((fd = ws_requestServer(ip, port, path, 0)) <= 0)
		{
			// return -1;单片机性能不高,直接给它多一点时间
		}
		//循环接收服务器下发
		while (1)
		{
			//接收数据
			ret = ws_recv(fd, recv_buff, sizeof(recv_buff), &retPkgType);
			//正常包
			if (ret > 0)
			{
				printf("client(%d): recv len/%d %s\r\n", pid, ret, recv_buff);

				//根据服务器下发内容做出反应
				if (strstr(recv_buff, "Say hi~") != NULL)
				{
					snprintf(send_buff, sizeof(send_buff), "Say hi~ I am client(%d)", pid);
					ret = ws_send(fd, send_buff, strlen(send_buff), true, WDT_TXTDATA);
				}
				else if (strstr(recv_buff, "I am ") != NULL)
					;
				//send返回异常, 连接已断开
				if (ret <= 0)
				{
					printf("client(%d): send failed %d, disconnect now ...\r\n", pid, ret);
					break;
				}
			}
			//非包数据
			else if (ret < 0)
				printf("client(%d): recv len/%d bad pkg %s\r\n", pid, -ret, recv_buff);
			//收到特殊包
			else if (retPkgType == WDT_DISCONN)
			{
				printf("client(%d): recv WDT_DISCONN \r\n", pid);
				break;
			}
			else if (retPkgType == WDT_PING)
				printf("client(%d): recv WDT_PING \r\n", pid);
			else if (retPkgType == WDT_PONG)
				printf("client(%d): recv WDT_PONG \r\n", pid);

			//一个合格的客户端,应该定时给服务器发心跳,以检测连接状态
			heart += 10;
			if (heart > 3000)
			{
				heart = 0;

				//发送心跳
#if 1
				//用普通数据
				snprintf(send_buff, sizeof(send_buff), "Heart from client(%d) %s", pid, ws_time());
				// ret = ws_send(fd, send_buff, sizeof(send_buff), true, WDT_TXTDATA); //大数据量压力测试
				ret = ws_send(fd, send_buff, strlen(send_buff), true, WDT_TXTDATA);
#else
				//用ping包代替心跳
				ret =  ws_send(fd, NULL, 0, true, WDT_PING);
#endif
				//send返回异常, 连接已断开
				if (ret <= 0)
				{
					printf("client(%d): send failed %d, disconnect now ...\r\n", pid, ret);
					break;
				}
			}
			
			ws_delayms(10);
		}

		close(fd);
		printf("client(%d): close\r\n", pid);
		return 0;
}

头文件

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h> //使用 malloc, calloc等动态分配内存方法
#include <time.h> 
#include"sockets.h"
#define WS_INFO(...) fprintf(stdout, "[WS_INFO] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stdout, __VA_ARGS__)
#define WS_ERR(...) fprintf(stderr, "[WS_ERR] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stderr, __VA_ARGS__)
// websocket根据data[0]判别数据包类型
// 比如0x81 = 0x80 | 0x1 为一个txt类型数据包

#define SEND_PKG_MAX (1024 * 1)
#define RECV_PKG_MAX (SEND_PKG_MAX + 16)
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9999
#define SERVER_PATH "/"

typedef enum
{
    WDT_NULL = 0, // 非标准数据包
    WDT_MINDATA,  // 0x0:中间数据包
    WDT_TXTDATA,  // 0x1:txt类型数据包
    WDT_BINDATA,  // 0x2:bin类型数据包
    WDT_DISCONN,  // 0x8:断开连接类型数据包 收到后需手动 close(fd)
    WDT_PING,     // 0x8:ping类型数据包 ws_recv 函数内自动回复pong
    WDT_PONG,     // 0xA:pong类型数据包
} Ws_DataType;

void ws_delayms(uint32_t ms);
char *ws_time(void);
int32_t ws_requestServer(char *ip, int32_t port, char *path, int32_t timeoutMs);
int32_t wss_base64_encode(const uint8_t *bindata, char *base64, int32_t binlength);
int32_t ws_send(int32_t fd, void *buff, int32_t buffLen, bool mask, Ws_DataType type);
int32_t ws_recv(int32_t fd, void *buff, int32_t buffSize, Ws_DataType *retType);
char *sha1_hash(const char *source);

static void WS_HEX(FILE *f, void *dat, uint32_t len);
static int32_t ws_buildShakeKey(char *key);
static void ws_getRandomString(char *buff, uint32_t len);
static void ws_buildHttpHead(char *ip, int32_t port, char *path, char *shakeKey, char *package);
static int32_t ws_matchShakeKey(char *clientKey, int32_t clientKeyLen, char *acceptKey, int32_t acceptKeyLen);
static int32_t ws_buildRespondShakeKey(char *acceptKey, uint32_t acceptKeyLen, char *respondKey);
static int32_t ws_enPackage(
    uint8_t *data,
    uint32_t dataLen,
    uint8_t *package,
    uint32_t packageMaxLen,
    bool mask,
    Ws_DataType type);
static int32_t ws_dePackage(
    uint8_t* data,
    uint32_t len,
    uint32_t* retDataLen,
    uint32_t* retHeadLen,
    Ws_DataType* retPkgType);

剩下的所有代码

#include "osal.h"//os_库
void ws_delayms(uint32_t ms)
{
    OS_MsDelay(ms);
    //ws_delayus(ms * 1000);
}

static void WS_HEX(FILE* f, void* dat, uint32_t len)
{
    uint8_t* p = (uint8_t*)dat;
    uint32_t i;
    for (i = 0; i < len; i++)
        fprintf(f, "%02X ", p[i]);
    fprintf(f, "\r\n");
}

/*******************************************************************************
 * 名称: ws_requestServer
 * 功能: 向websocket服务器发送http(携带握手key), 以和服务器构建连接, 非阻塞模式
 * 参数:
 *      ip: 服务器ip
 *      port: 服务器端口
 *      path: 接口地址,格式如"/tmp/xxx"
 *      timeoutMs: connect阶段超时设置,接收阶段为timeoutMs*2,写0使用默认值1000
 * 返回: >0 返回连接描述符 <= 0 连接失败或超时,所花费的时间ms的负值
 * 说明: 无
 ******************************************************************************/
int32_t ws_requestServer(char* ip, int32_t port, char* path, int32_t timeoutMs)
{
    int32_t ret, fd;
    int32_t timeoutCount = 0;
    char retBuff[512] = {0};
    char httpHead[512] = {0};
    char shakeKey[128] = {0};
    char tempIp[128] = {0};
    char* p;

    //服务器端网络地址结构体
    struct sockaddr_in report_addr;
    memset(&report_addr, 0, sizeof(report_addr)); //数据初始化--清零
    report_addr.sin_family = AF_INET;             //设置为IP通信
    report_addr.sin_port = htons(port);           //服务器端口号

    //服务器IP地址, 自动域名转换
    report_addr.sin_addr.s_addr = inet_addr(ip);

    //默认超时1秒
    if (timeoutMs == 0)
        timeoutMs = 1000;

    //create unix socket
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        WS_ERR("socket error\r\n");
        return -1;
    }

    //connect
    timeoutCount = 0;
    while (connect(fd, (struct sockaddr *)&report_addr, sizeof(struct sockaddr)) != 0)
    {
        if (++timeoutCount > timeoutMs)
        {
            WS_ERR("connect to %s:%d timeout(%dms)\r\n", ip, port, timeoutCount);
            close(fd);
            return -timeoutCount;
        }
        ws_delayms(1);
    }
    //非阻塞
    ret = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, ret | O_NONBLOCK);

    //发送http协议头
    memset(shakeKey, 0, sizeof(shakeKey));
    ws_buildShakeKey(shakeKey);                                   //创建握手key
    memset(httpHead, 0, sizeof(httpHead));                        //创建协议包
    ws_buildHttpHead(ip, port, path, shakeKey, (char*)httpHead); //组装http请求头
    send(fd, httpHead, strlen((const char*)httpHead), 0);

    while (1)
    {
        memset(retBuff, 0, sizeof(retBuff));
        ret = recv(fd, retBuff, sizeof(retBuff), 0);
        if (ret > 0){
            //返回的是http回应信息
            if (strncmp((const char*)retBuff, "HTTP", 4) == 0)
            {
                //定位到握手字符串
                if ((p = strstr((char*)retBuff, "Sec-WebSocket-Accept: ")) != NULL)
                {
                    p += strlen("Sec-WebSocket-Accept: ");
                    sscanf((const char*)p, "%s\r\n", p);

                    //比对握手信息
                    if (ws_matchShakeKey(shakeKey, strlen((const char*)shakeKey), p, strlen((const char*)p)) == 0){
                        return fd;
                    }
                       
                    //握手信号不对, 重发协议包
                    else
                        ret = send(fd, httpHead, strlen((const char*)httpHead), 0);
                }
                //重发协议包
                else
                    ret = send(fd, httpHead, strlen((const char*)httpHead), 0);
            }
            //显示异常返回数据
            else
            {
                //#ifdef WS_DEBUG
                WS_ERR("recv: len %d / unknown context\r\n%s\r\n", ret, retBuff);
                WS_HEX(stderr, retBuff, ret);
                //#endif
            }
        }
        ws_delayms(1);
        //超时检查
        if (++timeoutCount > timeoutMs * 2)
            break;
    }
    //连接失败,返回耗时(负值)

    close(fd);
    return -timeoutCount;
}

/*******************************************************************************
 * 名称: ws_buildShakeKey
 * 功能: client端使用随机数构建握手用的key
 * 参数: *key: 随机生成的握手key
 * 返回: key的长度
 * 说明: 无
 ******************************************************************************/
static int32_t ws_buildShakeKey(char* key)
{
    char tempKey[16] = {0};
    ws_getRandomString(tempKey, 16);
    //char *tempKey = "asdfghjklqwertyu";//可写死调试
    return wss_base64_encode((const uint8_t*)tempKey, (char*)key, 16);
}

/*******************************************************************************
 * 名称: ws_getRandomString
 * 功能: 生成随机字符串
 * 参数: 
 *      buff: 随机字符串存储到
 *      len: 生成随机字符串长度
 * 返回: 无
 * 说明: 无
 ******************************************************************************/
static void ws_getRandomString(char* buff, uint32_t len)
{
    
    uint32_t i;
    uint8_t temp;
    //srand((int32_t)time(0));加上随机数种子,程序会崩溃
    for (i = 0; i < len; i++)
    {
        temp = (uint8_t)(rand() % 256);
        if (temp == 0) //随机数不要0
            temp = 128;
        buff[i] = temp;
    }
}

//base64编/解码用的基础字符集
static const char ws_base64char[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*******************************************************************************
 * 名称: ws_base64_encode
 * 功能: ascii编码为base64格式
 * 参数: 
 *      bindata: ascii字符串输入
 *      base64: base64字符串输出
 *      binlength: bindata的长度
 * 返回: base64字符串长度
 * 说明: 无
 ******************************************************************************/
int32_t wss_base64_encode(const uint8_t* bindata, char* base64, int32_t binlength)
{

    int32_t i, j;
    uint8_t current;
    for (i = 0, j = 0; i < binlength; i += 3)
    {
        current = (bindata[i] >> 2);
        current &= (uint8_t)0x3F;
        base64[j++] = ws_base64char[(int32_t)current];
        current = ((uint8_t)(bindata[i] << 4)) & ((uint8_t)0x30);
        if (i + 1 >= binlength)
        {
            base64[j++] = ws_base64char[(int32_t)current];
            base64[j++] = '=';
            base64[j++] = '=';
            break;
        }
        current |= ((uint8_t)(bindata[i + 1] >> 4)) & ((uint8_t)0x0F);
        base64[j++] = ws_base64char[(int32_t)current];
        current = ((uint8_t)(bindata[i + 1] << 2)) & ((uint8_t)0x3C);
        if (i + 2 >= binlength)
        {
            base64[j++] = ws_base64char[(int32_t)current];
            base64[j++] = '=';
            break;
        }
        current |= ((uint8_t)(bindata[i + 2] >> 6)) & ((uint8_t)0x03);
        base64[j++] = ws_base64char[(int32_t)current];
        current = ((uint8_t)bindata[i + 2]) & ((uint8_t)0x3F);
        base64[j++] = ws_base64char[(int32_t)current];
    }
    base64[j] = '\0';

    return j;
}

/*******************************************************************************
 * 名称: ws_buildHttpHead
 * 功能: 构建client端连接服务器时的http协议头, 注意websocket是GET形式的
 * 参数:
 *      ip: 要连接的服务器ip字符串
 *      port: 服务器端口
 *      path: 要连接的端口地址
 *      shakeKey: 握手key, 可以由任意的16位字符串打包成base64后得到
 *      package: 存储最后打包好的内容
 * 返回: 无
 * 说明: 无
 ******************************************************************************/
static void ws_buildHttpHead(char* ip, int32_t port, char* path, char* shakeKey, char* package)
{

    const char httpDemo[] =
        "GET %s HTTP/1.1\r\n"
        "Connection: Upgrade\r\n"
        "Host: %s:%d\r\n"
        "Sec-WebSocket-Key: %s\r\n"
        "Sec-WebSocket-Version: 13\r\n"
        "Upgrade: websocket\r\n\r\n";
    sprintf(package, httpDemo, path, ip, port, shakeKey);
}

/*******************************************************************************
 * 名称: ws_matchShakeKey
 * 功能: client端收到来自服务器回应的key后进行匹配,以验证握手成功
 * 参数:
 *      clientKey: client端请求握手时发给服务器的key
 *      clientKeyLen: 长度
 *      acceptKey: 服务器回应的key
 *      acceptKeyLen: 长度
 * 返回: 0 成功  -1 失败
 * 说明: 无
 ******************************************************************************/
static int32_t ws_matchShakeKey(char* clientKey, int32_t clientKeyLen, char* acceptKey, int32_t acceptKeyLen)
{

    int32_t retLen;
    char tempKey[256] = {0};

    retLen = ws_buildRespondShakeKey(clientKey, clientKeyLen, tempKey);

    if (retLen != acceptKeyLen)
    {
        WS_ERR("len err, clientKey[%d] != acceptKey[%d]\r\n", retLen, acceptKeyLen);
        return -1;
    }
    else if (strcmp((const char*)tempKey, (const char*)acceptKey) != 0)
    {
        WS_ERR("strcmp err, clientKey[%s -> %s] != acceptKey[%s]\r\n", clientKey, tempKey, acceptKey);
        return -1;
    }
    return 0;
}

/*******************************************************************************
 * 名称: ws_buildRespondShakeKey
 * 功能: server端在接收client端的key后,构建回应用的key
 * 参数:
 *      acceptKey: 来自客户端的key字符串
 *      acceptKeyLen: 长度
 *      respondKey:  在 acceptKey 之后加上 GUID, 再sha1哈希, 再转成base64得到 respondKey
 * 返回: respondKey的长度(肯定比acceptKey要长)
 * 说明: 无
 ******************************************************************************/
static int32_t ws_buildRespondShakeKey(char* acceptKey, uint32_t acceptKeyLen, char* respondKey)
{
    char* clientKey;
    char* sha1DataTemp;
    uint8_t* sha1Data;
    int32_t i, j, sha1DataTempLen, ret;
    const char guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    uint32_t guidLen;

    if (acceptKey == NULL)
        return 0;

    guidLen = sizeof(guid);
    //clientKey = (char*)calloc(acceptKeyLen + guidLen + 10, sizeof(char));
    clientKey=(char*)OS_MemCalloc(acceptKeyLen + guidLen + 10, sizeof(char));
    memcpy(clientKey, acceptKey, acceptKeyLen);
    memcpy(&clientKey[acceptKeyLen], guid, guidLen);

    sha1DataTemp = sha1_hash(clientKey);
    sha1DataTempLen = strlen((const char*)sha1DataTemp);
    //sha1Data = (uint8_t*)calloc(sha1DataTempLen / 2 + 1, sizeof(char));
    sha1Data =(uint8_t*)OS_MemCalloc(sha1DataTempLen / 2 + 1, sizeof(char));
    //把hex字符串如"12ABCDEF",转为数值数组如{0x12,0xAB,0xCD,0xEF}
    for (i = j = 0; i < sha1DataTempLen;)
    {
        if (sha1DataTemp[i] > '9')
            sha1Data[j] = (10 + sha1DataTemp[i] - 'A') << 4;
        else
            sha1Data[j] = (sha1DataTemp[i] - '0') << 4;

        i += 1;

        if (sha1DataTemp[i] > '9')
            sha1Data[j] |= (10 + sha1DataTemp[i] - 'A');
        else
            sha1Data[j] |= (sha1DataTemp[i] - '0');

        i += 1;
        j += 1;
    }

    ret = wss_base64_encode((const uint8_t*)sha1Data, (char*)respondKey, j);
 
    //free(sha1DataTemp);
    //free(sha1Data);
    //free(clientKey);
    OS_MemFree(sha1DataTemp);
    OS_MemFree(sha1Data);
    OS_MemFree(clientKey);
    return ret;
}

/*******************************************************************************
 * 名称: ws_send
 * 功能: websocket数据基本打包和发送
 * 参数:
 *      fd: 连接描述符
 *      *buff: 数据
 *      buffLen: 长度
 *      mask: 数据是否使用掩码, 客户端到服务器必须使用掩码模式
 *      type: 数据要要以什么识别头类型发送(txt, bin, ping, pong ...)
 * 返回: 调用send的返回
 * 说明: 无
 ******************************************************************************/
int32_t ws_send(int32_t fd, void* buff, int32_t buffLen, bool mask, Ws_DataType type)
{
    uint8_t* wsPkg = NULL;
    int32_t retLen, ret;
    //参数检查
    if (buffLen < 0)
        return 0;
    //非包数据发送
    if (type == WDT_NULL)
        return send(fd, buff, buffLen, MSG_NOSIGNAL);
    //数据打包 +14 预留类型、掩码、长度保存位
    //wsPkg = (uint8_t*)calloc(buffLen + 14, sizeof(uint8_t));
    wsPkg = (uint8_t*)OS_MemCalloc(buffLen + 14, sizeof(uint8_t));
    retLen = ws_enPackage((uint8_t*)buff, buffLen, wsPkg, (buffLen + 14), mask, type);
    if (retLen <= 0)
    {
        //free(wsPkg);
        OS_MemFree(wsPkg);
        return 0;
    }
#ifdef WS_DEBUG
    //显示数据
    WS_INFO("ws_send: len/%d\r\n", retLen);
    WS_HEX(stdout, wsPkg, retLen);
#endif
    ret = send(fd, wsPkg, retLen, 0);
    //free(wsPkg);
    OS_MemFree(wsPkg);
    return ret;
}

/*******************************************************************************
 * 名称: ws_enPackage
 * 功能: websocket数据收发阶段的数据打包, 通常client发server的数据都要mask(掩码)处理, 反之server到client却不用
 * 参数:
 *      data: 准备发出的数据
 *      dataLen: 长度
 *      package: 打包后存储地址
 *      packageMaxLen: 存储地址可用长度
 *      mask: 是否使用掩码     1要   0 不要
 *      type: 数据类型, 由打包后第一个字节决定, 这里默认是数据传输, 即0x81
 * 返回: 打包后的长度(会比原数据长2~14个字节不等) <=0 打包失败 
 * 说明: 无
 ******************************************************************************/
static int32_t ws_enPackage(
    uint8_t* data,
    uint32_t dataLen,
    uint8_t* package,
    uint32_t packageMaxLen,
    bool mask,
    Ws_DataType type)
{
    uint32_t i, pkgLen = 0;
    //掩码
    uint8_t maskKey[4] = {0};
    uint32_t maskCount = 0;
    //最小长度检查
    if (packageMaxLen < 2)
        return -1;
    //根据包类型设置头字节
    if (type == WDT_MINDATA)
        *package++ = 0x80;
    else if (type == WDT_TXTDATA)
        *package++ = 0x81;
    else if (type == WDT_BINDATA)
        *package++ = 0x82;
    else if (type == WDT_DISCONN)
        *package++ = 0x88;
    else if (type == WDT_PING)
        *package++ = 0x89;
    else if (type == WDT_PONG)
        *package++ = 0x8A;
    else
        return -1;
    pkgLen += 1;
    //掩码位
    if (mask)
        *package = 0x80;
    //半字节记录长度
    if (dataLen < 126)
    {
        *package++ |= (dataLen & 0x7F);
        pkgLen += 1;
    }
    //2字节记录长度
    else if (dataLen < 65536)
    {
        if (packageMaxLen < 4)
            return -1;
        *package++ |= 0x7E;
        *package++ = (uint8_t)((dataLen >> 8) & 0xFF);
        *package++ = (uint8_t)((dataLen >> 0) & 0xFF);
        pkgLen += 3;
    }
    //8字节记录长度
    else
    {
        if (packageMaxLen < 10)
            return -1;
        *package++ |= 0x7F;
        *package++ = 0; //数据长度变量是 uint32_t dataLen, 暂时没有那么多数据
        *package++ = 0;
        *package++ = 0;
        *package++ = 0;
        *package++ = (uint8_t)((dataLen >> 24) & 0xFF); //到这里就够传4GB数据了
        *package++ = (uint8_t)((dataLen >> 16) & 0xFF);
        *package++ = (uint8_t)((dataLen >> 8) & 0xFF);
        *package++ = (uint8_t)((dataLen >> 0) & 0xFF);
        pkgLen += 9;
    }
    //数据使用掩码时,使用异或解码,maskKey[4]依次和数据异或运算,逻辑如下
    if (mask)
    {
        //长度不足
        if (packageMaxLen < pkgLen + dataLen + 4)
            return -1;
        //随机生成掩码
        ws_getRandomString((char*)maskKey, sizeof(maskKey));

        *package++ = maskKey[0];
        *package++ = maskKey[1];
        *package++ = maskKey[2];
        *package++ = maskKey[3];
        pkgLen += 4;
        for (i = 0, maskCount = 0; i < dataLen; i++, maskCount++)
        {
            //maskKey[4]循环使用
            if (maskCount == 4) //sizeof(maskKey))
                maskCount = 0;
            //异或运算后得到数据
            *package++ = maskKey[maskCount] ^ data[i];
        }
        pkgLen += i;
        //断尾
        *package = '\0';
    }
    //数据没使用掩码, 直接复制数据段
    else
    {
        //长度不足
        if (packageMaxLen < pkgLen + dataLen)
            return -1;
        //这种方法,data指针位置相近时拷贝异常
        // memcpy(package, data, dataLen);
        //手动拷贝
        for (i = 0; i < dataLen; i++)
            *package++ = data[i];
        pkgLen += i;
        //断尾
        *package = '\0';
    }

    return pkgLen;
}


/*******************************************************************************
 * 名称: ws_recv
 * 功能: websocket数据接收和基本解包
 * 参数: 
 *      fd: 连接描述符
 *      buff: 数据接收地址
 *      buffSize: 接收区可用最大长度,至少16字节
 * 返回:
 *      =0 没有收到有效数据(或者收到特殊包,如果是 WDT_DISCONN 则fd已被close)
 *      >0 成功接收并解包数据
 *      <0 非标准数据包数据的长度
 * 说明: 无
 ******************************************************************************/
int32_t ws_recv(int32_t fd, void* buff, int32_t buffSize, Ws_DataType* retType)
{
    int32_t ret;
    int32_t retRecv = 0;     //调用recv的返回
    int32_t retDePkg;        //调用解包的返回
    uint32_t retDataLen = 0; //解包得到的数据段长度
    uint32_t retHeadLen = 0; //解包得到的包头部长度
    int32_t retFinal = 0;    //最终返回
    uint32_t timeout = 0;    //接收超时计数

    char tmp[16]; //为防止一次接收到多包数据(粘包),先尝试性接收ws头部字节,得知总长度后再接收剩下部分
    Ws_DataType retPkgType = WDT_NULL; //默认返回包类型
    char* cBuff = (char*)buff; //转换指针类型

    //丢弃数据
    if (!buff || buffSize < 1)
    {
        while (recv(fd, tmp, sizeof(tmp), MSG_NOSIGNAL) > 0)
            ;
    }
    //先接收数据头部,头部最大2+4+8=14字节
    else
    {
        if (buffSize < 16)
            WS_ERR("error, buffSize must be >= 16 \r\n");
        else
            retRecv = recv(fd, buff, 14, MSG_NOSIGNAL);
    }
    if (retRecv > 0)
    {
        //数据解包
        retDePkg = ws_dePackage((uint8_t*)buff, retRecv, &retDataLen, &retHeadLen, &retPkgType);
        //1. 非标准数据包数据,再接收一次(防止丢数据),之后返回"负len"长度值
        //2. buffSize不足以收下这一包数据,当作非标准数据包数据处理,能收多少算多少
        if (retDePkg == 0 || (retDePkg < 0 && retRecv - retDePkg > buffSize))
        {
            //能收多少算多少
            retRecv += recv(fd, &cBuff[retRecv], buffSize - retRecv, MSG_NOSIGNAL);
            //对于包过大的问题
            if (retDePkg < 0)
            {
                //1. 发出警告
                WS_ERR("warnning, pkgLen(%d) > buffSize(%d)\r\n", retRecv - retDePkg, buffSize);
                //2. 把这包数据丢弃,以免影响后续包
                while (recv(fd, tmp, sizeof(tmp), MSG_NOSIGNAL) > 0)
                    ;
            }
            retFinal = -retRecv;
#ifdef WS_DEBUG
            //显示数据
            WS_INFO("ws_recv1: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n",
                    retRecv, retDePkg, retDataLen, retHeadLen, retPkgType);
            WS_HEX(stdout, buff, retRecv);
#endif
        }
        //正常收包
        else
        {
            //检查是否需要续传
            if (retDePkg < 0)
            {
                //再接收一次(通常情况)
                ret = recv(fd, &cBuff[retRecv], -retDePkg, MSG_NOSIGNAL);
                if (ret > 0)
                {
                    retRecv += ret;
                    retDePkg += ret;
                }
                //数据量上百K时需要多次recv,无数据200ms超时,继续接收
                for (timeout = 0; timeout < 200 && retDePkg < 0;)
                {
                    ws_delayms(5);
                    timeout += 5;
                    ret = recv(fd, &cBuff[retRecv], -retDePkg, MSG_NOSIGNAL);
                    if (ret > 0)
                    {
                        timeout = 0;
                        retRecv += ret;
                        retDePkg += ret;
                    }
                }
#ifdef WS_DEBUG
                //显示数据
                WS_INFO("ws_recv2: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n",
                        retRecv, retDePkg, retDataLen, retHeadLen, retPkgType);
                WS_HEX(stdout, buff, retRecv);
#endif
                //二次解包
                retDePkg = ws_dePackage((uint8_t*)buff, retRecv, &retDataLen, &retHeadLen, &retPkgType);
            }
#ifdef WS_DEBUG
            //显示数据
            WS_INFO("ws_recv3: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n",
                    retRecv, retDePkg, retDataLen, retHeadLen, retPkgType);
            WS_HEX(stdout, buff, retRecv);
#endif
            //一包数据终于完整的接收完了...
            if (retDePkg > 0)
            {
                //收到 PING 包,应自动回复 PONG
                if (retPkgType == WDT_PING)
                {
                    //自动 ping-pong
                    ws_send(fd, NULL, 0, false, WDT_PONG);
                    // WS_INFO("ws_recv: WDT_PING\r\n");
                    retFinal = 0;
                }
                //收到 PONG 包
                else if (retPkgType == WDT_PONG)
                {
                    // WS_INFO("ws_recv: WDT_PONG\r\n");
                    retFinal = 0;
                }
                //收到 断连 包
                else if (retPkgType == WDT_DISCONN)
                {
                    // WS_INFO("ws_recv: WDT_DISCONN\r\n");
                    retFinal = 0;
                }
                //其它正常数据包
                else
                    retFinal = retDePkg;
            }
            //未曾设想的道路...
            else
                retFinal = -retRecv;
        }
    }
    //返回包类型
    if (retType)
        *retType = retPkgType;

    return retFinal;
}

/*******************************************************************************
 * 名称: ws_dePackage
 * 功能: websocket数据收发阶段的数据解包,通常client发server的数据都要mask(掩码)处理,反之server到client却不用
 * 参数:
 *      data: 要解包的数据,解包后的数据会覆写到这里
 *      len: 要解包的数据的长度
 *      retDataLen: 解包数据段长度信息
 *      retHeadLen: 解包头部长度信息
 *      retPkgType: 识别包类型
 * 返回:
 *      0: 格式错误,非标准数据包数据
 *      <0: 识别包但不完整(能解析类型、掩码、长度),返回缺少的数据量(负值)
 *      >0: 解包数据成功,返回数据长度,等于retDataLen
 * 说明:
 *      建议recv时先接收14字节然后解包,根据返回缺失长度再recv一次,最后再解包,这样可有效避免连包时只解析到一包的问题
 ******************************************************************************/
static int32_t ws_dePackage(
    uint8_t* data,
    uint32_t len,
    uint32_t* retDataLen,
    uint32_t* retHeadLen,
    Ws_DataType* retPkgType)
{
    uint32_t cIn, cOut;
    //包类型
    uint8_t type;
    //数据段起始位置
    uint32_t dataOffset = 2;
    //数据段长度
    uint32_t dataLen = 0;
    //掩码
    uint8_t maskKey[4] = {0};
    bool mask = false;
    uint8_t maskCount = 0;
    //数据长度过短
    if (len < 2)
        return 0;
    //解析包类型
    if ((data[0] & 0x80) == 0x80)
    {
        type = data[0] & 0x0F;
        if (type == 0x00)
            *retPkgType = WDT_MINDATA;
        else if (type == 0x01)
            *retPkgType = WDT_TXTDATA;
        else if (type == 0x02)
            *retPkgType = WDT_BINDATA;
        else if (type == 0x08)
            *retPkgType = WDT_DISCONN;
        else if (type == 0x09)
            *retPkgType = WDT_PING;
        else if (type == 0x0A)
            *retPkgType = WDT_PONG;
        else
            return 0;
    }
    else
        return 0;
    //是否掩码,及长度占用字节数
    if ((data[1] & 0x80) == 0x80)
    {
        mask = true;
        maskCount = 4;
    }
    //2字节记录长度
    dataLen = data[1] & 0x7F;
    if (dataLen == 126)
    {
        //数据长度不足以包含长度信息
        if (len < 4)
            return 0;
        //2字节记录长度
        dataLen = data[2];
        dataLen = (dataLen << 8) + data[3];
        //转储长度信息
        *retDataLen = dataLen;
        *retHeadLen = 4 + maskCount;
        //数据长度不足以包含掩码信息
        if (len < (uint32_t)(4 + maskCount))
            return -(int32_t)(4 + maskCount + dataLen - len);
        //获得掩码
        if (mask)
        {
            maskKey[0] = data[4];
            maskKey[1] = data[5];
            maskKey[2] = data[6];
            maskKey[3] = data[7];
            dataOffset = 8;
        }
        else
            dataOffset = 4;
    }
    //8字节记录长度
    else if (dataLen == 127)
    {
        //数据长度不足以包含长度信息
        if (len < 10)
            return 0;
        //使用8个字节存储长度时,前4位必须为0,装不下那么多数据...
        if (data[2] != 0 || data[3] != 0 || data[4] != 0 || data[5] != 0)
            return 0;
        //8字节记录长度
        dataLen = data[6];
        dataLen = (dataLen << 8) | data[7];
        dataLen = (dataLen << 8) | data[8];
        dataLen = (dataLen << 8) | data[9];
        //转储长度信息
        *retDataLen = dataLen;
        *retHeadLen = 10 + maskCount;
        //数据长度不足以包含掩码信息
        if (len < (uint32_t)(10 + maskCount))
            return -(int32_t)(10 + maskCount + dataLen - len);
        //获得掩码
        if (mask)
        {
            maskKey[0] = data[10];
            maskKey[1] = data[11];
            maskKey[2] = data[12];
            maskKey[3] = data[13];
            dataOffset = 14;
        }
        else
            dataOffset = 10;
    }
    //半字节记录长度
    else
    {
        //转储长度信息
        *retDataLen = dataLen;
        *retHeadLen = 2 + maskCount;
        //数据长度不足
        if (len < (uint32_t)(2 + maskCount))
            return -(int32_t)(2 + maskCount + dataLen - len);
        //获得掩码
        if (mask)
        {
            maskKey[0] = data[2];
            maskKey[1] = data[3];
            maskKey[2] = data[4];
            maskKey[3] = data[5];
            dataOffset = 6;
        }
        else
            dataOffset = 2;
    }
    //数据长度不足以包含完整数据段
    if (len < dataLen + dataOffset)
        return -(int32_t)(dataLen + dataOffset - len);
    //解包数据使用掩码时, 使用异或解码, maskKey[4]依次和数据异或运算, 逻辑如下
    if (mask)
    {
        cIn = dataOffset;
        cOut = 0;
        maskCount = 0;
        for (; cOut < dataLen; cIn++, cOut++, maskCount++)
        {
            //maskKey[4]循环使用
            if (maskCount == 4) //sizeof(maskKey))
                maskCount = 0;
            //异或运算后得到数据
            data[cOut] = maskKey[maskCount] ^ data[cIn];
        }
        //断尾
        data[cOut] = '\0';
    }
    //解包数据没使用掩码, 直接复制数据段
    else
    {
        //这种方法,data指针位置相近时拷贝异常
        // memcpy(data, &data[dataOffset], dataLen);
        //手动拷贝
        cIn = dataOffset;
        cOut = 0;
        for (; cOut < dataLen; cIn++, cOut++)
            data[cOut] = data[cIn];
        //断尾
        data[dataLen] = '\0';
    }
    //有些特殊包数据段长度可能为0,这里为区分格式错误返回,置为1
    if (dataLen == 0)
        dataLen = 1;
    return dataLen;
}
//返回时间戳字符串,格式 HH:MM:SS
char* ws_time(void)
{
    static char timeStr[9] = {0};
    struct timeval tv = {0};
    gettimeofday(&tv, NULL);
    snprintf(timeStr, sizeof(timeStr), "%02ld:%02ld:%02ld",
             (tv.tv_sec % 86400 / 3600 + 8) % 24,
             tv.tv_sec % 3600 / 60,
             tv.tv_sec % 60);
    return timeStr;
}

//==================== 加密方法 sha1哈希 ====================

typedef struct SHA1Context
{
    uint32_t Message_Digest[5];
    uint32_t Length_Low;
    uint32_t Length_High;
    uint8_t Message_Block[64];
    int32_t Message_Block_Index;
    int32_t Computed;
    int32_t Corrupted;
} SHA1Context;

#define SHA1CircularShift(bits, word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32 - (bits))))

static void SHA1ProcessMessageBlock(SHA1Context *context)
{
    const uint32_t K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6};
    int32_t t;
    uint32_t temp;
    uint32_t W[80];
    uint32_t A, B, C, D, E;

    for (t = 0; t < 16; t++)
    {
        W[t] = ((uint32_t)context->Message_Block[t * 4]) << 24;
        W[t] |= ((uint32_t)context->Message_Block[t * 4 + 1]) << 16;
        W[t] |= ((uint32_t)context->Message_Block[t * 4 + 2]) << 8;
        W[t] |= ((uint32_t)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;
}

static 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;
}

static 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);
}

static int32_t SHA1Result(SHA1Context* context)
{
    if (context->Corrupted)
    {
        return 0;
    }
    if (!context->Computed)
    {
        SHA1PadMessage(context);
        context->Computed = 1;
    }
    return 1;
}

static void SHA1Input(SHA1Context* context, const char* message_array, uint32_t 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++;
    }
}

char* sha1_hash(const char* source)
{
    SHA1Context sha;
    char* buff = NULL;

    SHA1Reset(&sha);
    SHA1Input(&sha, source, strlen(source));

    if (!SHA1Result(&sha))
        WS_ERR("SHA1 ERROR: Could not compute message digest \r\n");
    else
    {
        //buff = (char*)calloc(128, sizeof(char));
        buff = (char*)OS_MemCalloc(128, sizeof(char));
        sprintf(buff, "%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 buff;
}
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值