zoukankan      html  css  js  c++  java
  • 用C语言实现websocket服务器

    Websocket Echo Server Demo

    背景

    嵌入式设备的应用开发大都依靠C语言来完成,我去研究如何用c语言实现websocket服务器也是为了在嵌入式设备中实现一个ip camera的功能,用户通过网页访问到嵌入式设备的摄像头以及音频,在学习的过程中先实现echo server是最基本的。

    主要参考资源

    具体实现

    整个websocket从握手到数据传输帧头的格式不在这里展开,具体参考编写 WebSocket 服务器——MDN,在这里只介绍一下websocket echo server的实现。

    • 头文件及宏定义
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    /*在握手时需要进行sha1编码和base64编码,
    在这里用openssl的库来实现*/
    #include <openssl/sha.h>
    #include <openssl/pem.h>
    #include <openssl/bio.h>
    #include <openssl/evp.h>
    
    #define BUFFER_SIZE 1024
    #define RESPONSE_HEADER_LEN_MAX 1024
    #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 数据帧头
    /*-----------为了便于理解,在这里吧数据帧格式粘出来-------------------
    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 ...                |
    +---------------------------------------------------------------+
    --------------------------------------------------------------------*/
    typedef struct _frame_head {
        char fin;
        char opcode;
        char mask;
        unsigned long long payload_length;
        char masking_key[4];
    } frame_head;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 封装套接字函数 
      为了使套接字使用看起来简洁一些,封装一个被动套接字函数,只需要传入监听端口和监听队列个数就可以返回套接字描述符,调用者可以直接用这个描述符accept去接收客户端连接。
    int passive_server(int port,int queue)
    {
        ///定义sockfd
        int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
    
        ///定义sockaddr_in
        struct sockaddr_in server_sockaddr;
        server_sockaddr.sin_family = AF_INET;
        server_sockaddr.sin_port = htons(port);
        server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        ///bind,成功返回0,出错返回-1
        if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
        {
            perror("bind");
            exit(1);
        }
        ///listen,成功返回0,出错返回-1
        if(listen(server_sockfd,queue) == -1)
        {
            perror("listen");
            exit(1);
        }
        printf("监听%d端口
    ",port);
        return server_sockfd;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • Base64编码函数 
      握手函数会用到
    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] = '';
        size = bptr->length;
    
        BIO_free_all(bio);
        return size;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 逐行读取函数 
      握手函数循环调用,每次获得一行字符串,返回下一行开始位置
    /**
     * @brief _readline
     * read a line string from all buffer
     * @param allbuf
     * @param level
     * @param linebuf
     * @return
     */
    int _readline(char* allbuf,int level,char* linebuf)
    {
        int len = strlen(allbuf);
        for (;level<len;++level)
        {
            if(allbuf[level]=='
    ' && allbuf[level+1]=='
    ')
                return level+2;
            else
                *(linebuf++) = allbuf[level];
        }
        return -1;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 握手函数 
      负责处理新客户端的连接,接收客户端http格式的请求,从中获得Sec-WebSocket-Key对应的值,与魔法字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 进行连接后进行sha1 hash,再将结果(sha1的直接结果,不是转化为字符串后的结果)进行Base64编码。最后构造响应头部,发送响应,与客户端建立websocket连接。
    int shakehands(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");
        printf("request
    ");
        printf("%s
    ",buffer);
    
        do {
            memset(linebuf,0,sizeof(linebuf));
            level = _readline(buffer,level,linebuf);
            //printf("line:%s
    ",linebuf);
    
            if (strstr(linebuf,"Sec-WebSocket-Key")!=NULL)
            {
                strcat(linebuf,GUID);
    //            printf("key:%s
    len=%d
    ",linebuf+19,strlen(linebuf+19));
                SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data);
    //            printf("sha1:%s
    ",sha1_data);
                base64_encode(sha1_data,strlen(sha1_data),sec_accept);
    //            printf("base64:%s
    ",sec_accept);
                /* write the response */
                sprintf(head, "HTTP/1.1 101 Switching Protocols
    " 
                              "Upgrade: websocket
    " 
                              "Connection: Upgrade
    " 
                              "Sec-WebSocket-Accept: %s
    " 
                              "
    ",sec_accept);
    
                printf("response
    ");
                printf("%s",head);
                if (write(cli_fd,head,strlen(head))<0)
                    perror("write");
    
                break;
            }
        }while((buffer[level]!='
    ' || buffer[level+1]!='
    ') && level!=-1);
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 字符串反转函数 
      用于解决大端小端问题
    void inverted_string(char *str,int len)
    {
        int i; char temp;
        for (i=0;i<len/2;++i)
        {
            temp = *(str+i);
            *(str+i) = *(str+len-i-1);
            *(str+len-i-1) = temp;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 接收及存储数据帧头 
      调用者传一个数据帧头结构体指针用于获取解析后的帧头 
      解析过程依照MDN中说的结构解析就好。
    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];
            if (read(fd,extern_len,8)<=0)
            {
                perror("read extern_len");
                return -1;
            }
            inverted_string(extern_len,8);
            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;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 去掩码函数 
      从客户端发来的数据是经过异或加密的,我们在解析帧头的时候获取到了掩码,我们通过掩码可以解码出原数据。
    /**
     * @brief umask
     * xor decode
     * @param data 传过来时为密文,解码后的明文同样存储在这里
     * @param len data的长度
     * @param mask 掩码
     */
    void umask(char *data,int len,char *mask)
    {
        int i;
        for (i=0;i<len;++i)
            *(data+i) ^= *(mask+(i%4));
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 发送数据帧头
    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
        {
            response_head = (char*)malloc(12);
            response_head[0] = 0x81;
            response_head[1] = 127;
            memcpy(response_head+2,head->payload_length,sizeof(unsigned long long));
            inverted_string(response_head+2,sizeof(unsigned long long));
            head_length = 12;
        }
    
        if(write(fd,response_head,head_length)<=0)
        {
            perror("write head");
            return -1;
        }
    
        free(response_head);
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 主函数 
      接收一个连接并循环回射。
    int main()
    {
        int ser_fd = passive_server(4444,20);
    
    
        struct sockaddr_in client_addr;
        socklen_t addr_length = sizeof(client_addr);
        int conn = accept(ser_fd,(struct sockaddr*)&client_addr, &addr_length);
    
        shakehands(conn);
    
        while (1)
        {
            frame_head head;
            int rul = recv_frame_head(conn,&head);
            if (rul < 0)
                break;
    //        printf("fin=%d
    opcode=0x%X
    mask=%d
    payload_len=%llu
    ",head.fin,head.opcode,head.mask,head.payload_length);
    
            //echo head
            send_frame_head(conn,&head);
            //read payload data
            char payload_data[1024] = {0};
            int size = 0;
            do {
                int rul;
                rul = read(conn,payload_data,1024);
                if (rul<=0)
                    break;
                size+=rul;
    
                umask(payload_data,size,head.masking_key);
                printf("recive:%s",payload_data);
    
                //echo data
                if (write(conn,payload_data,rul)<=0)
                    break;
            }while(size<head.payload_length);
            printf("
    -----------
    ");
    
        }
    
        close(conn);
        close(ser_fd);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 客户端测试用例: 
      将以下代码保存为websocket_client.html,用Chrome浏览器打开测试。 
      代码中console.log是在浏览器中按F12在控制台中查看输出
    <button onclick="svc_connectPlatform()"> connect</button>
    <button onclick="svc_send('hello web')"> send</button>
    <script>
    
        function svc_connectPlatform() {
            //alert("");
            var wsServer = 'ws://192.168.25.157:4444/';
            try {
                svc_websocket = new WebSocket(wsServer);
            } catch (evt) {
                console.log("new WebSocket error:" + evt.data);
                svc_websocket = null;
                if (typeof(connCb) != "undefined" && connCb != null)
                    connCb("-1", "connect error!");
                return;
            }
            //alert("");
            svc_websocket.onopen = svc_onOpen;
            svc_websocket.onclose = svc_onClose;
            svc_websocket.onmessage = svc_onMessage;
            svc_websocket.onerror = svc_onError;
        }
    
    
        function svc_onOpen(evt) {
            console.log("Connected to WebSocket server.");
        }
    
    
        function svc_onClose(evt) {
            console.log("Disconnected");
        }
    
    
        function svc_onMessage(evt) {
            console.log('Retrieved data from server: ' + evt.data);
        }
    
    
        function svc_onError(evt) {
            console.log('Error occured: ' + evt.data);
        }
    
    
        function svc_send(msg) {
            if (svc_websocket.readyState == WebSocket.OPEN) {
                svc_websocket.send(msg);
            } else {
                console.log("send failed. websocket not open. please check.");
            }
        }
    </script>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    开源代码:https://github.com/lhc3538/my-websocket-server

  • 相关阅读:
    EC++学习笔记(五) 实现
    EC++学习笔记(三) 资源管理
    EC++学习笔记(一) 习惯c++
    EC++学习笔记(六) 继承和面向对象设计
    STL学习笔记(三) 关联容器
    STL学习笔记(一) 容器
    背包问题详解
    EC++学习笔记(二) 构造/析构/赋值
    STL学习笔记(四) 迭代器
    常用安全测试用例
  • 原文地址:https://www.cnblogs.com/zxtceq/p/7388845.html
Copyright © 2011-2022 走看看