2.1.4websocket协议与服务器实现

前言

本文基于websocket协议,在2.1.3http/https服务器的实现中TCP服务器代码的基础上实现websocket服务器。

websocket

websocket的主要应用场景

  • 服务器主动推送数据给浏览器

websocket握手流程(在tcp三次握手之后)

  • 浏览器发送请求
  • 服务器接收请求并获取Sec-WebSocket-Key的值并进行相应的计算得到Sec-WebSocket-Accept的值,然后返回固定格式的信息给浏览器
  • 浏览器接收信息并将Sec-WebSocket-Accept值与浏览器自己计算的值进行比较,若相同则握手成功

服务器接收到的浏览器的websocket请求为:

GET / HTTP/1.1
Host: 192.168.210.132:9999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: null
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: GFjXgXWdcYvUfA/wMx/kig==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

服务器返回给浏览器的信息为:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: value

其中value需通过以下计算得到:

// 请求中的Sec-WebSocket-Key
key = GFjXgXWdcYvUfA/wMx/kig==
// websocket协议定义的GUID
GUID = 258EAFA5-E914-47DA-95CA-C5AB0DC85B11  
// 将上面两段字符合并
str =  GFjXgXWdcYvUfA/wMx/kig==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 
// 进行SHA-1加密算法处理
sha = SHA-1(str)
// 进行base64编码
value = base64-encoded(sha)

握手信息为字符格式,而传输的数据为二进制格式,数据的格式如下图:

在这里插入图片描述

基于websocket协议的服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

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

#define BUFFER_LENGTH       1024
#define MAX_EPOLL_EVENTS    1024

#define SERVER_PORT         9999
#define PORT_COUNT          1

typedef int NCALLBACK(int, int, void*);

// --- websocket
#define GUID                "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

#define ACCEPT_KEY_LENGTH   64

enum  // 状态机
{
    WS_END = 0,  // wsstatus初始值为WS_END,故 WS_END = 0
    WS_HANDSHARK = 1,
    WS_TRANSMISSION = 2,
    WS_COUNT
};

// websocket数据头
typedef struct ws_ophdr
{
    // 小端
    unsigned char opcode:4,
                  rsv3:1,
                  rsv2:1,
                  rsv1:1,
                  fin:1;

    unsigned char pl_len:7,
                  mask:1;
}ws_ophdr;
// ---

typedef struct ntyevent
{
    int fd;
    int events;  // EPOLLIN, EPOLLOUT等
    void* arg;
    int (*callback)(int fd, int events, void* arg);

    int status;  // 0代表fd未被epoll监听,1代表fd已被epoll监听
    char rbuffer[BUFFER_LENGTH];
    char wbuffer[BUFFER_LENGTH];

    int rlength;
    int wlength;
    // long last_active;

    char sec_accept[ACCEPT_KEY_LENGTH];

    int wsstatus;  // 0, 1, 2, 3

}ntyevent;

typedef struct eventblock
{
    struct ntyevent* events;
    struct eventblock* next;
}eventblock;

// 反应堆,监听事件,管理fd相关内容(查找fd对应的ntyevent等)
typedef struct ntyreactor
{
    int epfd;
    int blkcnt;

    struct eventblock* evblks;
}ntyreactor;

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

// --- ntyevent相关操作
// 设置ntyevent
void nty_event_set(ntyevent* ev, int fd, NCALLBACK callback, void* arg)
{
    ev->fd = fd;
    ev->callback = callback;
    ev->events = 0;
    ev->arg = arg;
    // ev->last_active = time(NULL);

    return;
}

// 将ntyevent对应fd及事件添加/修改到epoll监听列表
int nty_event_add(int epfd, int events, ntyevent* ev)
{
    struct epoll_event ep_ev = {0, {0}};
    ep_ev.data.ptr = ev;
    ep_ev.events = ev->events = events;

    int op;
    // 判断fd是否已经被epoll监听
    // 若是,则修改该fd被监听的事件,否则,将该fd添加到epoll监听列表
    if(ev->status == 1)
    {
        op = EPOLL_CTL_MOD;
    }
    else
    {
        op = EPOLL_CTL_ADD;
        ev->status = 1;
    }

    if(epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0)
    {
        printf("event add failed [fd=%d], events[%d]\n",ev->fd, events);
        return -1;
    }

    return 0;
}

