8、WebSocket介绍

一、为什么需要 WebSocket

  初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

  举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

   这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

二、简介

  WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

 

 其他特点包括:

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

三、应用场景

  (1)股票终端、(2)网页聊天,即时通讯、(3)视频弹幕。

四、Websocket组成

  (1)握手 (2)通信(两者之间发送的应用层数据)。

五、握手动作

  这个握手并不是tcp连接的3次握手。

  握手动作:已经建立好TCP连接以后,客户端发送给服务器的一段握手数据,服务器经过计算并返回给客户端,服务器和客户端计算的数据对比,相同的话,数据校验通过。

 

   计算过程:

  (1)服务器首先拿到Sec-WebSocket-Key,(2)然后连接GUID(唯一的),(3)经过哈希函数SHA-1(4)然后进行base64encode计算

  如下图:

  

 

   把得到的结果返回给客户端,客户端也会计算出结果,如果两者相同,则校验成功。

六、Websocket对应的状态机

  状态机的作用:根据状态的改变,判断是握手数据还是通信数据。

 

 七、Websocket对应的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>

#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>

#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

#define BUFFER_LENGTH            1024
#define GUID                     "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"


enum  WEBSOCKET_STATUS {
    WS_HANDSHARK,
    WS_DATATRANSFORM,
    WS_DATAEND,
};


struct sockitem 
{ 
    int sockfd;
    int(*callback)(int fd, int events, void *arg);

    char recvbuffer[BUFFER_LENGTH]; //
    char sendbuffer[BUFFER_LENGTH];

    int rlength;
    int slength;

    int status;

};

// mainloop / eventloop --> epoll -->  
struct reactor 
{
    int epfd;
    struct epoll_event events[512];
};


struct reactor *eventloop = NULL;

int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);

#if 1  // websocket

char* decode_packet(char *stream, char *mask, int length, int *ret);
int encode_packet(char *buffer, char *mask, char *stream, int length);

struct _nty_ophdr 
{
    unsigned char opcode : 4,
    rsv3 : 1,
       rsv2 : 1,
          rsv1 : 1,
             fin : 1;
    unsigned char payload_length : 7,
    mask : 1;

} __attribute__((packed));

struct _nty_websocket_head_126
{
    unsigned short payload_length;
    char mask_key[4];
    unsigned char data[8];
} __attribute__((packed));

struct _nty_websocket_head_127 
{

    unsigned long long payload_length;
    char mask_key[4];

    unsigned char data[8];

} __attribute__((packed));

typedef struct _nty_websocket_head_127 nty_websocket_head_127;
typedef struct _nty_websocket_head_126 nty_websocket_head_126;
typedef struct _nty_ophdr nty_ophdr;


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


int readline(char* allbuf, int level, char* linebuf) 
{
    int len = strlen(allbuf);

    for (; level < len; ++level)    {
        if (allbuf[level] == '\r' && allbuf[level + 1] == '\n')
            return level + 2;
        else
            *(linebuf++) = allbuf[level];
    }

    return -1;
}

int handshark(struct sockitem *si, struct reactor *mainloop) 
{
    char linebuf[256];
    char sec_accept[32];
    int level = 0;
    unsigned char sha1_data[SHA_DIGEST_LENGTH + 1] = { 0 };
    char head[BUFFER_LENGTH] = { 0 };

    do {
        memset(linebuf, 0, sizeof(linebuf));
        level = readline(si->recvbuffer, level, 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);
            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);

            printf("response\n");
            printf("%s\n\n\n", head);
#if 0
            if (write(cli_fd, head, strlen(head)) < 0)     //write ---> send            
                perror("write");
#else
            memset(si->recvbuffer, 0, BUFFER_LENGTH);

            memcpy(si->sendbuffer, head, strlen(head)); // to send 
            si->slength = strlen(head);

            // to set epollout events
            struct epoll_event ev;
            ev.events = EPOLLOUT | EPOLLET;
            //ev.data.fd = clientfd;
            si->sockfd = si->sockfd;
            si->callback = send_cb;
            si->status = WS_DATATRANSFORM;
            ev.data.ptr = si;

            epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);

#endif
            break;
        }

    } while ((si->recvbuffer[level] != '\r' || si->recvbuffer[level + 1] != '\n') && level != -1);

    return 0;
}

int transform(struct sockitem *si, struct reactor *mainloop) 
{
    int ret = 0;
    char mask[4] = { 0 };
    char *data = decode_packet(si->recvbuffer, mask, si->rlength, &ret);


    printf("data : %s , length : %d\n", data, ret);

    ret = encode_packet(si->sendbuffer, mask, data, ret);
    si->slength = ret;

    memset(si->recvbuffer, 0, BUFFER_LENGTH);

    struct epoll_event ev;
    ev.events = EPOLLOUT | EPOLLET;
    //ev.data.fd = clientfd;
    si->sockfd = si->sockfd;
    si->callback = send_cb;
    si->status = WS_DATATRANSFORM;
    ev.data.ptr = si;

    epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);

    return 0;
}

void umask(char *data, int len, char *mask) {
    int i;
    for (i = 0; i < len; i++)
        *(data + i) ^= *(mask + (i % 4));
}

