├── .gitignore ├── kill.sh ├── python3_test ├── README.md ├── server.py └── client.py ├── Makefile ├── start.sh ├── README.md ├── c_test ├── ws_com.h ├── ws_server.h ├── main_server.c ├── main_client.c ├── ws_server.c └── ws_com.c └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | server 3 | client 4 | *.bin -------------------------------------------------------------------------------- /kill.sh: -------------------------------------------------------------------------------- 1 | 2 | killall client 3 | sleep 1 4 | killall server 5 | -------------------------------------------------------------------------------- /python3_test/README.md: -------------------------------------------------------------------------------- 1 | # 依赖库安装 2 | 3 | * pip3 install websocket-client 4 | * pip3 install websocket-server 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | #CC=arm-linux-gnueabihf-gcc 3 | CC=gcc 4 | 5 | target: 6 | $(CC) -Wall -o client ./c_test/main_client.c ./c_test/ws_com.c ./c_test/ws_server.c -I./c_test -lpthread 7 | $(CC) -Wall -o server ./c_test/main_server.c ./c_test/ws_com.c ./c_test/ws_server.c -I./c_test -lpthread 8 | 9 | clean: 10 | rm -rf client server 11 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | 2 | # 客户端数量 3 | # 不要超过 main_server.c 中 CLIENT_MAX 定义的数量 4 | cNum=100 5 | 6 | echo "" 7 | echo ">>>>>>>>>> 使用 ./kill.sh 关闭测试进程 <<<<<<<<<<" 8 | echo ">>>>>>>>>> 测试在 25 秒后自动结束 <<<<<<<<<<" 9 | echo ">>>>>>>>>> (避免混乱已关闭client的打印) <<<<<<<<<<" 10 | echo ">>>>>>>>>> 客户端数量 $cNum <<<<<<<<<<" 11 | echo "" 12 | 13 | sleep 3 14 | ./server & 15 | sleep 1 16 | 17 | while [ $cNum -gt 0 ] ; do 18 | cNum=`expr $cNum - 1` 19 | # 避免混乱,不看客户端打印 20 | ./client > /dev/null & 21 | done 22 | 23 | sleep 20 24 | 25 | # 关闭进程 26 | killall client 27 | sleep 5 28 | killall server 29 | 30 | echo "" 31 | echo ">>>>>>>>>> 测试结束 <<<<<<<<<<" 32 | echo "" 33 | -------------------------------------------------------------------------------- /python3_test/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding:utf-8 -*- 3 | 4 | # 安装: pip3 install websocket-server 5 | # 参考: https://blog.csdn.net/weixin_37989267/article/details/87928934 6 | from websocket_server import WebsocketServer 7 | 8 | def new_client(client, server): 9 | print('new_client', client['id']) 10 | server.send_message_to_all('Say hi~ I am server') 11 | 12 | def client_left(client, server): 13 | print('client_left', client['id']) 14 | 15 | # 自动回复ping包的逻辑有待添加... 16 | def message_received(client, server, message): 17 | print('message_received', client['id'], message) 18 | server.send_message_to_all(message) 19 | 20 | if __name__ == '__main__': 21 | 22 | server = WebsocketServer(9999, "127.0.0.1") 23 | server.set_fn_new_client(new_client) 24 | server.set_fn_client_left(client_left) 25 | server.set_fn_message_received(message_received) 26 | server.run_forever() 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 编译 2 | 3 | * make 生成执行程序 server 和 client, ip为本机默认ip, 端口999 4 | 5 | # 测试 6 | 7 | * 方法一: 先 ./server & 把服务器抛后台, 再运行客户端 ./client 8 | 9 | * 方法二: 直接运行测试脚本 ./start.sh &, 想提前停止测试则运行 ./kill.sh 10 | 11 | * 方法三: 先 ./server & 把服务器抛后台, 再找个网页的在线websocket输入ip(网口IP,不要用127.0.0.1)和端口测试 12 | 13 | # 注意 14 | 15 | * 1.在虚拟机里架服务器的话, 最好用桥接的方式获得ip, net方式可能不通; 16 | 17 | * 2.服务器示例代码未作过高并发压力测试, 仅供开发参考; 18 | 19 | * 3.关于服务端bind超时, 通常是端口被占用或服务器关闭时有客户端未断开造成, 后者会在1分钟后恢复正常. 20 | 21 | # 其它 22 | 23 | * websocket协议介绍: https://blog.csdn.net/SGuniver_22/article/details/74273839 24 | 25 | * 欢迎提出bug、服务器和客户端优化建议、使用过程遇到的问题等等 26 | 27 | * 有github帐号的可以左上角issues, 或者上面csdn文章评论区提问. 28 | 29 | # 限制服务器接入量的因素 30 | 31 | * 配置因素: 32 | * 1.文件描述符上限, 使用指令“ulimit -a”在"open files"项可见, 一般为1024, 可尝试用指令"ulimit -n 4096"提高; 或者直接在文件"/etc/security/limits.conf"添加行"* soft nofile 4096"后重启进行永久配置; 最后注意不要超过"/proc/sys/fs/file-max"中的数量; 33 | 34 | * 2.客户端测试设备端口限制, 客户端设备在发起tcp连接时会占用本地空闲端口, 除去特殊端口0, 理论能用1~65535 (这条有争议); 35 | 36 | * 3."/proc/sys/fs/epoll/max_user_watches"中限制了epoll可监听最大句柄数量, 可以用sysctl指令进行修改; 37 | 38 | * 4.内存限制。我们知道多线程可以提高并发,然而线程相当于一个长时间调用不反回的函数,会持续占用一段"栈"内存(也就是函数里局部变量的内存空间);如果按"栈"内存预留为5M算,那么一台8G内存的电脑撑死也就能开 8*1024/5=1638 个线程,当然实际还远达不到这个数。所以想通过线程提高接入量,需先确认下设备的内存情况。 39 | -------------------------------------------------------------------------------- /c_test/ws_com.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _WS_COM_H_ 3 | #define _WS_COM_H_ 4 | 5 | #ifdef __cplusplus 6 | extern "C" 7 | { 8 | #endif 9 | 10 | #include //引入 bool 类型 11 | #include //引入 int8_t uint8_t int32_t uint32_t 等 12 | 13 | // #define WS_DEBUG //开启debug打印 14 | 15 | // websocket根据data[0]判别数据包类型 16 | // 比如0x81 = 0x80 | 0x1 为一个txt类型数据包 17 | typedef enum 18 | { 19 | WDT_NULL = 0, // 非标准数据包 20 | WDT_MINDATA, // 0x0:中间数据包 21 | WDT_TXTDATA, // 0x1:txt类型数据包 22 | WDT_BINDATA, // 0x2:bin类型数据包 23 | WDT_DISCONN, // 0x8:断开连接类型数据包 收到后需手动 close(fd) 24 | WDT_PING, // 0x8:ping类型数据包 ws_recv 函数内自动回复pong 25 | WDT_PONG, // 0xA:pong类型数据包 26 | } Ws_DataType; 27 | 28 | int32_t ws_requestServer(char* ip, int32_t port, char* path, int32_t timeoutMs); 29 | int32_t ws_replyClient(int32_t fd, char* buff, int32_t buffLen, char* path); 30 | 31 | int32_t ws_send(int32_t fd, void* buff, int32_t buffLen, bool mask, Ws_DataType type); 32 | int32_t ws_recv(int32_t fd, void* buff, int32_t buffSize, Ws_DataType *retType); 33 | 34 | //返回时间戳,格式如"20:45:30" 35 | char* ws_time(void); 36 | 37 | //域名转IP工具,成功返回大于0请求时长ms,失败返回负值的请求时长ms 38 | int ws_getIpByHostName(const char* hostName, char* retIp, int timeoutMs); 39 | 40 | //延时工具 41 | void ws_delayus(uint32_t us); 42 | void ws_delayms(uint32_t ms); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | 48 | #endif // _WS_COM_H_ 49 | -------------------------------------------------------------------------------- /python3_test/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding:utf-8 -*- 3 | 4 | # 安装: pip3 install websocket-client 5 | # 注意: 不推荐用pyhon2版本,运行时会报'SSLSocket'错误 6 | # 参考: https://blog.csdn.net/weixin_37989267/article/details/87928934 7 | import websocket 8 | import threading 9 | import time 10 | 11 | def on_message(ws, message): 12 | print('on_message', message) 13 | 14 | def on_error(ws, error): 15 | global isOpen 16 | print('on_error', error) 17 | isOpen = False 18 | 19 | def on_close(ws): 20 | global isOpen 21 | print('on_close') 22 | isOpen = False 23 | 24 | def on_open(ws): 25 | global isOpen 26 | print('on_open') 27 | isOpen = True 28 | 29 | def ws_loop(ws): 30 | # 注意该'run_forever()'函数是阻塞的 31 | ws.run_forever() 32 | # 定时ping服务器 33 | # ws.run_forever(ping_interval = 10, ping_timeout = 5) 34 | 35 | if __name__ == "__main__": 36 | # main函数里定义的变量默认为全局变量 37 | isOpen = False 38 | # 打印后台信息 39 | websocket.enableTrace(False) 40 | # 装载接口 41 | ws = websocket.WebSocketApp( 42 | "ws://127.0.0.1:9999/", 43 | on_message=on_message, 44 | on_error=on_error, 45 | on_close=on_close, 46 | on_open=on_open) 47 | # 开线程维护连接(避免阻塞在这里) 48 | threading.Thread(target=ws_loop, args=(ws,)).start() 49 | # 周期发送心跳 50 | while True: 51 | time.sleep(3) 52 | if isOpen: 53 | try: 54 | ws.send( 55 | 'Heart from client ' + 56 | time.strftime("%H:%M:%S", time.localtime())) 57 | except: 58 | print('ws.send error !!') 59 | isOpen = false 60 | -------------------------------------------------------------------------------- /c_test/ws_server.h: -------------------------------------------------------------------------------- 1 | #ifndef _WS_SERVER_H_ 2 | #define _WS_SERVER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" 6 | { 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include "ws_com.h" 13 | 14 | //收包缓冲区大小,应确保能够一次接收完一整包数据,按需调整 15 | //每接入一个客户端将开辟一块独立缓冲区空间 16 | #define WS_SERVER_PKG (1024 * 100 + 16) 17 | 18 | //最大副线程数量(不包括负责accept的主线程) 19 | //计算机线程数量有限,建议限制在300以内(或上网搜索"linux最大线程数量"进一步了解) 20 | #define WS_SERVER_THREAD 10 21 | 22 | //每个副线程维护客户端最大数量 23 | #define WS_SERVER_CLIENT_OF_THREAD 500 24 | 25 | /* 26 | * 接入客户端最大数量 27 | * 28 | * 限制接入量的"性能因素": 29 | * 1.普通计算机接入破千之后CPU会逐渐拉满,很难再接入/发起更多客户端 30 | * 2.服务端表现为接入量上涨变缓 31 | * 3.客户端表现为connect阶段超时、登录阶段超时等 32 | * 33 | * 限制接入量的"参数因素": 34 | * 1.线程数量上限,上面宏定义 WS_SERVER_THREAD 中有提到 35 | * 2.文件描述符上限,使用指令“ulimit -a”在"open files"项可见,一般为4096,可尝试用指令"ulimit -n 8192"提高 36 | */ 37 | #define WS_SERVER_CLIENT (WS_SERVER_THREAD * WS_SERVER_CLIENT_OF_THREAD) // 10*500=5000 38 | 39 | //bind超时,通常为服务器端口被占用,或者有客户端还连着上次的服务器 40 | #define WS_SERVER_BIND_TIMEOUT_MS 1000 41 | 42 | //连接后又不进行websocket握手,5秒超时踢出 43 | #define WS_SERVER_LOGIN_TIMEOUT_MS 5000 44 | 45 | //接入客户端数量超出这个数时不在 onMessage 打印,避免卡顿 46 | #define WS_SERVER_CLIENT_OF_PRINTF 500 47 | 48 | //断连原因 49 | typedef enum { 50 | WET_NONE = 0, 51 | WET_EPOLL, //epoll检测 52 | WET_SEND, //发送失败 53 | WET_LOGIN, //websocket握手检查失败(http请求格式错误或者path值不一致) 54 | WET_LOGIN_TIMEOUT, //连接后迟迟不发起websocket握手 55 | WET_DISCONNECT, //收到断开协议包 56 | } Ws_ExitType; 57 | 58 | //先声明结构体,后面可以互相嵌套使用 59 | typedef struct WsClient Ws_Client; 60 | typedef struct WsThread Ws_Thread; 61 | typedef struct WsServer Ws_Server; 62 | 63 | //客户端事件回调函数原型 64 | typedef void (*WsOnLogin)(Ws_Client *wsc); 65 | typedef void (*WsOnMessage)(Ws_Client *wsc, char *msg, int msgLen, Ws_DataType dataType); 66 | typedef void (*WsOnExit)(Ws_Client *wsc, Ws_ExitType exitType); 67 | 68 | //客户端使用的参数结构体 69 | struct WsClient 70 | { 71 | int fd; //accept之后得到的客户端连接描述符 72 | uint8_t ip[4]; //接入客户端的ip(accpet阶段获得) 73 | int port; //接入客户端的端口 74 | Ws_ExitType exitType; //断连标志 75 | bool isLogin; //是否完成websocket握手验证 76 | bool isExiting; //正在退出(防止反复del) 77 | uint32_t recvBytes; //总接收字节计数 78 | uint32_t index; //接入客户端的历史序号(从1数起) 79 | uint32_t loginTimeout; //等待websocket握手超时计数 80 | void *priv; //用户私有指针 81 | Ws_Server *wss; //所在服务器指针 82 | Ws_Thread *wst; //所在副线程指针 83 | }; 84 | 85 | //副线程结构体(只要还有一个客户端在维护就不会退出线程) 86 | struct WsThread 87 | { 88 | int fd_epoll; //epoll描述符 89 | int clientCount; //该线程正在维护的客户端数量 90 | bool isRun; //线程运行状况 91 | Ws_Server *wss; 92 | }; 93 | 94 | //服务器主线程使用的参数结构体 95 | struct WsServer 96 | { 97 | int fd; //服务器描述符 98 | int fd_epoll; //epoll描述符 99 | int port; //服务器端口 100 | char path[256]; //服务器路径 101 | void *priv; //用户私有指针 102 | int clientCount; //当前接入客户端总数 103 | bool isExit; //线程结束标志 104 | pthread_mutex_t lock; 105 | WsOnLogin onLogin; 106 | WsOnMessage onMessage; 107 | WsOnExit onExit; 108 | Ws_Thread thread[WS_SERVER_THREAD]; //副线程数组 109 | Ws_Client client[WS_SERVER_CLIENT]; //全体客户端列表 110 | }; 111 | 112 | //服务器创建和回收 113 | Ws_Server* ws_server_create( 114 | int port, //服务器端口 115 | const char *path, //服务器路径 116 | void *priv, //用户私有指针,回调函数里使用 wsc->priv 取回 117 | WsOnLogin onLogin, //客户端接入时(已连上),你要做什么? 118 | WsOnMessage onMessage, //收到客户端数据时,你要做什么? 119 | WsOnExit onExit); //客户端断开时(已断开),你要做什么? 120 | 121 | void ws_server_release(Ws_Server **wss); 122 | 123 | #ifdef __cplusplus 124 | } 125 | #endif 126 | 127 | #endif // _WS_SERVER_H_ 128 | -------------------------------------------------------------------------------- /c_test/main_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ws_server.h" 5 | 6 | /* 7 | * 接收数据回调 8 | * 参数: 9 | * wsc: 客户端信息结构体指针 10 | * msg: 接收数据内容 11 | * msgLen: >0时为websocket数据包,<0时为非包数据,没有=0的情况 12 | * type: websocket包类型 13 | */ 14 | void onMessage(Ws_Client *wsc, char *msg, int msgLen, Ws_DataType type) 15 | { 16 | int ret = 0; 17 | //正常 websocket 数据包 18 | if (msgLen > 0) 19 | { 20 | //客户端过多时不再打印,避免卡顿 21 | if (wsc->wss->clientCount <= WS_SERVER_CLIENT_OF_PRINTF) 22 | printf("onMessage: fd/%03d index/%03d total/%03d %d/%dbytes %s\r\n", 23 | wsc->fd, wsc->index, wsc->wss->clientCount, msgLen, wsc->recvBytes, msgLen < 128 ? msg : " "); 24 | 25 | //在这里根据客户端的请求内容, 提供相应的回复 26 | if (strstr(msg, "Say hi~") != NULL) 27 | ; 28 | //回显,收到什么回复什么 29 | else 30 | ret = ws_send(wsc->fd, msg, msgLen, false, type); 31 | //发送失败,标记异常(后续会被自动回收) 32 | if (ret < 0) 33 | wsc->exitType = WET_SEND; 34 | } 35 | //非 websocket 数据包 36 | else if (msgLen < 0) 37 | { 38 | msgLen = -msgLen; 39 | printf("onMessage: fd/%03d index/%03d total/%03d %d/%dbytes bad pkg %s\r\n", 40 | wsc->fd, wsc->index, wsc->wss->clientCount, msgLen, wsc->recvBytes, msgLen < 128 ? msg : " "); 41 | } 42 | //特殊包(不需作任何处理,知道就行) 43 | else 44 | { 45 | if (type == WDT_PING) 46 | printf("onMessage: fd/%03d index/%03d total/%03d pkg WDT_PING \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 47 | else if (type == WDT_PONG) 48 | printf("onMessage: fd/%03d index/%03d total/%03d pkg WDT_PONG \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 49 | else if (type == WDT_DISCONN) 50 | printf("onMessage: fd/%03d index/%03d total/%03d pkg WDT_DISCONN \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 51 | } 52 | } 53 | 54 | //客户端接入时(已连上),你要做什么? 55 | void onLogin(Ws_Client *wsc) 56 | { 57 | printf("onLogin: fd/%03d index/%03d total/%03d addr/%d.%d.%d.%d:%d\r\n", 58 | wsc->fd, wsc->index, wsc->wss->clientCount, 59 | wsc->ip[0], wsc->ip[1], wsc->ip[2], wsc->ip[3], wsc->port); 60 | //打招呼 61 | ws_send(wsc->fd, (char*)"Say hi~ I am server", 19, false, WDT_TXTDATA); 62 | } 63 | 64 | //客户端断开时(已断开),你要做什么? 65 | void onExit(Ws_Client *wsc, Ws_ExitType exitType) 66 | { 67 | //断开原因 68 | switch (exitType) 69 | { 70 | case WET_EPOLL: 71 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by epoll\r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 72 | break; 73 | case WET_SEND: 74 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by send\r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 75 | break; 76 | case WET_LOGIN: 77 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by login failed \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 78 | break; 79 | case WET_LOGIN_TIMEOUT: 80 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by login timeout \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 81 | break; 82 | case WET_DISCONNECT: 83 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by disconnect \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 84 | break; 85 | default: 86 | printf("onExit: fd/%03d index/%03d total/%03d disconnect by unknow \r\n", wsc->fd, wsc->index, wsc->wss->clientCount); 87 | } 88 | } 89 | 90 | /* 91 | * usage: ./server port path 92 | */ 93 | int main(int argc, char **argv) 94 | { 95 | int i; 96 | char buff[1024]; 97 | 98 | //服务器必须参数 99 | Ws_Server *wss = ws_server_create( 100 | argc > 1 ? atoi(argv[1]) : 9999, //服务器端口 101 | argc > 2 ? argv[2] : "/", //服务器路径(这样写表示路径为空) 102 | NULL, //指向自己的数据的指针,回调函数里使用 wsc->priv 取回 103 | &onLogin, //客户端接入时(已连上),你要做什么? 104 | &onMessage, //收到客户端数据时,你要做什么? 105 | &onExit); //客户端断开时(已断开),你要做什么? 106 | 107 | //服务器启动至少先等3秒(有时会bind超时) 108 | while (!wss->isExit) 109 | { 110 | ws_delayms(3000); 111 | //每3秒推送信息给所有客户端 112 | for (i = 0; i < WS_SERVER_CLIENT; i++) 113 | { 114 | if (wss->client[i].fd && wss->client[i].isLogin && !wss->client[i].exitType) 115 | { 116 | snprintf(buff, sizeof(buff), "Tips from server fd/%03d index/%03d total/%03d %s", 117 | wss->client[i].fd, wss->client[i].index, wss->clientCount, ws_time()); 118 | 119 | // if (ws_send(wss->client[i].fd, buff, sizeof(buff), false, WDT_TXTDATA) < 0) //大数据量压力测试 120 | if (ws_send(wss->client[i].fd, buff, strlen(buff), false, WDT_TXTDATA) < 0) 121 | { 122 | //发送失败,标记异常 123 | wss->client[i].exitType = WET_SEND; 124 | } 125 | } 126 | } 127 | } 128 | 129 | ws_server_release(&wss); 130 | printf("server exit \r\n"); 131 | return 0; 132 | } 133 | -------------------------------------------------------------------------------- /c_test/main_client.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include //exit() 4 | #include 5 | #include 6 | #include //getpid 7 | 8 | #include "ws_com.h" 9 | 10 | //发包数据量 10K 11 | #define SEND_PKG_MAX (1024 * 10) 12 | 13 | //收包缓冲区大小 10K+ 14 | #define RECV_PKG_MAX (SEND_PKG_MAX + 16) 15 | 16 | #define SERVER_IP "127.0.0.1" 17 | #define SERVER_PORT 9999 18 | #define SERVER_PATH "/" 19 | 20 | #if 1 // 发收包测试 21 | 22 | /* 23 | * usage: ./client ip port path 24 | */ 25 | int main(int argc, char **argv) 26 | { 27 | int fd, pid; 28 | int ret; 29 | int heart = 0; 30 | Ws_DataType retPkgType; 31 | 32 | char recv_buff[RECV_PKG_MAX]; 33 | char send_buff[SEND_PKG_MAX]; 34 | 35 | int port = SERVER_PORT; 36 | char ip[32] = SERVER_IP; 37 | char path[64] = SERVER_PATH; 38 | 39 | //传参接收 40 | if (argc > 1) { 41 | memset(ip, 0, sizeof(ip)); 42 | strcpy(ip, argv[1]); 43 | } 44 | if (argc > 2) { 45 | sscanf(argv[2], "%d", &port); 46 | } 47 | if (argc > 3) { 48 | memset(path, 0, sizeof(path)); 49 | strcpy(path, argv[3]); 50 | } 51 | 52 | //用本进程pid作为唯一标识 53 | pid = getpid(); 54 | printf("client ws://%s:%d%s pid/%d\r\n", ip, port, path, pid); 55 | 56 | //3秒超时连接服务器 57 | //同时大量接入时,服务器不能及时响应,可以加大超时时间 58 | if ((fd = ws_requestServer(ip, port, path, 3000)) <= 0) 59 | { 60 | printf("connect failed !!\r\n"); 61 | return -1; 62 | } 63 | 64 | //循环接收服务器下发 65 | while (1) 66 | { 67 | //接收数据 68 | ret = ws_recv(fd, recv_buff, sizeof(recv_buff), &retPkgType); 69 | //正常包 70 | if (ret > 0) 71 | { 72 | printf("client(%d): recv len/%d %s\r\n", pid, ret, recv_buff); 73 | 74 | //根据服务器下发内容做出反应 75 | if (strstr(recv_buff, "Say hi~") != NULL) 76 | { 77 | snprintf(send_buff, sizeof(send_buff), "Say hi~ I am client(%d)", pid); 78 | ret = ws_send(fd, send_buff, strlen(send_buff), true, WDT_TXTDATA); 79 | } 80 | else if (strstr(recv_buff, "I am ") != NULL) 81 | ; 82 | 83 | //send返回异常, 连接已断开 84 | if (ret <= 0) 85 | { 86 | printf("client(%d): send failed %d, disconnect now ...\r\n", pid, ret); 87 | break; 88 | } 89 | } 90 | //非包数据 91 | else if (ret < 0) 92 | printf("client(%d): recv len/%d bad pkg %s\r\n", pid, -ret, recv_buff); 93 | //收到特殊包 94 | else if (retPkgType == WDT_DISCONN) 95 | { 96 | printf("client(%d): recv WDT_DISCONN \r\n", pid); 97 | break; 98 | } 99 | else if (retPkgType == WDT_PING) 100 | printf("client(%d): recv WDT_PING \r\n", pid); 101 | else if (retPkgType == WDT_PONG) 102 | printf("client(%d): recv WDT_PONG \r\n", pid); 103 | 104 | //一个合格的客户端,应该定时给服务器发心跳,以检测连接状态 105 | heart += 10; 106 | if (heart > 3000) 107 | { 108 | heart = 0; 109 | 110 | //发送心跳 111 | #if 1 112 | //用普通数据 113 | snprintf(send_buff, sizeof(send_buff), "Heart from client(%d) %s", pid, ws_time()); 114 | // ret = ws_send(fd, send_buff, sizeof(send_buff), true, WDT_TXTDATA); //大数据量压力测试 115 | ret = ws_send(fd, send_buff, strlen(send_buff), true, WDT_TXTDATA); 116 | #else 117 | //用ping包代替心跳 118 | ret = ws_send(fd, NULL, 0, true, WDT_PING); 119 | #endif 120 | //send返回异常, 连接已断开 121 | if (ret <= 0) 122 | { 123 | printf("client(%d): send failed %d, disconnect now ...\r\n", pid, ret); 124 | break; 125 | } 126 | } 127 | 128 | ws_delayms(10); 129 | } 130 | 131 | close(fd); 132 | printf("client(%d): close\r\n", pid); 133 | return 0; 134 | } 135 | 136 | #else // 利用服务器回显,发收文件测试 137 | 138 | #include 139 | #include 140 | #include 141 | 142 | //指定要读取的文件 143 | #define FILE_R "./in.bin" 144 | //指定要写入的文件 145 | #define FILE_W "./out.bin" 146 | 147 | /* 148 | * usage: ./client ip port path 149 | */ 150 | int main(int argc, char **argv) 151 | { 152 | int ret, recvTotal = 0, sendTotal = 0, timeout = 0; 153 | Ws_DataType type; 154 | char recv_buff[RECV_PKG_MAX]; 155 | char send_buff[SEND_PKG_MAX]; 156 | int fd, fr, fw; 157 | char frOver = 0, fwOver = 0; 158 | 159 | int port = SERVER_PORT; 160 | char ip[32] = SERVER_IP; 161 | char path[64] = SERVER_PATH; 162 | 163 | //传参接收 164 | if (argc > 1) { 165 | memset(ip, 0, sizeof(ip)); 166 | strcpy(ip, argv[1]); 167 | } 168 | if (argc > 2) { 169 | sscanf(argv[2], "%d", &port); 170 | } 171 | if (argc > 3) { 172 | memset(path, 0, sizeof(path)); 173 | strcpy(path, argv[3]); 174 | } 175 | 176 | fd = ws_requestServer(ip, port, path, 2000); 177 | if (fd < 1) 178 | { 179 | printf("connect failed !!\r\n"); 180 | return 1; 181 | } 182 | 183 | fr = open(FILE_R, O_RDONLY); 184 | if (fr < 1) 185 | { 186 | printf("open %s failed\r\n", FILE_R); 187 | printf("you can create file by shell 'dd if=/dev/zero of=./in.bin bs=1M count=10'\r\n"); 188 | goto exit_ws; 189 | } 190 | 191 | fw = open(FILE_W, O_RDWR | O_CREAT | O_TRUNC, 0666); 192 | if (fw < 1) 193 | { 194 | printf("open %s failed \r\n", FILE_W); 195 | goto exit_fr; 196 | } 197 | 198 | while (!frOver || !fwOver) 199 | { 200 | //读文件,发数据 201 | if (!frOver) 202 | { 203 | ret = read(fr, send_buff, sizeof(send_buff)); 204 | if (ret > 0) 205 | { 206 | timeout = 0; 207 | sendTotal += ret; 208 | ret = ws_send(fd, send_buff, ret, true, WDT_BINDATA); 209 | } 210 | else 211 | frOver = 1; 212 | } 213 | //收数据 214 | do 215 | { 216 | ret = ws_recv(fd, recv_buff, sizeof(recv_buff), &type); 217 | // type == WDT_BINDATA 暂且区分服务器服务器下发的其它推送内容,避免和文件数据混淆 218 | if (ret > 0 && type == WDT_BINDATA) 219 | { 220 | timeout = 0; 221 | recvTotal += ret; 222 | printf("recv/%d total/%d send/%d bytes\r\n", ret, recvTotal, sendTotal); 223 | write(fw, recv_buff, ret); 224 | } 225 | else if (ret < 0) 226 | { 227 | timeout = 0; 228 | recvTotal += -ret; 229 | printf("recv/%d total/%d send/%d bytes bad pkg\r\n", ret, recvTotal, sendTotal); 230 | } 231 | else if (frOver) 232 | { 233 | timeout += 100; 234 | //200ms超时 235 | if (timeout > 200000 || recvTotal >= sendTotal) 236 | { 237 | fwOver = 1; 238 | //主动断连 239 | ws_send(fd, NULL, 0, false, WDT_DISCONN); 240 | } 241 | break; 242 | } 243 | else 244 | timeout = 0; 245 | } while (ret != 0); 246 | 247 | ws_delayus(100); 248 | } 249 | 250 | close(fw); 251 | exit_fr: 252 | close(fr); 253 | exit_ws: 254 | close(fd); 255 | 256 | return 0; 257 | } 258 | 259 | #endif -------------------------------------------------------------------------------- /c_test/ws_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include //非阻塞宏 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "ws_server.h" 16 | 17 | #define WSS_INFO(...) fprintf(stdout, "[WSS_INFO] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stdout, __VA_ARGS__) 18 | #define WSS_ERR(...) fprintf(stderr, "[WSS_ERR] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stderr, __VA_ARGS__) 19 | 20 | //服务器副线程,负责检测 数据接收 和 客户端断开 21 | static void* server_thread2(void *argv); 22 | 23 | //抛线程工具 24 | static void new_thread(void *obj, void* (*callback)(void*)) 25 | { 26 | pthread_t th; 27 | pthread_attr_t attr; 28 | int ret; 29 | //禁用线程同步,线程运行结束后自动释放 30 | pthread_attr_init(&attr); 31 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 32 | //抛出线程 33 | ret = pthread_create(&th, &attr, callback, (void *)obj); 34 | if (ret != 0) 35 | WSS_ERR("pthread_create failed !! %s\r\n", strerror(ret)); 36 | //attr destroy 37 | pthread_attr_destroy(&attr); 38 | } 39 | 40 | //注意在"epoll_wait时"或"目标fd已close()"的情况下会ctrl会失败 41 | static void _epoll_ctrl(int fd_epoll, int fd, uint32_t event, int ctrl, void *ptr) 42 | { 43 | struct epoll_event ev; 44 | ev.events = event; 45 | if (ptr) 46 | ev.data.ptr = ptr; 47 | else 48 | ev.data.fd = fd; 49 | if (epoll_ctl(fd_epoll, ctrl, fd, &ev) != 0) 50 | WSS_ERR("epoll ctrl %d error !!\r\n", ctrl); 51 | } 52 | 53 | /* 54 | * 接收数据,自动连接客户端 55 | * 返回: 56 | * >0 有数据 57 | * =0 无数据 58 | * -1 连接异常 59 | */ 60 | static int client_recv(Ws_Client *wsc) 61 | { 62 | int ret = 0; 63 | Ws_DataType retPkgType = WDT_NULL; 64 | char *buff = (char*)calloc(WS_SERVER_PKG, 1); 65 | 66 | if (!buff) 67 | { 68 | WSS_ERR("calloc %dbytes failed\r\n", WS_SERVER_PKG); 69 | return -1; 70 | } 71 | 72 | do 73 | { 74 | ret = ws_recv(wsc->fd, buff, WS_SERVER_PKG, &retPkgType); 75 | //这可能是一包客户端请求 76 | if (!wsc->isLogin && 77 | ret < 0 && 78 | strncmp(buff, "GET", 3) == 0 && 79 | strstr(buff, "Sec-WebSocket-Key")) 80 | { 81 | //构建回复 82 | if (ws_replyClient(wsc->fd, buff, -ret, wsc->wss->path) > 0) 83 | { 84 | //这个延时很有必要,否则下面onLogin里面发东西客户端可能收不到 85 | ws_delayms(5); 86 | wsc->isLogin = true; 87 | //回调 88 | if (wsc->wss->onLogin) 89 | wsc->wss->onLogin(wsc); 90 | ret = 0; 91 | break; 92 | } 93 | //websocket握手失败,标记断开类型 94 | else 95 | { 96 | wsc->exitType = WET_LOGIN; 97 | ret = -1; 98 | break; 99 | } 100 | } 101 | //接收数据量统计 102 | if (ret != 0) 103 | wsc->recvBytes += ret > 0 ? ret : (-ret); 104 | //消息回调 105 | if (wsc->wss->onMessage) 106 | wsc->wss->onMessage(wsc, buff, ret, retPkgType); 107 | //断连协议,标记断开类型 108 | if (retPkgType == WDT_DISCONN) 109 | { 110 | wsc->exitType = WET_DISCONNECT; 111 | ret = -1; 112 | break; 113 | } 114 | //有效的一次数据接收返回1 115 | if (ret != 0) 116 | ret = 1; 117 | } while (0); 118 | free(buff); 119 | //正常返回 120 | return ret; 121 | } 122 | 123 | //onMessage异步回调 124 | // static void client_onMessage(void *argv) 125 | // { 126 | // Ws_Client *wsc = (Ws_Client *)argv; 127 | // int ret = 1; 128 | // //收完为止 129 | // while (ret > 0) 130 | // ret = client_recv(wsc); 131 | // } 132 | 133 | //onExit异步回调 134 | static void* client_onExit(void *argv) 135 | { 136 | Ws_Client *wsc = (Ws_Client *)argv; 137 | if (wsc->wss->onExit) 138 | wsc->wss->onExit(wsc, wsc->exitType); 139 | //重置结构体,给下次使用 140 | memset(wsc, 0, sizeof(Ws_Client)); 141 | return NULL; 142 | } 143 | 144 | //取得空闲的坑,返回序号 145 | static int client_get(Ws_Server *wss, int fd, uint32_t ip, int port) 146 | { 147 | int i; 148 | for (i = 0; i < WS_SERVER_CLIENT; i++) 149 | { 150 | if (!wss->client[i].fd && 151 | !wss->client[i].isExiting && 152 | !wss->client[i].wst) 153 | { 154 | memset(&wss->client[i], 0, sizeof(Ws_Client)); 155 | wss->client[i].fd = fd; 156 | *((uint32_t*)(wss->client[i].ip)) = ip; 157 | wss->client[i].port = port; 158 | wss->client[i].wss = wss; 159 | wss->client[i].priv = wss->priv; 160 | wss->client[i].index = ++wss->clientCount; 161 | return i; 162 | } 163 | } 164 | WSS_ERR("failed, out of range(%d) !!\r\n", WS_SERVER_CLIENT); //满员 165 | return -1; 166 | } 167 | 168 | //共用代码块,完成客户端加人、客户端结构初始化、注册epoll监听 169 | #define COMMON_CODE() \ 170 | wsc->wst = wst;\ 171 | wst->clientCount += 1;\ 172 | _epoll_ctrl(wst->fd_epoll, wsc->fd, EPOLLIN, EPOLL_CTL_ADD, wsc); 173 | 174 | //添加客户端 175 | static void client_add(Ws_Server *wss, int fd, uint32_t ip, int port) 176 | { 177 | int ret; 178 | Ws_Client *wsc; 179 | Ws_Thread *wst; 180 | 181 | pthread_mutex_lock(&wss->lock); 182 | 183 | //取得空闲客户端指针 184 | ret = client_get(wss, fd, ip, port); 185 | if (ret < 0) 186 | { 187 | pthread_mutex_unlock(&wss->lock); 188 | return; 189 | } 190 | 191 | //新增客户端及其匹配的线程 192 | wsc = &wss->client[ret]; 193 | wst = &wss->thread[ret / WS_SERVER_CLIENT_OF_THREAD]; 194 | 195 | //线程已开启 196 | if (wst->isRun && //线程在运行 197 | wst->fd_epoll) //线程epoll正常 198 | { 199 | //共用代码块 200 | COMMON_CODE(); 201 | } 202 | //开启新线程 203 | else 204 | { 205 | //参数初始化 206 | wst->wss = wss; 207 | wst->fd_epoll = epoll_create(WS_SERVER_CLIENT_OF_THREAD); 208 | //开线程 209 | new_thread(wst, &server_thread2); 210 | //共用代码块 211 | COMMON_CODE(); 212 | } 213 | pthread_mutex_unlock(&wss->lock); 214 | } 215 | 216 | //移除特定客户端 217 | static void client_del(Ws_Thread *wst, Ws_Client *wsc) 218 | { 219 | if (wsc->isExiting) 220 | return; 221 | //标记,防止反复del 222 | wsc->isExiting = true; 223 | //从epoll监听列表中移除 224 | _epoll_ctrl(wst->fd_epoll, wsc->fd, 0, EPOLL_CTL_DEL, wsc); 225 | //关闭描述符 226 | close(wsc->fd); 227 | //如有需则断连回调 228 | new_thread(wsc, &client_onExit); 229 | //减人 230 | wst->clientCount -= 1; 231 | wst->wss->clientCount -= 1; 232 | } 233 | 234 | //副线程检测异常客户端并移除 235 | static void client_detect(Ws_Thread *wst, bool delAll) 236 | { 237 | Ws_Client *client = wst->wss->client; 238 | int i; 239 | for (i = 0; i < WS_SERVER_CLIENT; i++) 240 | { 241 | if (client[i].wst == wst && //客户端属于该线程管辖 242 | client[i].fd && //这是有效连接 243 | !client[i].isExiting) //不是正在退出状态 244 | { 245 | //有异常错误 || 就是要删除 246 | if(client[i].exitType || delAll) 247 | client_del(wst, &client[i]); 248 | //非登录状态,进行websocket握手超时计数 249 | else if (!client[i].isLogin) 250 | { 251 | //5秒超时(延时不准,只是大概) 252 | client[i].loginTimeout += 500; 253 | if (client[i].loginTimeout > WS_SERVER_LOGIN_TIMEOUT_MS) 254 | { 255 | client[i].exitType = WET_LOGIN_TIMEOUT; 256 | client_del(wst, &client[i]); 257 | } 258 | } 259 | } 260 | } 261 | } 262 | 263 | //服务器副线程,负责检测 数据接收 和 客户端断开 264 | //只要还有一个客户端在维护就不会退出线程 265 | static void* server_thread2(void *argv) 266 | { 267 | Ws_Thread *wst = (Ws_Thread *)argv; 268 | int nfds, count; 269 | struct epoll_event events[WS_SERVER_CLIENT_OF_THREAD]; 270 | 271 | while (!wst->wss->isExit)// && wst->clientCount > 0) 272 | { 273 | wst->isRun = true; 274 | //等待事件发生,-1阻塞,0/非阻塞,其它数值为超时ms 275 | if ((nfds = epoll_wait(wst->fd_epoll, events, WS_SERVER_CLIENT_OF_THREAD, 500)) < 0) 276 | { 277 | WSS_ERR("epoll_wait failed\r\n"); 278 | break; 279 | } 280 | for (count = 0; count < nfds; count++) 281 | { 282 | //epoll错误 283 | if ((events[count].events & EPOLLERR) || (events[count].events & EPOLLHUP)) 284 | { 285 | //标记异常类型 286 | ((Ws_Client *)events[count].data.ptr)->exitType = WET_EPOLL; 287 | //移除 288 | client_del(wst, (Ws_Client *)events[count].data.ptr); 289 | } 290 | //接收数据事件 291 | else if (events[count].events & EPOLLIN) 292 | client_recv((Ws_Client *)events[count].data.ptr); 293 | } 294 | //异常客户端检查 295 | client_detect(wst, false); 296 | } 297 | wst->isRun = false; 298 | //关闭epoll描述符 299 | close(wst->fd_epoll); 300 | //关闭线程维护的所有客户端(正常情况应该都已经关闭了) 301 | client_detect(wst, true); 302 | //清空内存,下次使用 303 | memset(wst, 0, sizeof(Ws_Thread)); 304 | return NULL; 305 | } 306 | 307 | //服务器主线程,负责检测 新客户端接入 308 | static void* server_thread(void *argv) 309 | { 310 | Ws_Server *wss = (Ws_Server *)argv; 311 | int ret, count; 312 | int fd_accept; 313 | 314 | socklen_t socAddrLen; 315 | struct sockaddr_in acceptAddr; 316 | struct sockaddr_in serverAddr = {0}; 317 | 318 | int nfds; 319 | struct epoll_event events[WS_SERVER_CLIENT]; 320 | 321 | serverAddr.sin_family = AF_INET; //设置为IP通信 322 | serverAddr.sin_addr.s_addr = INADDR_ANY; //服务器IP地址 323 | serverAddr.sin_port = htons(wss->port); //服务器端口号 324 | socAddrLen = sizeof(struct sockaddr_in); 325 | 326 | //socket init 327 | if ((wss->fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0) 328 | { 329 | WSS_ERR("create socket failed\r\n"); 330 | return NULL; 331 | } 332 | 333 | //地址可重用设置(有效避免bind超时) 334 | setsockopt(wss->fd, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof(ret)); 335 | 336 | //设置为非阻塞接收 337 | ret = fcntl(wss->fd, F_GETFL, 0); 338 | fcntl(wss->fd, F_SETFL, ret | O_NONBLOCK); 339 | 340 | //bind 341 | count = 0; 342 | while (bind(wss->fd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr)) != 0) 343 | { 344 | if (++count > WS_SERVER_BIND_TIMEOUT_MS) 345 | { 346 | WSS_ERR("bind timeout %d 服务器端口占用中,请稍候再试\r\n", count); 347 | goto server_exit; 348 | } 349 | ws_delayms(1); 350 | } 351 | 352 | //listen 353 | if (listen(wss->fd, 0) != 0) 354 | { 355 | WSS_ERR("listen failed\r\n"); 356 | goto server_exit; 357 | } 358 | 359 | pthread_mutex_init(&wss->lock, NULL); 360 | 361 | //创建一个epoll描述符 362 | wss->fd_epoll = epoll_create(WS_SERVER_CLIENT); 363 | 364 | //向epoll注册server_sockfd监听事件 365 | _epoll_ctrl(wss->fd_epoll, wss->fd, EPOLLIN | EPOLLET, EPOLL_CTL_ADD, NULL); 366 | 367 | //正式开始 368 | WSS_INFO("server ws://127.0.0.1:%d%s start \r\n", wss->port, wss->path); 369 | 370 | while (!wss->isExit) 371 | { 372 | //等待事件发生,-1阻塞,0/非阻塞,其它数值为超时ms 373 | if ((nfds = epoll_wait(wss->fd_epoll, events, WS_SERVER_CLIENT, 500)) < 0) 374 | { 375 | WSS_ERR("epoll_wait failed\r\n"); 376 | break; 377 | } 378 | for (count = 0; count < nfds; count++) 379 | { 380 | //新通道接入事件 381 | if (events[count].data.fd == wss->fd) 382 | { 383 | fd_accept = accept(wss->fd, (struct sockaddr *)&acceptAddr, &socAddrLen); 384 | //添加客户端 385 | if (fd_accept >= 0) 386 | client_add(wss, fd_accept, acceptAddr.sin_addr.s_addr, acceptAddr.sin_port); 387 | } 388 | } 389 | } 390 | 391 | //移除所有副线程 392 | wss->isExit = true; 393 | //关闭epoll描述符 394 | close(wss->fd_epoll); 395 | wss->fd_epoll = 0; 396 | 397 | pthread_mutex_destroy(&wss->lock); 398 | 399 | server_exit: 400 | wss->isExit = true; 401 | //关闭socket 402 | close(wss->fd); 403 | wss->fd = 0; 404 | return NULL; 405 | } 406 | 407 | void ws_server_release(Ws_Server **wss) 408 | { 409 | if (wss) 410 | { 411 | if (*wss) 412 | { 413 | (*wss)->isExit = true; 414 | while ((*wss)->fd) 415 | ws_delayms(5); 416 | free(*wss); 417 | *wss = NULL; 418 | } 419 | } 420 | } 421 | 422 | Ws_Server* ws_server_create( 423 | int port, 424 | const char *path, 425 | void *priv, 426 | WsOnLogin onLogin, 427 | WsOnMessage onMessage, 428 | WsOnExit onExit) 429 | { 430 | Ws_Server *wss = (Ws_Server*)calloc(1, sizeof(Ws_Server)); 431 | wss->port = port; 432 | strcpy(wss->path, path ? path : "/"); 433 | wss->priv = priv; 434 | wss->onLogin = onLogin; 435 | wss->onMessage = onMessage; 436 | wss->onExit = onExit; 437 | new_thread(wss, &server_thread); 438 | return wss; 439 | } 440 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /c_test/ws_com.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include //使用 malloc, calloc等动态分配内存方法 6 | #include //获取系统时间 7 | #include 8 | #include 9 | #include //非阻塞 10 | #include 11 | #include //inet_addr() 12 | #include //close() 13 | #include //文件IO操作 14 | #include // 15 | #include 16 | #include 17 | #include 18 | #include //gethostbyname, gethostbyname2, gethostbyname_r, gethostbyname_r2 19 | #include 20 | #include 21 | #include 22 | #include //SIOCSIFADDR 23 | 24 | #include "ws_com.h" 25 | 26 | #define WS_INFO(...) fprintf(stdout, "[WS_INFO] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stdout, __VA_ARGS__) 27 | #define WS_ERR(...) fprintf(stderr, "[WS_ERR] %s(%d): ", __FUNCTION__, __LINE__),fprintf(stderr, __VA_ARGS__) 28 | 29 | static void WS_HEX(FILE* f, void* dat, uint32_t len) 30 | { 31 | uint8_t* p = (uint8_t*)dat; 32 | uint32_t i; 33 | for (i = 0; i < len; i++) 34 | fprintf(f, "%02X ", p[i]); 35 | fprintf(f, "\r\n"); 36 | } 37 | 38 | //稍微精准的延时 39 | #include 40 | void ws_delayus(uint32_t us) 41 | { 42 | struct timeval tim; 43 | tim.tv_sec = us / 1000000; 44 | tim.tv_usec = us % 1000000; 45 | select(0, NULL, NULL, NULL, &tim); 46 | } 47 | void ws_delayms(uint32_t ms) 48 | { 49 | ws_delayus(ms * 1000); 50 | } 51 | 52 | //返回时间戳字符串,格式 HH:MM:SS 53 | char* ws_time(void) 54 | { 55 | static char timeStr[9] = {0}; 56 | struct timeval tv = {0}; 57 | gettimeofday(&tv, NULL); 58 | snprintf(timeStr, sizeof(timeStr), "%02ld:%02ld:%02ld", 59 | (tv.tv_sec % 86400 / 3600 + 8) % 24, 60 | tv.tv_sec % 3600 / 60, 61 | tv.tv_sec % 60); 62 | return timeStr; 63 | } 64 | 65 | //==================== 域名转IP ==================== 66 | 67 | typedef struct 68 | { 69 | pthread_t thread_id; 70 | char ip[256]; 71 | bool result; 72 | bool actionEnd; 73 | } GetHostName_Struct; 74 | 75 | static void* ws_getHostThread(void* argv) 76 | { 77 | int32_t ret; 78 | //int32_t i; 79 | char buf[1024]; 80 | struct hostent host_body, *host = NULL; 81 | struct in_addr **addr_list; 82 | GetHostName_Struct *gs = (GetHostName_Struct *)argv; 83 | 84 | /* 此类方法不可重入! 即使关闭线程 85 | if((host = gethostbyname(gs->ip)) == NULL) 86 | //if((host = gethostbyname2(gs->ip, AF_INET)) == NULL) 87 | { 88 | gs->actionEnd = true; 89 | return NULL; 90 | }*/ 91 | if (gethostbyname_r(gs->ip, &host_body, buf, sizeof(buf), &host, &ret)) 92 | { 93 | gs->actionEnd = true; 94 | return NULL; 95 | } 96 | 97 | if (host == NULL) 98 | { 99 | gs->actionEnd = true; 100 | return NULL; 101 | } 102 | 103 | addr_list = (struct in_addr **)host->h_addr_list; 104 | // printf("ip name: %s\r\nip list: ", host->h_name); 105 | // for(i = 0; addr_list[i] != NULL; i++) 106 | // printf("%s, ", inet_ntoa(*addr_list[i])); 107 | // printf("\r\n"); 108 | 109 | //一个域名可用解析出多个ip,这里只用了第一个 110 | if (addr_list[0] == NULL) 111 | { 112 | gs->actionEnd = true; 113 | return NULL; 114 | } 115 | memset(gs->ip, 0, sizeof(gs->ip)); 116 | strcpy(gs->ip, (char*)(inet_ntoa(*addr_list[0]))); 117 | gs->result = true; 118 | gs->actionEnd = true; 119 | return NULL; 120 | } 121 | 122 | //域名转IP工具,成功返回大于0请求时长ms,失败返回负值的请求时长ms 123 | int32_t ws_getIpByHostName(const char* hostName, char* retIp, int32_t timeoutMs) 124 | { 125 | int32_t timeout = 0; 126 | GetHostName_Struct gs; 127 | if (!hostName || strlen(hostName) < 1) 128 | return -1; 129 | //开线程从域名获取IP 130 | memset(&gs, 0, sizeof(GetHostName_Struct)); 131 | strcpy(gs.ip, hostName); 132 | gs.result = false; 133 | gs.actionEnd = false; 134 | if (pthread_create(&gs.thread_id, NULL, ws_getHostThread, &gs) < 0) 135 | return -1; 136 | //等待请求结果 137 | do { 138 | ws_delayms(1); 139 | } while (!gs.actionEnd && ++timeout < timeoutMs); 140 | //pthread_cancel(gs.thread_id); 141 | pthread_join(gs.thread_id, NULL); 142 | if (!gs.result) 143 | return -timeout; 144 | //一个域名可用解析出多个ip,这里只用了第一个 145 | memset(retIp, 0, strlen((const char*)retIp)); 146 | strcpy(retIp, gs.ip); 147 | return timeout; 148 | } 149 | 150 | //==================== 加密方法BASE64 ==================== 151 | 152 | //base64编/解码用的基础字符集 153 | static const char ws_base64char[] = 154 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 155 | 156 | /******************************************************************************* 157 | * 名称: ws_base64_encode 158 | * 功能: ascii编码为base64格式 159 | * 参数: 160 | * bindata: ascii字符串输入 161 | * base64: base64字符串输出 162 | * binlength: bindata的长度 163 | * 返回: base64字符串长度 164 | * 说明: 无 165 | ******************************************************************************/ 166 | int32_t ws_base64_encode(const uint8_t* bindata, char* base64, int32_t binlength) 167 | { 168 | int32_t i, j; 169 | uint8_t current; 170 | for (i = 0, j = 0; i < binlength; i += 3) 171 | { 172 | current = (bindata[i] >> 2); 173 | current &= (uint8_t)0x3F; 174 | base64[j++] = ws_base64char[(int32_t)current]; 175 | current = ((uint8_t)(bindata[i] << 4)) & ((uint8_t)0x30); 176 | if (i + 1 >= binlength) 177 | { 178 | base64[j++] = ws_base64char[(int32_t)current]; 179 | base64[j++] = '='; 180 | base64[j++] = '='; 181 | break; 182 | } 183 | current |= ((uint8_t)(bindata[i + 1] >> 4)) & ((uint8_t)0x0F); 184 | base64[j++] = ws_base64char[(int32_t)current]; 185 | current = ((uint8_t)(bindata[i + 1] << 2)) & ((uint8_t)0x3C); 186 | if (i + 2 >= binlength) 187 | { 188 | base64[j++] = ws_base64char[(int32_t)current]; 189 | base64[j++] = '='; 190 | break; 191 | } 192 | current |= ((uint8_t)(bindata[i + 2] >> 6)) & ((uint8_t)0x03); 193 | base64[j++] = ws_base64char[(int32_t)current]; 194 | current = ((uint8_t)bindata[i + 2]) & ((uint8_t)0x3F); 195 | base64[j++] = ws_base64char[(int32_t)current]; 196 | } 197 | base64[j] = '\0'; 198 | return j; 199 | } 200 | /******************************************************************************* 201 | * 名称: ws_base64_decode 202 | * 功能: base64格式解码为ascii 203 | * 参数: 204 | * base64: base64字符串输入 205 | * bindata: ascii字符串输出 206 | * 返回: 解码出来的ascii字符串长度 207 | * 说明: 无 208 | ******************************************************************************/ 209 | int32_t ws_base64_decode(const char* base64, uint8_t* bindata) 210 | { 211 | int32_t i, j; 212 | uint8_t k; 213 | uint8_t temp[4]; 214 | for (i = 0, j = 0; base64[i] != '\0'; i += 4) 215 | { 216 | memset(temp, 0xFF, sizeof(temp)); 217 | for (k = 0; k < 64; k++) 218 | { 219 | if (ws_base64char[k] == base64[i]) 220 | temp[0] = k; 221 | } 222 | for (k = 0; k < 64; k++) 223 | { 224 | if (ws_base64char[k] == base64[i + 1]) 225 | temp[1] = k; 226 | } 227 | for (k = 0; k < 64; k++) 228 | { 229 | if (ws_base64char[k] == base64[i + 2]) 230 | temp[2] = k; 231 | } 232 | for (k = 0; k < 64; k++) 233 | { 234 | if (ws_base64char[k] == base64[i + 3]) 235 | temp[3] = k; 236 | } 237 | bindata[j++] = ((uint8_t)(((uint8_t)(temp[0] << 2)) & 0xFC)) | 238 | ((uint8_t)((uint8_t)(temp[1] >> 4) & 0x03)); 239 | if (base64[i + 2] == '=') 240 | break; 241 | bindata[j++] = ((uint8_t)(((uint8_t)(temp[1] << 4)) & 0xF0)) | 242 | ((uint8_t)((uint8_t)(temp[2] >> 2) & 0x0F)); 243 | if (base64[i + 3] == '=') 244 | break; 245 | bindata[j++] = ((uint8_t)(((uint8_t)(temp[2] << 6)) & 0xF0)) | 246 | ((uint8_t)(temp[3] & 0x3F)); 247 | } 248 | return j; 249 | } 250 | 251 | //==================== 加密方法 sha1哈希 ==================== 252 | 253 | typedef struct SHA1Context 254 | { 255 | uint32_t Message_Digest[5]; 256 | uint32_t Length_Low; 257 | uint32_t Length_High; 258 | uint8_t Message_Block[64]; 259 | int32_t Message_Block_Index; 260 | int32_t Computed; 261 | int32_t Corrupted; 262 | } SHA1Context; 263 | 264 | #define SHA1CircularShift(bits, word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32 - (bits)))) 265 | 266 | static void SHA1ProcessMessageBlock(SHA1Context *context) 267 | { 268 | const uint32_t K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; 269 | int32_t t; 270 | uint32_t temp; 271 | uint32_t W[80]; 272 | uint32_t A, B, C, D, E; 273 | 274 | for (t = 0; t < 16; t++) 275 | { 276 | W[t] = ((uint32_t)context->Message_Block[t * 4]) << 24; 277 | W[t] |= ((uint32_t)context->Message_Block[t * 4 + 1]) << 16; 278 | W[t] |= ((uint32_t)context->Message_Block[t * 4 + 2]) << 8; 279 | W[t] |= ((uint32_t)context->Message_Block[t * 4 + 3]); 280 | } 281 | 282 | for (t = 16; t < 80; t++) 283 | W[t] = SHA1CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); 284 | 285 | A = context->Message_Digest[0]; 286 | B = context->Message_Digest[1]; 287 | C = context->Message_Digest[2]; 288 | D = context->Message_Digest[3]; 289 | E = context->Message_Digest[4]; 290 | 291 | for (t = 0; t < 20; t++) 292 | { 293 | temp = SHA1CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; 294 | temp &= 0xFFFFFFFF; 295 | E = D; 296 | D = C; 297 | C = SHA1CircularShift(30, B); 298 | B = A; 299 | A = temp; 300 | } 301 | for (t = 20; t < 40; t++) 302 | { 303 | temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1]; 304 | temp &= 0xFFFFFFFF; 305 | E = D; 306 | D = C; 307 | C = SHA1CircularShift(30, B); 308 | B = A; 309 | A = temp; 310 | } 311 | for (t = 40; t < 60; t++) 312 | { 313 | temp = SHA1CircularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; 314 | temp &= 0xFFFFFFFF; 315 | E = D; 316 | D = C; 317 | C = SHA1CircularShift(30, B); 318 | B = A; 319 | A = temp; 320 | } 321 | for (t = 60; t < 80; t++) 322 | { 323 | temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3]; 324 | temp &= 0xFFFFFFFF; 325 | E = D; 326 | D = C; 327 | C = SHA1CircularShift(30, B); 328 | B = A; 329 | A = temp; 330 | } 331 | context->Message_Digest[0] = (context->Message_Digest[0] + A) & 0xFFFFFFFF; 332 | context->Message_Digest[1] = (context->Message_Digest[1] + B) & 0xFFFFFFFF; 333 | context->Message_Digest[2] = (context->Message_Digest[2] + C) & 0xFFFFFFFF; 334 | context->Message_Digest[3] = (context->Message_Digest[3] + D) & 0xFFFFFFFF; 335 | context->Message_Digest[4] = (context->Message_Digest[4] + E) & 0xFFFFFFFF; 336 | context->Message_Block_Index = 0; 337 | } 338 | 339 | static void SHA1Reset(SHA1Context* context) 340 | { 341 | context->Length_Low = 0; 342 | context->Length_High = 0; 343 | context->Message_Block_Index = 0; 344 | 345 | context->Message_Digest[0] = 0x67452301; 346 | context->Message_Digest[1] = 0xEFCDAB89; 347 | context->Message_Digest[2] = 0x98BADCFE; 348 | context->Message_Digest[3] = 0x10325476; 349 | context->Message_Digest[4] = 0xC3D2E1F0; 350 | 351 | context->Computed = 0; 352 | context->Corrupted = 0; 353 | } 354 | 355 | static void SHA1PadMessage(SHA1Context* context) 356 | { 357 | if (context->Message_Block_Index > 55) 358 | { 359 | context->Message_Block[context->Message_Block_Index++] = 0x80; 360 | while (context->Message_Block_Index < 64) 361 | context->Message_Block[context->Message_Block_Index++] = 0; 362 | SHA1ProcessMessageBlock(context); 363 | while (context->Message_Block_Index < 56) 364 | context->Message_Block[context->Message_Block_Index++] = 0; 365 | } 366 | else 367 | { 368 | context->Message_Block[context->Message_Block_Index++] = 0x80; 369 | while (context->Message_Block_Index < 56) 370 | context->Message_Block[context->Message_Block_Index++] = 0; 371 | } 372 | context->Message_Block[56] = (context->Length_High >> 24) & 0xFF; 373 | context->Message_Block[57] = (context->Length_High >> 16) & 0xFF; 374 | context->Message_Block[58] = (context->Length_High >> 8) & 0xFF; 375 | context->Message_Block[59] = (context->Length_High) & 0xFF; 376 | context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF; 377 | context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF; 378 | context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF; 379 | context->Message_Block[63] = (context->Length_Low) & 0xFF; 380 | 381 | SHA1ProcessMessageBlock(context); 382 | } 383 | 384 | static int32_t SHA1Result(SHA1Context* context) 385 | { 386 | if (context->Corrupted) 387 | { 388 | return 0; 389 | } 390 | if (!context->Computed) 391 | { 392 | SHA1PadMessage(context); 393 | context->Computed = 1; 394 | } 395 | return 1; 396 | } 397 | 398 | static void SHA1Input(SHA1Context* context, const char* message_array, uint32_t length) 399 | { 400 | if (!length) 401 | return; 402 | 403 | if (context->Computed || context->Corrupted) 404 | { 405 | context->Corrupted = 1; 406 | return; 407 | } 408 | 409 | while (length-- && !context->Corrupted) 410 | { 411 | context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF); 412 | 413 | context->Length_Low += 8; 414 | 415 | context->Length_Low &= 0xFFFFFFFF; 416 | if (context->Length_Low == 0) 417 | { 418 | context->Length_High++; 419 | context->Length_High &= 0xFFFFFFFF; 420 | if (context->Length_High == 0) 421 | context->Corrupted = 1; 422 | } 423 | 424 | if (context->Message_Block_Index == 64) 425 | { 426 | SHA1ProcessMessageBlock(context); 427 | } 428 | message_array++; 429 | } 430 | } 431 | 432 | static char* sha1_hash(const char* source) 433 | { 434 | SHA1Context sha; 435 | char* buff = NULL; 436 | 437 | SHA1Reset(&sha); 438 | SHA1Input(&sha, source, strlen(source)); 439 | 440 | if (!SHA1Result(&sha)) 441 | WS_ERR("SHA1 ERROR: Could not compute message digest \r\n"); 442 | else 443 | { 444 | buff = (char*)calloc(128, sizeof(char)); 445 | sprintf(buff, "%08X%08X%08X%08X%08X", 446 | sha.Message_Digest[0], 447 | sha.Message_Digest[1], 448 | sha.Message_Digest[2], 449 | sha.Message_Digest[3], 450 | sha.Message_Digest[4]); 451 | } 452 | return buff; 453 | } 454 | 455 | //==================== websocket部分 ==================== 456 | 457 | /******************************************************************************* 458 | * 名称: ws_getRandomString 459 | * 功能: 生成随机字符串 460 | * 参数: 461 | * buff: 随机字符串存储到 462 | * len: 生成随机字符串长度 463 | * 返回: 无 464 | * 说明: 无 465 | ******************************************************************************/ 466 | static void ws_getRandomString(char* buff, uint32_t len) 467 | { 468 | uint32_t i; 469 | uint8_t temp; 470 | srand((int32_t)time(0)); 471 | for (i = 0; i < len; i++) 472 | { 473 | temp = (uint8_t)(rand() % 256); 474 | if (temp == 0) //随机数不要0 475 | temp = 128; 476 | buff[i] = temp; 477 | } 478 | } 479 | 480 | /******************************************************************************* 481 | * 名称: ws_buildShakeKey 482 | * 功能: client端使用随机数构建握手用的key 483 | * 参数: *key: 随机生成的握手key 484 | * 返回: key的长度 485 | * 说明: 无 486 | ******************************************************************************/ 487 | static int32_t ws_buildShakeKey(char* key) 488 | { 489 | char tempKey[16] = {0}; 490 | ws_getRandomString(tempKey, 16); 491 | return ws_base64_encode((const uint8_t*)tempKey, (char*)key, 16); 492 | } 493 | 494 | /******************************************************************************* 495 | * 名称: ws_buildRespondShakeKey 496 | * 功能: server端在接收client端的key后,构建回应用的key 497 | * 参数: 498 | * acceptKey: 来自客户端的key字符串 499 | * acceptKeyLen: 长度 500 | * respondKey: 在 acceptKey 之后加上 GUID, 再sha1哈希, 再转成base64得到 respondKey 501 | * 返回: respondKey的长度(肯定比acceptKey要长) 502 | * 说明: 无 503 | ******************************************************************************/ 504 | static int32_t ws_buildRespondShakeKey(char* acceptKey, uint32_t acceptKeyLen, char* respondKey) 505 | { 506 | char* clientKey; 507 | char* sha1DataTemp; 508 | uint8_t* sha1Data; 509 | int32_t i, j, sha1DataTempLen, ret; 510 | const char guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 511 | uint32_t guidLen; 512 | 513 | if (acceptKey == NULL) 514 | return 0; 515 | 516 | guidLen = sizeof(guid); 517 | clientKey = (char*)calloc(acceptKeyLen + guidLen + 10, sizeof(char)); 518 | memcpy(clientKey, acceptKey, acceptKeyLen); 519 | memcpy(&clientKey[acceptKeyLen], guid, guidLen); 520 | 521 | sha1DataTemp = sha1_hash(clientKey); 522 | sha1DataTempLen = strlen((const char*)sha1DataTemp); 523 | sha1Data = (uint8_t*)calloc(sha1DataTempLen / 2 + 1, sizeof(char)); 524 | 525 | //把hex字符串如"12ABCDEF",转为数值数组如{0x12,0xAB,0xCD,0xEF} 526 | for (i = j = 0; i < sha1DataTempLen;) 527 | { 528 | if (sha1DataTemp[i] > '9') 529 | sha1Data[j] = (10 + sha1DataTemp[i] - 'A') << 4; 530 | else 531 | sha1Data[j] = (sha1DataTemp[i] - '0') << 4; 532 | 533 | i += 1; 534 | 535 | if (sha1DataTemp[i] > '9') 536 | sha1Data[j] |= (10 + sha1DataTemp[i] - 'A'); 537 | else 538 | sha1Data[j] |= (sha1DataTemp[i] - '0'); 539 | 540 | i += 1; 541 | j += 1; 542 | } 543 | 544 | ret = ws_base64_encode((const uint8_t*)sha1Data, (char*)respondKey, j); 545 | 546 | free(sha1DataTemp); 547 | free(sha1Data); 548 | free(clientKey); 549 | return ret; 550 | } 551 | 552 | /******************************************************************************* 553 | * 名称: ws_matchShakeKey 554 | * 功能: client端收到来自服务器回应的key后进行匹配,以验证握手成功 555 | * 参数: 556 | * clientKey: client端请求握手时发给服务器的key 557 | * clientKeyLen: 长度 558 | * acceptKey: 服务器回应的key 559 | * acceptKeyLen: 长度 560 | * 返回: 0 成功 -1 失败 561 | * 说明: 无 562 | ******************************************************************************/ 563 | static int32_t ws_matchShakeKey(char* clientKey, int32_t clientKeyLen, char* acceptKey, int32_t acceptKeyLen) 564 | { 565 | int32_t retLen; 566 | char tempKey[256] = {0}; 567 | 568 | retLen = ws_buildRespondShakeKey(clientKey, clientKeyLen, tempKey); 569 | if (retLen != acceptKeyLen) 570 | { 571 | WS_ERR("len err, clientKey[%d] != acceptKey[%d]\r\n", retLen, acceptKeyLen); 572 | return -1; 573 | } 574 | else if (strcmp((const char*)tempKey, (const char*)acceptKey) != 0) 575 | { 576 | WS_ERR("strcmp err, clientKey[%s -> %s] != acceptKey[%s]\r\n", clientKey, tempKey, acceptKey); 577 | return -1; 578 | } 579 | return 0; 580 | } 581 | 582 | /******************************************************************************* 583 | * 名称: ws_buildHttpHead 584 | * 功能: 构建client端连接服务器时的http协议头, 注意websocket是GET形式的 585 | * 参数: 586 | * ip: 要连接的服务器ip字符串 587 | * port: 服务器端口 588 | * path: 要连接的端口地址 589 | * shakeKey: 握手key, 可以由任意的16位字符串打包成base64后得到 590 | * package: 存储最后打包好的内容 591 | * 返回: 无 592 | * 说明: 无 593 | ******************************************************************************/ 594 | static void ws_buildHttpHead(char* ip, int32_t port, char* path, char* shakeKey, char* package) 595 | { 596 | const char httpDemo[] = 597 | "GET %s HTTP/1.1\r\n" 598 | "Connection: Upgrade\r\n" 599 | "Host: %s:%d\r\n" 600 | "Sec-WebSocket-Key: %s\r\n" 601 | "Sec-WebSocket-Version: 13\r\n" 602 | "Upgrade: websocket\r\n\r\n"; 603 | sprintf(package, httpDemo, path, ip, port, shakeKey); 604 | } 605 | 606 | /******************************************************************************* 607 | * 名称: ws_buildHttpRespond 608 | * 功能: 构建server端回复client连接请求的http协议 609 | * 参数: 610 | * acceptKey: 来自client的握手key 611 | * acceptKeyLen: 长度 612 | * package: 存储 613 | * 返回: 无 614 | * 说明: 无 615 | ******************************************************************************/ 616 | static void ws_buildHttpRespond(char* acceptKey, uint32_t acceptKeyLen, char * protVal, char* package) 617 | { 618 | const char httpDemo[] = 619 | "HTTP/1.1 101 Switching Protocols\r\n" 620 | "Upgrade: websocket\r\n" 621 | "Server: Microsoft-HTTPAPI/2.0\r\n" 622 | "Connection: Upgrade\r\n" 623 | "Sec-WebSocket-Accept: %s\r\n" 624 | "%s" 625 | "%s\r\n\r\n"; //时间打包待续, 格式如 "Date: Tue, 20 Jun 2017 08:50:41 CST\r\n" 626 | time_t now; 627 | struct tm *tm_now; 628 | char timeStr[256] = {0}; 629 | char respondShakeKey[256] = {0}; 630 | 631 | //构建回应的握手key 632 | ws_buildRespondShakeKey(acceptKey, acceptKeyLen, respondShakeKey); 633 | //构建回应时间字符串 634 | time(&now); 635 | tm_now = localtime(&now); 636 | strftime(timeStr, sizeof(timeStr), "Date: %a, %d %b %Y %T %Z", tm_now); 637 | //组成回复信息 638 | char sproto[128] ={""}; 639 | if( protVal && strlen(protVal)>0){ 640 | sprintf(sproto,"Sec-WebSocket-Protocol:%s\r\n", protVal); 641 | } 642 | sprintf(package, httpDemo, respondShakeKey, sproto, timeStr); 643 | } 644 | 645 | /******************************************************************************* 646 | * 名称: ws_enPackage 647 | * 功能: websocket数据收发阶段的数据打包, 通常client发server的数据都要mask(掩码)处理, 反之server到client却不用 648 | * 参数: 649 | * data: 准备发出的数据 650 | * dataLen: 长度 651 | * package: 打包后存储地址 652 | * packageMaxLen: 存储地址可用长度 653 | * mask: 是否使用掩码 1要 0 不要 654 | * type: 数据类型, 由打包后第一个字节决定, 这里默认是数据传输, 即0x81 655 | * 返回: 打包后的长度(会比原数据长2~14个字节不等) <=0 打包失败 656 | * 说明: 无 657 | ******************************************************************************/ 658 | static int32_t ws_enPackage( 659 | uint8_t* data, 660 | uint32_t dataLen, 661 | uint8_t* package, 662 | uint32_t packageMaxLen, 663 | bool mask, 664 | Ws_DataType type) 665 | { 666 | uint32_t i, pkgLen = 0; 667 | //掩码 668 | uint8_t maskKey[4] = {0}; 669 | uint32_t maskCount = 0; 670 | //最小长度检查 671 | if (packageMaxLen < 2) 672 | return -1; 673 | //根据包类型设置头字节 674 | if (type == WDT_MINDATA) 675 | *package++ = 0x80; 676 | else if (type == WDT_TXTDATA) 677 | *package++ = 0x81; 678 | else if (type == WDT_BINDATA) 679 | *package++ = 0x82; 680 | else if (type == WDT_DISCONN) 681 | *package++ = 0x88; 682 | else if (type == WDT_PING) 683 | *package++ = 0x89; 684 | else if (type == WDT_PONG) 685 | *package++ = 0x8A; 686 | else 687 | return -1; 688 | pkgLen += 1; 689 | //掩码位 690 | if (mask) 691 | *package = 0x80; 692 | //半字节记录长度 693 | if (dataLen < 126) 694 | { 695 | *package++ |= (dataLen & 0x7F); 696 | pkgLen += 1; 697 | } 698 | //2字节记录长度 699 | else if (dataLen < 65536) 700 | { 701 | if (packageMaxLen < 4) 702 | return -1; 703 | *package++ |= 0x7E; 704 | *package++ = (uint8_t)((dataLen >> 8) & 0xFF); 705 | *package++ = (uint8_t)((dataLen >> 0) & 0xFF); 706 | pkgLen += 3; 707 | } 708 | //8字节记录长度 709 | else 710 | { 711 | if (packageMaxLen < 10) 712 | return -1; 713 | *package++ |= 0x7F; 714 | *package++ = 0; //数据长度变量是 uint32_t dataLen, 暂时没有那么多数据 715 | *package++ = 0; 716 | *package++ = 0; 717 | *package++ = 0; 718 | *package++ = (uint8_t)((dataLen >> 24) & 0xFF); //到这里就够传4GB数据了 719 | *package++ = (uint8_t)((dataLen >> 16) & 0xFF); 720 | *package++ = (uint8_t)((dataLen >> 8) & 0xFF); 721 | *package++ = (uint8_t)((dataLen >> 0) & 0xFF); 722 | pkgLen += 9; 723 | } 724 | //数据使用掩码时,使用异或解码,maskKey[4]依次和数据异或运算,逻辑如下 725 | if (mask) 726 | { 727 | //长度不足 728 | if (packageMaxLen < pkgLen + dataLen + 4) 729 | return -1; 730 | //随机生成掩码 731 | ws_getRandomString((char*)maskKey, sizeof(maskKey)); 732 | *package++ = maskKey[0]; 733 | *package++ = maskKey[1]; 734 | *package++ = maskKey[2]; 735 | *package++ = maskKey[3]; 736 | pkgLen += 4; 737 | for (i = 0, maskCount = 0; i < dataLen; i++, maskCount++) 738 | { 739 | //maskKey[4]循环使用 740 | if (maskCount == 4) //sizeof(maskKey)) 741 | maskCount = 0; 742 | //异或运算后得到数据 743 | *package++ = maskKey[maskCount] ^ data[i]; 744 | } 745 | pkgLen += i; 746 | //断尾 747 | *package = '\0'; 748 | } 749 | //数据没使用掩码, 直接复制数据段 750 | else 751 | { 752 | //长度不足 753 | if (packageMaxLen < pkgLen + dataLen) 754 | return -1; 755 | //这种方法,data指针位置相近时拷贝异常 756 | // memcpy(package, data, dataLen); 757 | //手动拷贝 758 | for (i = 0; i < dataLen; i++) 759 | *package++ = data[i]; 760 | pkgLen += i; 761 | //断尾 762 | *package = '\0'; 763 | } 764 | 765 | return pkgLen; 766 | } 767 | 768 | /******************************************************************************* 769 | * 名称: ws_dePackage 770 | * 功能: websocket数据收发阶段的数据解包,通常client发server的数据都要mask(掩码)处理,反之server到client却不用 771 | * 参数: 772 | * data: 要解包的数据,解包后的数据会覆写到这里 773 | * len: 要解包的数据的长度 774 | * retDataLen: 解包数据段长度信息 775 | * retHeadLen: 解包头部长度信息 776 | * retPkgType: 识别包类型 777 | * 返回: 778 | * 0: 格式错误,非标准数据包数据 779 | * <0: 识别包但不完整(能解析类型、掩码、长度),返回缺少的数据量(负值) 780 | * >0: 解包数据成功,返回数据长度,等于retDataLen 781 | * 说明: 782 | * 建议recv时先接收14字节然后解包,根据返回缺失长度再recv一次,最后再解包,这样可有效避免连包时只解析到一包的问题 783 | ******************************************************************************/ 784 | static int32_t ws_dePackage( 785 | uint8_t* data, 786 | uint32_t len, 787 | uint32_t* retDataLen, 788 | uint32_t* retHeadLen, 789 | Ws_DataType* retPkgType) 790 | { 791 | uint32_t cIn, cOut; 792 | //包类型 793 | uint8_t type; 794 | //数据段起始位置 795 | uint32_t dataOffset = 2; 796 | //数据段长度 797 | uint32_t dataLen = 0; 798 | //掩码 799 | uint8_t maskKey[4] = {0}; 800 | bool mask = false; 801 | uint8_t maskCount = 0; 802 | //数据长度过短 803 | if (len < 2) 804 | return 0; 805 | //解析包类型 806 | if ((data[0] & 0x80) == 0x80) 807 | { 808 | type = data[0] & 0x0F; 809 | if (type == 0x00) 810 | *retPkgType = WDT_MINDATA; 811 | else if (type == 0x01) 812 | *retPkgType = WDT_TXTDATA; 813 | else if (type == 0x02) 814 | *retPkgType = WDT_BINDATA; 815 | else if (type == 0x08) 816 | *retPkgType = WDT_DISCONN; 817 | else if (type == 0x09) 818 | *retPkgType = WDT_PING; 819 | else if (type == 0x0A) 820 | *retPkgType = WDT_PONG; 821 | else 822 | return 0; 823 | } 824 | else 825 | return 0; 826 | //是否掩码,及长度占用字节数 827 | if ((data[1] & 0x80) == 0x80) 828 | { 829 | mask = true; 830 | maskCount = 4; 831 | } 832 | //2字节记录长度 833 | dataLen = data[1] & 0x7F; 834 | if (dataLen == 126) 835 | { 836 | //数据长度不足以包含长度信息 837 | if (len < 4) 838 | return 0; 839 | //2字节记录长度 840 | dataLen = data[2]; 841 | dataLen = (dataLen << 8) + data[3]; 842 | //转储长度信息 843 | *retDataLen = dataLen; 844 | *retHeadLen = 4 + maskCount; 845 | //数据长度不足以包含掩码信息 846 | if (len < (uint32_t)(4 + maskCount)) 847 | return -(int32_t)(4 + maskCount + dataLen - len); 848 | //获得掩码 849 | if (mask) 850 | { 851 | maskKey[0] = data[4]; 852 | maskKey[1] = data[5]; 853 | maskKey[2] = data[6]; 854 | maskKey[3] = data[7]; 855 | dataOffset = 8; 856 | } 857 | else 858 | dataOffset = 4; 859 | } 860 | //8字节记录长度 861 | else if (dataLen == 127) 862 | { 863 | //数据长度不足以包含长度信息 864 | if (len < 10) 865 | return 0; 866 | //使用8个字节存储长度时,前4位必须为0,装不下那么多数据... 867 | if (data[2] != 0 || data[3] != 0 || data[4] != 0 || data[5] != 0) 868 | return 0; 869 | //8字节记录长度 870 | dataLen = data[6]; 871 | dataLen = (dataLen << 8) | data[7]; 872 | dataLen = (dataLen << 8) | data[8]; 873 | dataLen = (dataLen << 8) | data[9]; 874 | //转储长度信息 875 | *retDataLen = dataLen; 876 | *retHeadLen = 10 + maskCount; 877 | //数据长度不足以包含掩码信息 878 | if (len < (uint32_t)(10 + maskCount)) 879 | return -(int32_t)(10 + maskCount + dataLen - len); 880 | //获得掩码 881 | if (mask) 882 | { 883 | maskKey[0] = data[10]; 884 | maskKey[1] = data[11]; 885 | maskKey[2] = data[12]; 886 | maskKey[3] = data[13]; 887 | dataOffset = 14; 888 | } 889 | else 890 | dataOffset = 10; 891 | } 892 | //半字节记录长度 893 | else 894 | { 895 | //转储长度信息 896 | *retDataLen = dataLen; 897 | *retHeadLen = 2 + maskCount; 898 | //数据长度不足 899 | if (len < (uint32_t)(2 + maskCount)) 900 | return -(int32_t)(2 + maskCount + dataLen - len); 901 | //获得掩码 902 | if (mask) 903 | { 904 | maskKey[0] = data[2]; 905 | maskKey[1] = data[3]; 906 | maskKey[2] = data[4]; 907 | maskKey[3] = data[5]; 908 | dataOffset = 6; 909 | } 910 | else 911 | dataOffset = 2; 912 | } 913 | //数据长度不足以包含完整数据段 914 | if (len < dataLen + dataOffset) 915 | return -(int32_t)(dataLen + dataOffset - len); 916 | //解包数据使用掩码时, 使用异或解码, maskKey[4]依次和数据异或运算, 逻辑如下 917 | if (mask) 918 | { 919 | cIn = dataOffset; 920 | cOut = 0; 921 | maskCount = 0; 922 | for (; cOut < dataLen; cIn++, cOut++, maskCount++) 923 | { 924 | //maskKey[4]循环使用 925 | if (maskCount == 4) //sizeof(maskKey)) 926 | maskCount = 0; 927 | //异或运算后得到数据 928 | data[cOut] = maskKey[maskCount] ^ data[cIn]; 929 | } 930 | //断尾 931 | data[cOut] = '\0'; 932 | } 933 | //解包数据没使用掩码, 直接复制数据段 934 | else 935 | { 936 | //这种方法,data指针位置相近时拷贝异常 937 | // memcpy(data, &data[dataOffset], dataLen); 938 | //手动拷贝 939 | cIn = dataOffset; 940 | cOut = 0; 941 | for (; cOut < dataLen; cIn++, cOut++) 942 | data[cOut] = data[cIn]; 943 | //断尾 944 | data[dataLen] = '\0'; 945 | } 946 | //有些特殊包数据段长度可能为0,这里为区分格式错误返回,置为1 947 | if (dataLen == 0) 948 | dataLen = 1; 949 | return dataLen; 950 | } 951 | 952 | /******************************************************************************* 953 | * 名称: ws_requestServer 954 | * 功能: 向websocket服务器发送http(携带握手key), 以和服务器构建连接, 非阻塞模式 955 | * 参数: 956 | * ip: 服务器ip 957 | * port: 服务器端口 958 | * path: 接口地址,格式如"/tmp/xxx" 959 | * timeoutMs: connect阶段超时设置,接收阶段为timeoutMs*2,写0使用默认值1000 960 | * 返回: >0 返回连接描述符 <= 0 连接失败或超时,所花费的时间ms的负值 961 | * 说明: 无 962 | ******************************************************************************/ 963 | int32_t ws_requestServer(char* ip, int32_t port, char* path, int32_t timeoutMs) 964 | { 965 | int32_t ret, fd; 966 | int32_t timeoutCount = 0; 967 | char retBuff[512] = {0}; 968 | char httpHead[512] = {0}; 969 | char shakeKey[128] = {0}; 970 | char tempIp[128] = {0}; 971 | char* p; 972 | 973 | //服务器端网络地址结构体 974 | struct sockaddr_in report_addr; 975 | memset(&report_addr, 0, sizeof(report_addr)); //数据初始化--清零 976 | report_addr.sin_family = AF_INET; //设置为IP通信 977 | report_addr.sin_port = htons(port); //服务器端口号 978 | 979 | //服务器IP地址, 自动域名转换 980 | //report_addr.sin_addr.s_addr = inet_addr(ip); 981 | if ((report_addr.sin_addr.s_addr = inet_addr(ip)) == INADDR_NONE) 982 | { 983 | ret = ws_getIpByHostName(ip, tempIp, 1000); 984 | if (ret < 0) 985 | return ret; 986 | else if (strlen((const char*)tempIp) < 7) 987 | return -ret; 988 | else 989 | timeoutCount += ret; 990 | if ((report_addr.sin_addr.s_addr = inet_addr(tempIp)) == INADDR_NONE) 991 | return -ret; 992 | #ifdef WS_DEBUG 993 | WS_INFO("Host(%s) to IP(%s)\r\n", ip, tempIp); 994 | #endif 995 | } 996 | 997 | //默认超时1秒 998 | if (timeoutMs == 0) 999 | timeoutMs = 1000; 1000 | 1001 | //create unix socket 1002 | if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 1003 | { 1004 | WS_ERR("socket error\r\n"); 1005 | return -1; 1006 | } 1007 | 1008 | //connect 1009 | timeoutCount = 0; 1010 | while (connect(fd, (struct sockaddr *)&report_addr, sizeof(struct sockaddr)) != 0) 1011 | { 1012 | if (++timeoutCount > timeoutMs) 1013 | { 1014 | WS_ERR("connect to %s:%d timeout(%dms)\r\n", ip, port, timeoutCount); 1015 | close(fd); 1016 | return -timeoutCount; 1017 | } 1018 | ws_delayms(1); 1019 | } 1020 | 1021 | //非阻塞 1022 | ret = fcntl(fd, F_GETFL, 0); 1023 | fcntl(fd, F_SETFL, ret | O_NONBLOCK); 1024 | 1025 | //发送http协议头 1026 | memset(shakeKey, 0, sizeof(shakeKey)); 1027 | ws_buildShakeKey(shakeKey); //创建握手key 1028 | memset(httpHead, 0, sizeof(httpHead)); //创建协议包 1029 | ws_buildHttpHead(ip, port, path, shakeKey, (char*)httpHead); //组装http请求头 1030 | send(fd, httpHead, strlen((const char*)httpHead), MSG_NOSIGNAL); 1031 | 1032 | #ifdef WS_DEBUG 1033 | WS_INFO("connect: %dms\r\n%s\r\n", timeoutCount, httpHead); 1034 | #endif 1035 | while (1) 1036 | { 1037 | memset(retBuff, 0, sizeof(retBuff)); 1038 | ret = recv(fd, retBuff, sizeof(retBuff), MSG_NOSIGNAL); 1039 | if (ret > 0) 1040 | { 1041 | #ifdef WS_DEBUG 1042 | //显示http返回 1043 | WS_INFO("recv: len %d / %dms\r\n%s\r\n", ret, timeoutCount, retBuff); 1044 | #endif 1045 | //返回的是http回应信息 1046 | if (strncmp((const char*)retBuff, "HTTP", 4) == 0) 1047 | { 1048 | //定位到握手字符串 1049 | if ((p = strstr((char*)retBuff, "Sec-WebSocket-Accept: ")) != NULL) 1050 | { 1051 | p += strlen("Sec-WebSocket-Accept: "); 1052 | sscanf((const char*)p, "%s\r\n", p); 1053 | //比对握手信息 1054 | if (ws_matchShakeKey(shakeKey, strlen((const char*)shakeKey), p, strlen((const char*)p)) == 0) 1055 | return fd; 1056 | //握手信号不对, 重发协议包 1057 | else 1058 | ret = send(fd, httpHead, strlen((const char*)httpHead), MSG_NOSIGNAL); 1059 | } 1060 | //重发协议包 1061 | else 1062 | ret = send(fd, httpHead, strlen((const char*)httpHead), MSG_NOSIGNAL); 1063 | } 1064 | //显示异常返回数据 1065 | else 1066 | { 1067 | //#ifdef WS_DEBUG 1068 | WS_ERR("recv: len %d / unknown context\r\n%s\r\n", ret, retBuff); 1069 | WS_HEX(stderr, retBuff, ret); 1070 | //#endif 1071 | } 1072 | } 1073 | ws_delayms(1); 1074 | //超时检查 1075 | if (++timeoutCount > timeoutMs * 2) 1076 | break; 1077 | } 1078 | //连接失败,返回耗时(负值) 1079 | close(fd); 1080 | return -timeoutCount; 1081 | } 1082 | 1083 | /******************************************************************************* 1084 | * 名称: ws_replyClient 1085 | * 功能: 服务器回复客户端的连接请求, 以建立websocket连接 1086 | * 参数: 1087 | * fd: 连接描述符 1088 | * buff: 接收到来自客户端的数据(内含http连接请求) 1089 | * buffLen: 1090 | * path: path匹配检查,不用可以置NULL 1091 | * 返回: >0 建立websocket连接成功 <=0 建立websocket连接失败 1092 | * 说明: 无 1093 | ******************************************************************************/ 1094 | int32_t ws_replyClient(int32_t fd, char* buff, int32_t buffLen, char* path) 1095 | { 1096 | char* keyOffset, *protOffset; 1097 | int32_t ret; 1098 | char recvShakeKey[512] = {0}; 1099 | char recvPotocol[512] ={0}; 1100 | char respondPackage[1024] = {0}; 1101 | #ifdef WS_DEBUG 1102 | WS_INFO("recv: len %d \r\n%s\r\n", buffLen, buff); 1103 | #endif 1104 | //path检查 1105 | if (path && !strstr((char*)buff, path)) 1106 | { 1107 | WS_ERR("path not matched\r\n"); 1108 | return -1; 1109 | } 1110 | //获取握手key 1111 | if (!(keyOffset = strstr((char*)buff, "Sec-WebSocket-Key: "))) 1112 | { 1113 | WS_ERR("Sec-WebSocket-Key not found\r\n"); 1114 | return -1; 1115 | } 1116 | //获取握手key 1117 | keyOffset += strlen("Sec-WebSocket-Key: "); 1118 | sscanf((const char*)keyOffset, "%s", recvShakeKey); 1119 | ret = strlen((const char*)recvShakeKey); 1120 | if (ret < 1) 1121 | { 1122 | WS_ERR("Sec-WebSocket-Key not matched\r\n"); 1123 | return -1; 1124 | } 1125 | 1126 | if (!(protOffset = strstr((char*)buff, "Sec-WebSocket-Protocol: "))) 1127 | { 1128 | WS_INFO("Sec-WebSocket-Protocol not found\r\n"); 1129 | }else{ 1130 | //获取握手key 1131 | protOffset += strlen("Sec-WebSocket-Protocol: "); 1132 | sscanf((const char*)protOffset, "%s", recvPotocol); 1133 | WS_INFO("Sec-WebSocket-Protocol:[%s]\r\n[%s]\n", recvPotocol,buff); 1134 | } 1135 | 1136 | //创建回复key 1137 | ws_buildHttpRespond(recvShakeKey, (uint32_t)ret, recvPotocol, respondPackage); 1138 | return send(fd, respondPackage, strlen((const char*)respondPackage), MSG_NOSIGNAL); 1139 | } 1140 | 1141 | /******************************************************************************* 1142 | * 名称: ws_send 1143 | * 功能: websocket数据基本打包和发送 1144 | * 参数: 1145 | * fd: 连接描述符 1146 | * *buff: 数据 1147 | * buffLen: 长度 1148 | * mask: 数据是否使用掩码, 客户端到服务器必须使用掩码模式 1149 | * type: 数据要要以什么识别头类型发送(txt, bin, ping, pong ...) 1150 | * 返回: 调用send的返回 1151 | * 说明: 无 1152 | ******************************************************************************/ 1153 | int32_t ws_send(int32_t fd, void* buff, int32_t buffLen, bool mask, Ws_DataType type) 1154 | { 1155 | uint8_t* wsPkg = NULL; 1156 | int32_t retLen, ret; 1157 | //参数检查 1158 | if (buffLen < 0) 1159 | return 0; 1160 | //非包数据发送 1161 | if (type == WDT_NULL) 1162 | return send(fd, buff, buffLen, MSG_NOSIGNAL); 1163 | //数据打包 +14 预留类型、掩码、长度保存位 1164 | wsPkg = (uint8_t*)calloc(buffLen + 14, sizeof(uint8_t)); 1165 | retLen = ws_enPackage((uint8_t*)buff, buffLen, wsPkg, (buffLen + 14), mask, type); 1166 | if (retLen <= 0) 1167 | { 1168 | free(wsPkg); 1169 | return 0; 1170 | } 1171 | #ifdef WS_DEBUG 1172 | //显示数据 1173 | WS_INFO("ws_send: len/%d\r\n", retLen); 1174 | WS_HEX(stdout, wsPkg, retLen); 1175 | #endif 1176 | ret = send(fd, wsPkg, retLen, MSG_NOSIGNAL); 1177 | free(wsPkg); 1178 | return ret; 1179 | } 1180 | 1181 | /******************************************************************************* 1182 | * 名称: ws_recv 1183 | * 功能: websocket数据接收和基本解包 1184 | * 参数: 1185 | * fd: 连接描述符 1186 | * buff: 数据接收地址 1187 | * buffSize: 接收区可用最大长度,至少16字节 1188 | * 返回: 1189 | * =0 没有收到有效数据(或者收到特殊包,如果是 WDT_DISCONN 则fd已被close) 1190 | * >0 成功接收并解包数据 1191 | * <0 非标准数据包数据的长度 1192 | * 说明: 无 1193 | ******************************************************************************/ 1194 | int32_t ws_recv(int32_t fd, void* buff, int32_t buffSize, Ws_DataType* retType) 1195 | { 1196 | int32_t ret; 1197 | int32_t retRecv = 0; //调用recv的返回 1198 | int32_t retDePkg; //调用解包的返回 1199 | uint32_t retDataLen = 0; //解包得到的数据段长度 1200 | uint32_t retHeadLen = 0; //解包得到的包头部长度 1201 | int32_t retFinal = 0; //最终返回 1202 | uint32_t timeout = 0; //接收超时计数 1203 | 1204 | char tmp[16]; //为防止一次接收到多包数据(粘包),先尝试性接收ws头部字节,得知总长度后再接收剩下部分 1205 | Ws_DataType retPkgType = WDT_NULL; //默认返回包类型 1206 | char* cBuff = (char*)buff; //转换指针类型 1207 | 1208 | //丢弃数据 1209 | if (!buff || buffSize < 1) 1210 | { 1211 | while (recv(fd, tmp, sizeof(tmp), MSG_NOSIGNAL) > 0) 1212 | ; 1213 | } 1214 | //先接收数据头部,头部最大2+4+8=14字节 1215 | else 1216 | { 1217 | if (buffSize < 16) 1218 | WS_ERR("error, buffSize must be >= 16 \r\n"); 1219 | else 1220 | retRecv = recv(fd, buff, 14, MSG_NOSIGNAL); 1221 | } 1222 | if (retRecv > 0) 1223 | { 1224 | //数据解包 1225 | retDePkg = ws_dePackage((uint8_t*)buff, retRecv, &retDataLen, &retHeadLen, &retPkgType); 1226 | //1. 非标准数据包数据,再接收一次(防止丢数据),之后返回"负len"长度值 1227 | //2. buffSize不足以收下这一包数据,当作非标准数据包数据处理,能收多少算多少 1228 | if (retDePkg == 0 || (retDePkg < 0 && retRecv - retDePkg > buffSize)) 1229 | { 1230 | //能收多少算多少 1231 | retRecv += recv(fd, &cBuff[retRecv], buffSize - retRecv, MSG_NOSIGNAL); 1232 | //对于包过大的问题 1233 | if (retDePkg < 0) 1234 | { 1235 | //1. 发出警告 1236 | WS_ERR("warnning, pkgLen(%d) > buffSize(%d)\r\n", retRecv - retDePkg, buffSize); 1237 | //2. 把这包数据丢弃,以免影响后续包 1238 | while (recv(fd, tmp, sizeof(tmp), MSG_NOSIGNAL) > 0) 1239 | ; 1240 | } 1241 | retFinal = -retRecv; 1242 | #ifdef WS_DEBUG 1243 | //显示数据 1244 | WS_INFO("ws_recv1: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n", 1245 | retRecv, retDePkg, retDataLen, retHeadLen, retPkgType); 1246 | WS_HEX(stdout, buff, retRecv); 1247 | #endif 1248 | } 1249 | //正常收包 1250 | else 1251 | { 1252 | //检查是否需要续传 1253 | if (retDePkg < 0) 1254 | { 1255 | //再接收一次(通常情况) 1256 | ret = recv(fd, &cBuff[retRecv], -retDePkg, MSG_NOSIGNAL); 1257 | if (ret > 0) 1258 | { 1259 | retRecv += ret; 1260 | retDePkg += ret; 1261 | } 1262 | //数据量上百K时需要多次recv,无数据200ms超时,继续接收 1263 | for (timeout = 0; timeout < 200 && retDePkg < 0;) 1264 | { 1265 | ws_delayms(5); 1266 | timeout += 5; 1267 | ret = recv(fd, &cBuff[retRecv], -retDePkg, MSG_NOSIGNAL); 1268 | if (ret > 0) 1269 | { 1270 | timeout = 0; 1271 | retRecv += ret; 1272 | retDePkg += ret; 1273 | } 1274 | } 1275 | #ifdef WS_DEBUG 1276 | //显示数据 1277 | WS_INFO("ws_recv2: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n", 1278 | retRecv, retDePkg, retDataLen, retHeadLen, retPkgType); 1279 | WS_HEX(stdout, buff, retRecv); 1280 | #endif 1281 | //二次解包 1282 | retDePkg = ws_dePackage((uint8_t*)buff, retRecv, &retDataLen, &retHeadLen, &retPkgType); 1283 | } 1284 | #ifdef WS_DEBUG 1285 | //显示数据 1286 | WS_INFO("ws_recv3: len/%d retDePkg/%d retDataLen/%d retHeadLen/%d retPkgType/%d\r\n", 1287 | retRecv, retDePkg, retDataLen, retHeadLen, retPkgType); 1288 | WS_HEX(stdout, buff, retRecv); 1289 | #endif 1290 | //一包数据终于完整的接收完了... 1291 | if (retDePkg > 0) 1292 | { 1293 | //收到 PING 包,应自动回复 PONG 1294 | if (retPkgType == WDT_PING) 1295 | { 1296 | //自动 ping-pong 1297 | ws_send(fd, NULL, 0, false, WDT_PONG); 1298 | // WS_INFO("ws_recv: WDT_PING\r\n"); 1299 | retFinal = 0; 1300 | } 1301 | //收到 PONG 包 1302 | else if (retPkgType == WDT_PONG) 1303 | { 1304 | // WS_INFO("ws_recv: WDT_PONG\r\n"); 1305 | retFinal = 0; 1306 | } 1307 | //收到 断连 包 1308 | else if (retPkgType == WDT_DISCONN) 1309 | { 1310 | // WS_INFO("ws_recv: WDT_DISCONN\r\n"); 1311 | retFinal = 0; 1312 | } 1313 | //其它正常数据包 1314 | else 1315 | retFinal = retDePkg; 1316 | } 1317 | //未曾设想的道路... 1318 | else 1319 | retFinal = -retRecv; 1320 | } 1321 | } 1322 | //返回包类型 1323 | if (retType) 1324 | *retType = retPkgType; 1325 | 1326 | return retFinal; 1327 | } 1328 | --------------------------------------------------------------------------------