// 将ntyevent对应fd及事件从epoll监听列表中删除
int nty_event_del(int epfd, ntyevent* ev)
{
    struct epoll_event ep_ev = {0, {0}};

    // 若fd未被添加到epoll监听列表
    if(ev->status != 1)
    {
        return -1;
    }

    ep_ev.data.ptr = ev;
    ev->status = 0;
    epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev);

    return 0;
}
// ---

// --- ntyreactor相关操作
// 初始化ntyreactor
int ntyreactor_init(ntyreactor* reactor)
{
    if(!reactor) return -1;
    memset(reactor, 0, sizeof(ntyreactor));

    reactor->epfd = epoll_create(1);
    if(reactor->epfd <= 0)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        return -2;
    }

    ntyevent* evs = (ntyevent*)malloc(MAX_EPOLL_EVENTS*sizeof(ntyevent));
    if(!evs)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        close(reactor->epfd);
        return -3;
    }
    memset(evs, 0, (MAX_EPOLL_EVENTS)*sizeof(ntyevent));

    eventblock* block = (eventblock*)malloc(sizeof(eventblock));
    if(!block)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        free(evs);
        close(reactor->epfd);
        return -3;
    }

    block->events = evs;
    block->next = NULL;

    reactor->evblks = block;
    reactor->blkcnt = 1;

    return 0;
}

// 析构ntyreactor
int ntyreactor_destory(ntyreactor* reactor)
{
    close(reactor->epfd);

    eventblock* blk = reactor->evblks;
    eventblock* blk_next;
    while(blk)
    {
        blk_next = blk->next;

        free(blk->events);
        free(blk);

        blk = blk_next;
    }

    return 0;
}

// 为ntyreactor的链表(evblks)创建一个新结点,并分配内存
int ntyreactor_alloc(ntyreactor* reactor)
{
    if(!reactor) return -1;
    if(!reactor->evblks) return -1;

    eventblock* blk = reactor->evblks;
    while(blk->next)
        blk = blk->next;

    ntyevent* evs = (ntyevent*)malloc((MAX_EPOLL_EVENTS)*sizeof(ntyevent));
    if(!evs)
    {
        printf("%s ntyevent failed\n", __func__);
        return -2;
    }
    memset(evs, 0, (MAX_EPOLL_EVENTS)*sizeof(ntyevent));

    eventblock* block = (eventblock*)malloc(sizeof(eventblock));
    if(!block)
    {
        printf("%s eventblock failed\n", __func__);
        free(evs);
        return -3;
    }

    block->events = evs;
    block->next = NULL;

    blk->next = block;
    ++(reactor->blkcnt);

    return 0;
}

// 在ntyreactor中查找sockfd对应的ntyevent
ntyevent* ntyreactor_idx(ntyreactor* reactor, int sockfd)
{
    if(!reactor) return NULL;
    if(!reactor->evblks) return NULL;

    int blkidx = sockfd / MAX_EPOLL_EVENTS;
    while(blkidx >= reactor->blkcnt)
    {
        ntyreactor_alloc(reactor);
    }

    int i = 0;
    eventblock* blk = reactor->evblks;
    while(i++ != blkidx && blk)
    {
        blk = blk->next;
    }

    return &blk->events[sockfd % MAX_EPOLL_EVENTS];
}

// 向ntyreactor添加listenfd
int ntyreactor_addlistener(ntyreactor* reactor, int sockfd, NCALLBACK* acceptor)
{
    if(!reactor) return -1;
    if(!reactor->evblks) return -1;

    ntyevent* event = ntyreactor_idx(reactor, sockfd);
    if(!event) return -1;

    nty_event_set(event, sockfd, acceptor, reactor);
    nty_event_add(reactor->epfd, EPOLLIN, event);

    return 0;
}