char* decode_packet(char *stream, char *mask, int length, int *ret) 
{
    nty_ophdr *hdr = (nty_ophdr*)stream;
    unsigned char *data = stream + sizeof(nty_ophdr);
    int size = 0;
    int start = 0;
    //char mask[4] = {0};
    int i = 0;

    //if (hdr->fin == 1) return NULL;

    if ((hdr->mask & 0x7F) == 126) {

        nty_websocket_head_126 *hdr126 = (nty_websocket_head_126*)data;
        size = hdr126->payload_length;

        for (i = 0; i < 4; i++) {
            mask[i] = hdr126->mask_key[i];
        }

        start = 8;

    }
    else if ((hdr->mask & 0x7F) == 127) {

        nty_websocket_head_127 *hdr127 = (nty_websocket_head_127*)data;
        size = hdr127->payload_length;

        for (i = 0; i < 4; i++) {
            mask[i] = hdr127->mask_key[i];
        }

        start = 14;

    }
    else {
        size = hdr->payload_length;

        memcpy(mask, data, 4);
        start = 6;
    }

    *ret = size;
    umask(stream + start, size, mask);

    return stream + start;

}

int encode_packet(char *buffer, char *mask, char *stream, int length) 
{
    nty_ophdr head = { 0 };
    head.fin = 1;
    head.opcode = 1;
    int size = 0;

    if (length < 126) 
    {
        head.payload_length = length;
        memcpy(buffer, &head, sizeof(nty_ophdr));
        size = 2;
    }
    else if (length < 0xffff) 
    {
        nty_websocket_head_126 hdr = { 0 };
        hdr.payload_length = length;
        memcpy(hdr.mask_key, mask, 4);

        memcpy(buffer, &head, sizeof(nty_ophdr));
        memcpy(buffer + sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_126));
        size = sizeof(nty_websocket_head_126);

    }
    else 
    {
        nty_websocket_head_127 hdr = { 0 };
        hdr.payload_length = length;
        memcpy(hdr.mask_key, mask, 4);

        memcpy(buffer, &head, sizeof(nty_ophdr));
        memcpy(buffer + sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_127));

        size = sizeof(nty_websocket_head_127);
    }

    memcpy(buffer + 2, stream, length);

    return length + 2;
}


#endif 



static int set_nonblock(int fd) 
{
    int flags;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) return flags;
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0) return -1;
    return 0;
}


int send_cb(int fd, int events, void *arg) 
{
    struct sockitem *si = (struct sockitem*)arg;

    send(fd, si->sendbuffer, si->slength, 0); //

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    //ev.data.fd = clientfd;
    si->sockfd = fd;
    si->callback = recv_cb;
    ev.data.ptr = si;

    memset(si->sendbuffer, 0, BUFFER_LENGTH);

    epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);
}

//  ./epoll 8080

int recv_cb(int fd, int events, void *arg) 
{
    //int clientfd = events[i].data.fd;
    struct sockitem *si = (struct sockitem*)arg;
    struct epoll_event ev;

    int ret = recv(fd, si->recvbuffer, BUFFER_LENGTH, 0);
    if (ret < 0) 
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
        { 
            return -1;
        }
        else 
        {

        }

        ev.events = EPOLLIN;
        //ev.data.fd = fd;
        epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
        close(fd);
        free(si);


    }
    else if (ret == 0) 
    { 
        printf("disconnect %d\n", fd);
        ev.events = EPOLLIN;
        //ev.data.fd = fd;
        epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
        close(fd);
        free(si);

    }
    else 
    {
        //printf("Recv: %s, %d Bytes\n", si->recvbuffer, ret);
        si->rlength = 0;

        if (si->status == WS_HANDSHARK) {
            printf("request\n");
            printf("%s\n", si->recvbuffer);

            handshark(si, eventloop);
        }
        else if (si->status == WS_DATATRANSFORM) {
            transform(si, eventloop);
        }
        else if (si->status == WS_DATAEND) {

        }

    }

}

int accept_cb(int fd, int events, void *arg) 
{
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(struct sockaddr_in));
    socklen_t client_len = sizeof(client_addr);

    int clientfd = accept(fd, (struct sockaddr*)&client_addr, &client_len);
    if (clientfd <= 0) return -1;

    set_nonblock(clientfd);

    char str[INET_ADDRSTRLEN] = { 0 };
    printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
        ntohs(client_addr.sin_port));

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    //ev.data.fd = clientfd;

    struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
    si->sockfd = clientfd;
    si->callback = recv_cb;
    si->status = WS_HANDSHARK;
    ev.data.ptr = si;

    epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev);

    return clientfd;
}

int main(int argc, char *argv[]) 
{
    if (argc < 2) 
    {
        return -1;
    }

    int port = atoi(argv[1]);



    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
    {
        return -1;
    }

    set_nonblock(sockfd);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) 
    {
        return -2;
    }

    if (listen(sockfd, 5) < 0) 
    {
        return -3;
    }

    eventloop = (struct reactor*)malloc(sizeof(struct reactor));
    // epoll opera

    eventloop->epfd = epoll_create(1);

    struct epoll_event ev;
    ev.events = EPOLLIN;

    struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
    si->sockfd = sockfd;
    si->callback = accept_cb;
    ev.data.ptr = si;

    epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);

    while (1) 
    {
        int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1);
        if (nready < -1) 
        {
            break;
        }

        int i = 0;
        for (i = 0; i < nready; i++) 
        {
            if (eventloop->events[i].events & EPOLLIN) 
            {
                //printf("sockitem\n");
                struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;
                si->callback(si->sockfd, eventloop->events[i].events, si);
            }

            if (eventloop->events[i].events & EPOLLOUT) 
            {
                struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;
                si->callback(si->sockfd, eventloop->events[i].events, si);
            }
        }

    }

}
View Code

 

posted @ 2021-03-12 22:54  zwj鹿港小镇  阅读(55)  评论(0)    收藏  举报