本人参考大佬的代码移植进单片机,调试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;
}