// 运行ntyreactor,即循环执行epoll_wait,对可读可写IO执行相应回调函数
int ntyreactor_run(ntyreactor* reactor)
{
    if(!reactor) return -1;
    if(reactor->epfd < 0) return -1;
    if(!reactor->evblks) return -1;

    struct epoll_event events[MAX_EPOLL_EVENTS+1];

    int checkpos = 0, i;
    
    while(1)
    {
        int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, 1000);
        if(nready < 0)
        {
            printf("epoll_wait error, exit\n");
            continue;
        }

        for(i = 0;i < nready; i++)
        {
            ntyevent* ev = (ntyevent*)events[i].data.ptr;

            if((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))    // 若IO可输入
            {
                ev->callback(ev->fd, events[i].events, ev->arg);
            }
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))  // 若IO可输出
            {
                ev->callback(ev->fd, events[i].events, ev->arg);
            }
        }
    }
}
// ---

// --- websocket
// 读取一行
int readline(char* allbuf, int idx, char* linebuf)
{
    int len = strlen(allbuf);
    for(;idx < len;idx++)
    {
        if(allbuf[idx] == '\r' && allbuf[idx+1] == '\n')
            return idx + 2;
        else
            *(linebuf++) = allbuf[idx]; 
    }

    return -1;
}

// 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] = '\0';    
	size = bptr->length;    

	BIO_free_all(bio);    
	return size;
}

// websocket握手
int ws_handshark(ntyevent* ev)
{
    int idx = 0;
    char sec_data[128] = {0};
    char sec_accept[128] = {0};

    do
    {
        char linebuf[BUFFER_LENGTH] = {0};
        idx = readline(ev->rbuffer, idx, linebuf);

        if(strstr(linebuf, "Sec-WebSocket-Key"))  // 获取Sec-WebSocket-Key
        {
            strcat(linebuf, GUID);

            SHA1(linebuf+strlen("Sec-WebSocket-Key: "), strlen(linebuf+strlen("Sec-WebSocket-Key: ")), sec_data);

            base64_encode(sec_data, strlen(sec_data), sec_accept);

            printf("idx: %d, line: %s\n", idx, sec_accept);

            memcpy(ev->sec_accept, sec_accept, ACCEPT_KEY_LENGTH);

            return 1;
        }
    }
    while((ev->rbuffer[idx] != '\r' || ev->rbuffer[idx+1] != '\n') && idx != -1);

    return 0;
}

// 将密文转为明文
void unmask(char* payload, int length, char* mask_key)
{
    int i = 0;

    for(i = 0;i < length;i++)
    {
        payload[i] ^= mask_key[i % 4];
    }
}

// 
int ws_transmission(ntyevent* ev)
{
    ws_ophdr* hdr = (ws_ophdr*)ev->rbuffer;

    printf("%s\n", __func__);

    if(hdr->pl_len < 126)
    {
        unsigned char* payload = NULL;
        if(hdr->mask)  // 有Masking-key(4 bytes),为密文
        {
            payload = ev->rbuffer + 6;
            unmask(payload, hdr->pl_len, ev->rbuffer + 2);
        }
        else  // 无Masking-key(0 bytes),为明文
        {
            payload = ev->rbuffer + 2;
        }

        printf("payload: %s\n", payload);

        return 1;
    }
    else if(hdr->pl_len == 126)
    {

    }
    else if(hdr->pl_len == 127)
    {

    }
    else
    {
        return 0;
        //assert(0);
    }
}

// 解析websocket请求
int ws_request(ntyevent* ev)
{
    if(ev->wsstatus == WS_HANDSHARK)
    {
        if(ws_handshark(ev))
            ev->wsstatus = WS_TRANSMISSION;
    }
    else if(ev->wsstatus == WS_TRANSMISSION)
    {
        if(ws_transmission(ev))
            ev->wsstatus = WS_END;
    }
}

// 响应websocket请求
int ws_response(ntyevent* ev)
{
    ev->wlength = sprintf(ev->wbuffer, "HTTP/1.1 101 Switching Protocols\r\n"
                                       "Upgrade: websocket\r\n"
                                       "Connection: Upgrade\r\n"
                                       "Sec-WebSocket-Accept: %s\r\n\r\n",
                                       ev->sec_accept);

    return ev->wlength;
}
// ---

