电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
STM32MP157 Mini开发板评测 Linux C实现支持WebSocket的服务器
分 享
扫描二维码分享
STM32MP157 Mini开发板评测 Linux C实现支持WebSocket的服务器
服务器
socket
linux
艾克
关注
发布时间: 2021-11-08
丨
阅读: 1297
这篇文章要做的是tcp通信实例,Linux下实现一个支持websocket协议的服务器。也是在ATK-MP157mini开发板上的学习实践。 整体而言,从两个方面进行服务器代码的实现。 **一、TPC通信的建立。** 1、下面这张图说明了客户端与服务端进行TCP通信的一个过程。 ![](https://cf04.ickimg.com/bbsimages/202111/cd5c0e91bfbc823dc58c07225a8f15cd.png) 2、什么是Socket呢? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 3、服务端编程梳理。 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。 4、一段代码供参考。 ```c void *thread_socket_read(void *arg){ //tcp int fd_server; char ipstr[128]; socklen_t sin_size; struct sockaddr_in addr_server,addr_client; struct tcp_info info; int len = sizeof(info); //web char payload_data[1024] = {0}; frame_head head; message_queue msg_socketr; if((fd_server = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("socket"); exit(-1); } bzero(&addr_server,sizeof(addr_server)); addr_server.sin_family = AF_INET; addr_server.sin_addr.s_addr = htonl(INADDR_ANY); addr_server.sin_port = htons(8000); int port_reuse = 1; setsockopt(fd_server, SOL_SOCKET,SO_REUSEADDR, (const void *)&port_reuse, sizeof(port_reuse)); //set_tcp_keepAlive(fd_server,6,2,2); int keepAlive = 1; setsockopt(fd_server,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)); //set_tcp_keepAlive(fd_server,6,2,2); if(bind(fd_server,(struct sockaddr *)&addr_server,sizeof(addr_server)) == -1){ perror("bind"); exit(-1); } listen(fd_server,128); printf("start listen\n"); relisten: sin_size = sizeof(addr_client); if((fd_client = accept(fd_server,(struct sockaddr *)&addr_client,(socklen_t*)&sin_size)) == -1){ perror("accept"); } //如果有客户端连接上服务器,就输出客户端的ip地址和端口号 printf("client ip %s\tport %d\n", inet_ntop(AF_INET,(struct sockaddr *)&addr_client.sin_addr.s_addr,ipstr,sizeof(ipstr)),ntohs(addr_client.sin_port)); printf("webserver connect ok\n\n"); shakehands_to_web(fd_client); while(1){ getsockopt(fd_client, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); if(info.tcpi_state == TCP_ESTABLISHED){ // recv data from web client*/ recv_frame_head(fd_client,&head); //you block int rul = recv(fd_client,payload_data,1024,MSG_DONTWAIT); if(rul > 0){ umask(payload_data,rul,head.masking_key); //printf("len=%d %s\n",strlen(payload_data),payload_data); msg_socketr.type = soc_recv; memcpy(msg_socketr.buffer,payload_data,130); msgsnd(msgget((key_t)1001,IPC_CREAT|0666),&msg_socketr,130,0); } memset(payload_data,0,sizeof(payload_data)); }else{ close(fd_client); fd_client = 0; printf("client open\n"); goto relisten; } usleep(20000); } } ``` **二、WebSocket协议支持的加入**。 1、openssl库的引入。 握手过程中需要进行sha1编码和base64编码,因此系统和工程中要引入openssl的支持。 2、帧格式解析。 ``` 这样来看是比较直观的,注意通信过程的数据长度对应的控制字节 /@@*------------------------------------------------------------------- 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ ``` 需要实现base64编码的封装。 ```c int base64_encode(char *in_str, int in_len, char *out_str) { BIO *b64, *bio; BUF_MEM *bptr = NULL; size_t size = 0; if (in_str == NULL || out_str == NULL) return -1; b64 = BIO_new(BIO_f_base64()); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); BIO_write(bio, in_str, in_len); BIO_flush(bio); BIO_get_mem_ptr(bio, &bptr); memcpy(out_str, bptr->data, bptr->length); out_str[bptr->length-1] = '\0'; size = bptr->length; BIO_free_all(bio); return size; } ``` 握手过程的处理函数。接收客户端http格式的请求,从中获得Sec-WebSocket-Key对应的值,与魔法字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 进行连接后进行sha1 hash,再将结果(sha1)的直接结果,不是转化为字符串后的结果)进行Base64编码。最后构造响应头部,发送响应,与客户端建立websocket连接。 ```c int shakehands_to_web(int cli_fd) { //next line's point num int level = 0; //all request data char buffer[BUFFER_SIZE]; //a line data char linebuf[256]; //Sec-WebSocket-Accept char sec_accept[32]; //sha1 data unsigned char sha1_data[SHA_DIGEST_LENGTH+1]={0}; //reponse head buffer char head[BUFFER_SIZE] = {0}; if (read(cli_fd,buffer,sizeof(buffer))<=0) perror("read"); do { memset(linebuf,0,sizeof(linebuf)); level = readline_from_buffer(buffer,level,linebuf); //printf("line:%s\n",linebuf); if (strstr(linebuf,"Sec-WebSocket-Key")!=NULL) { strcat(linebuf,GUID); SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data); base64_encode(sha1_data,strlen(sha1_data),sec_accept); /@@* write the response */ sprintf(head, "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade: websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "\r\n",sec_accept); ); if (write(cli_fd,head,strlen(head))<0) perror("write"); break; } }while((buffer[level]!='\r' || buffer[level+1]!='\n') && level!=-1); return 0; } ``` 帧头的封装和解析。 ```c int recv_frame_head(int fd,frame_head* head) { char one_char; /@@*read fin and op code*/ if (read(fd,&one_char,1)<=0) { perror("read fin"); return -1; } head->fin = (one_char & 0x80) == 0x80; head->opcode = one_char & 0x0F; if (read(fd,&one_char,1)<=0) { perror("read mask"); return -1; } head->mask = (one_char & 0x80) == 0X80; /@@*get payload length*/ head->payload_length = one_char & 0x7F; if (head->payload_length == 126) { char extern_len[2]; if (read(fd,extern_len,2)<=0) { perror("read extern_len"); return -1; } head->payload_length = (extern_len[0]&0xFF) << 8 | (extern_len[1]&0xFF); } else if (head->payload_length == 127) { char extern_len[8],temp; int i; if (read(fd,extern_len,8)<=0) { perror("read extern_len"); return -1; } for(i=0;i<4;i++) { temp = extern_len[i]; extern_len[i] = extern_len[7-i]; extern_len[7-i] = temp; } memcpy(&(head->payload_length),extern_len,8); } /@@*read masking-key*/ if (read(fd,head->masking_key,4)<=0) { perror("read masking-key"); return -1; } return 0; } int send_frame_head(int fd,frame_head* head) { char *response_head; int head_length = 0; if(head->payload_length<126) { response_head = (char*)malloc(2); response_head[0] = 0x81; response_head[1] = head->payload_length; head_length = 2; } else if (head->payload_length<0xFFFF) { response_head = (char*)malloc(4); response_head[0] = 0x81; response_head[1] = 126; response_head[2] = (head->payload_length >> 8 & 0xFF); response_head[3] = (head->payload_length & 0xFF); head_length = 4; } else { //long data response_head = (char*)malloc(10); response_head[0] = 0x81; response_head[1] = 127; response_head[2] = (head->payload_length >> 56 & 0xff); response_head[3] = (head->payload_length >> 48 & 0xff); response_head[4] = (head->payload_length >> 40 & 0xff); response_head[5] = (head->payload_length >> 32 & 0xff); response_head[6] = (head->payload_length >> 24 & 0xff); response_head[7] = (head->payload_length >> 16 & 0xff); response_head[8] = (head->payload_length >> 8 & 0xff); response_head[9] = (head->payload_length & 0xff); head_length = 10; } if(write(fd,response_head,head_length)<=0) { perror("write head"); return -1; } free(response_head); return 0; } ``` 还有去掩码的处理,原数据是亦或加密的。 ```c //xor decode void umask(char *data,int len,char *mask) { int i; for (i=0;i
eep(1); } close(conn); close(ser_fd); } ``` 4、实践应用,基于Linux。 在ATK-MP157mini板子上运行需要arm-gcc交叉编译,在Linux 主机上只需要gcc编译即可。运行实例,可以看到如下内容。需要留意的是编译时需要加-lcrypto,基于openssl的链接库。 ![](https://cf04.ickimg.com/bbsimages/202111/54ea28325bb11e3b6d0a7704853eb8f9.png) 然后运行web端代码,javascript实现的客户端,在控制台就可以看到console.log函数的输出内容了,与服务器发出的字符串吻合即没有问题。 ![](https://cf04.ickimg.com/bbsimages/202111/863290c5c7446b998a5e4ef2a02f4b48.png) 感兴趣的可以关注笔者公众号: 懂一点技术的老王
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
艾克
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交