// --- 回调函数
// recv回调函数
int recv_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    ntyevent* ev = ntyreactor_idx(reactor, fd);

    if(!ev) return -1;

    int len = recv(fd, ev->rbuffer, BUFFER_LENGTH, 0);
    nty_event_del(reactor->epfd, ev);

    if(len > 0)  // 已接收数据
    {
        ev->rlength = len;
        ev->rbuffer[len] = '\0';  // 去除脏数据

        //printf("recv [%d]:%s\n", fd, ev->rbuffer);

        // --- websocket
        printf("wsstatus: %d\n", ev->wsstatus);
        if(ev->wsstatus == WS_END)
        {
            ev->wsstatus = WS_HANDSHARK;
        }
        printf("request\n");
        ws_request(ev);  // 解析websocket请求
        // ---

        // 接收数据后,将fd设置为待发送模式
        nty_event_set(ev, fd, send_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLOUT, ev);
    }
    else if(len == 0)  // 客户端断开连接
    {
        nty_event_del(reactor->epfd, ev);
        printf("recv_cb --> disconnect\n");
        close(ev->fd);
    }
    else  // 发生异常
    {
        if(errno == EAGAIN && errno == EWOULDBLOCK)
        {

        }
        else if(errno == ECONNRESET)
        {
            nty_event_del(reactor->epfd, ev);
            close(ev->fd);
        }
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
        // strerror() 获取指向错误消息字符串的指针
    }

    return len;
}

// send回调函数
int send_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    ntyevent* ev = ntyreactor_idx(reactor, fd);

    if(!ev) return -1;

    // --- websocket
    ws_response(ev);  // 响应websocket请求
    // ---

    int len = send(fd, ev->wbuffer, ev->wlength, 0);
    if(len > 0)  // 已发送数据
    {
        //printf("send[fd=%d], [%d]%s\n",fd, len, ev->wbuffer);
        
        // 发送数据后,将fd设置为待接收模式
        nty_event_del(reactor->epfd, ev);
        nty_event_set(ev, fd, recv_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLIN, ev);
    }
    else  // 发送失败
    {
        nty_event_del(reactor->epfd, ev);
        close(ev->fd);

        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }

    return len;
}

// accept回调函数
int accept_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    if(!reactor) return -1;

    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);

    int clientfd = 0;

    if((clientfd = accept(fd, (struct sockaddr*)&client_addr, &len)) == -1)
    {
        if(errno != EAGAIN && errno != EINTR)
        {

        }
        printf("accept[fd=%d] error %s\n", fd, strerror(errno));
        return -1;
    }

    // 将clientfd设置为非阻塞
    int flag = 0;
    if((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0)
    {
        printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
        return -1;
    }

    ntyevent* event = ntyreactor_idx(reactor, clientfd);

    if(!event) return -1;

    // 初始化客户端fd为接收模式
	nty_event_set(event, clientfd, recv_cb, reactor);
	nty_event_add(reactor->epfd, EPOLLIN, event);

    printf("new connect [%s:%d], pos[%d]\n",
        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd);

    return 0;
}
// ---

// 创建服务器端口(listenfd)
int init_sock(short port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    if(listen(fd, 20) < 0)
    {
        printf("listen failed : %s\n", strerror(errno));
        return -1;
    }

    printf("listen server port : %d\n", port);
    return fd;
}

int main(int argc, char* argv[])
{
    ntyreactor* reactor = (ntyreactor*)malloc(sizeof(ntyreactor));
    ntyreactor_init(reactor);

    unsigned short port = SERVER_PORT;
    if(argc == 2)
    {
        port = atoi(argv[1]);
    }

    int i = 0;
    int sockfds[PORT_COUNT] = {0};

    for(i = 0; i < PORT_COUNT; i++)
    {
        sockfds[i] = init_sock(port + i);
        ntyreactor_addlistener(reactor, sockfds[i], accept_cb);
    }

    ntyreactor_run(reactor);

    ntyreactor_destory(reactor);

    for(i = 0; i < PORT_COUNT; i++)
    {
        close(sockfds[i]);
    }
    free(reactor);

    return 0;
}

使用SHA-1加密算法及实现base64编码需安装openssl库

sudo apt-get install libssl-dev

编译时需链接动态库ssl及crypto

gcc -o websocket websocket.c -lssl -lcrypto

参考博客:

C语言 | 位域的使用详解

弄懂大端小端含义

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值