博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebSocket的C++服务器端实现
阅读量:6434 次
发布时间:2019-06-23

本文共 5999 字,大约阅读时间需要 19 分钟。

由于需要在项目中增加Websocket协议,与客户端进行通信,不想使用开源的库,比如WebSocketPP,就自己根据WebSocket协议实现一套函数,完全使用C++实现。

代码已经实现,放在个人github上面,地址:https://github.com/jice1001/websocket.git。下面进行解释说明:

一、原理

  Websocket协议解析,已经在前面博客里面详细讲解过,可以参考博客http://www.cnblogs.com/jice1990/p/5435419.html,这里就不详细细说。

服务器端实现就是使用TCP协议,使用传统的socket流程进行绑定监听,使用epoll控制多路并发,收到Websocket握手包时候进行握手处理,握手成功便可进行数据收发。

二、实现

  1、服务器监听

  该部分使用的是TCP socket流程,首先是通过socket函数建立socket,通过bind函数绑定到某个端口,本例使用的是9000,然后通过listen函数开启监听,代码如下:

listenfd_ = socket(AF_INET, SOCK_STREAM, 0);    if(listenfd_ == -1){        DEBUG_LOG("创建套接字失败!");        return -1;    }    struct sockaddr_in server_addr;    memset(&server_addr, 0, sizeof(sockaddr_in));    server_addr.sin_family = AF_INET;    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);    server_addr.sin_port = htons(PORT);    if(-1 == bind(listenfd_, (struct sockaddr *)(&server_addr), sizeof(server_addr))){        DEBUG_LOG("绑定套接字失败!");        return -1;    }    if(-1 == listen(listenfd_, 5)){        DEBUG_LOG("监听失败!");        return -1;    }

  2、epoll控制多路并发

  该部分使用的是epoll流程,首先在初始化时候使用epoll_create创建epoll句柄

epollfd_ = epoll_create(1024);

  然后通过epoll_wait等待fd事件来临,当监听到是listenfd事件时候,说明是客户端连接服务器,就使用accept接受连接,然后注册该连接EPOLLIN事件,当epoll监听到EPOLLIN事件时候,即可进行握手和数据读取。代码如下:

void ctl_event(int fd, bool flag){    struct epoll_event ev;    ev.data.fd = fd;    ev.events = flag ? EPOLLIN : 0;    epoll_ctl(epollfd_, flag ? EPOLL_CTL_ADD : EPOLL_CTL_DEL, fd, &ev);    if(flag){        set_noblock(fd);        websocket_handler_map_[fd] = new Websocket_Handler(fd);        if(fd != listenfd_)            DEBUG_LOG("fd: %d 加入epoll循环", fd);    }    else{        close(fd);        delete websocket_handler_map_[fd];        websocket_handler_map_.erase(fd);        DEBUG_LOG("fd: %d 退出epoll循环", fd);    }}
int epoll_loop(){    struct sockaddr_in client_addr;    socklen_t clilen;    int nfds = 0;    int fd = 0;    int bufflen = 0;    struct epoll_event events[MAXEVENTSSIZE];    while(true){        nfds = epoll_wait(epollfd_, events, MAXEVENTSSIZE, TIMEWAIT);        for(int i = 0; i < nfds; i++){            if(events[i].data.fd == listenfd_){                fd = accept(listenfd_, (struct sockaddr *)&client_addr, &clilen);                ctl_event(fd, true);            }            else if(events[i].events & EPOLLIN){                if((fd = events[i].data.fd) < 0)                    continue;                Websocket_Handler *handler = websocket_handler_map_[fd];                if(handler == NULL)                    continue;                if((bufflen = read(fd, handler->getbuff(), BUFFLEN)) <= 0){                    ctl_event(fd, false);                }                else{                    handler->process();                }            }        }    }    return 0;}

  3、Websocket握手连接

  握手部分主要是根据Websocket握手包进行解析,然后根据Sec-WebSocket-Key进行SHA1哈希,生成相应的key,返回给客户端,与客户端进行握手。代码如下:

//该函数是获取websocket握手包的信息,按照分割字符进行解析 int fetch_http_info(){    std::istringstream s(buff_);    std::string request;    std::getline(s, request);    if (request[request.size()-1] == '\r') {        request.erase(request.end()-1);    } else {        return -1;    }    std::string header;    std::string::size_type end;    while (std::getline(s, header) && header != "\r") {        if (header[header.size()-1] != '\r') {            continue; //end        } else {            header.erase(header.end()-1);    //remove last char        }        end = header.find(": ",0);        if (end != std::string::npos) {            std::string key = header.substr(0,end);            std::string value = header.substr(end+2);            header_map_[key] = value;        }    }    return 0;}
//该函数是根据websocket返回包的格式拼接相应的返回包 void parse_str(char *request){      strcat(request, "HTTP/1.1 101 Switching Protocols\r\n");    strcat(request, "Connection: upgrade\r\n");    strcat(request, "Sec-WebSocket-Accept: ");    std::string server_key = header_map_["Sec-WebSocket-Key"];    server_key += MAGIC_KEY;    SHA1 sha;    unsigned int message_digest[5];    sha.Reset();    sha << server_key.c_str();    sha.Result(message_digest);    for (int i = 0; i < 5; i++) {        message_digest[i] = htonl(message_digest[i]);    }    server_key = base64_encode(reinterpret_cast
(message_digest),20); server_key += "\r\n"; strcat(request, server_key.c_str()); strcat(request, "Upgrade: websocket\r\n\r\n");}

  4、数据读取

  当服务器与客户端握手成功后,就可以进行正常的通信,读取数据了。使用的是TCP协议的方法,解析Websocket包根据协议格式,在前面博客里面有详细分析,这里只把实现代码贴出来。

int fetch_websocket_info(char *msg){    int pos = 0;    fetch_fin(msg, pos);    fetch_opcode(msg, pos);    fetch_mask(msg, pos);    fetch_payload_length(msg, pos);    fetch_masking_key(msg, pos);    return fetch_payload(msg, pos);}int fetch_fin(char *msg, int &pos){    fin_ = (unsigned char)msg[pos] >> 7;    return 0;}int fetch_opcode(char *msg, int &pos){    opcode_ = msg[pos] & 0x0f;    pos++;    return 0;}int fetch_mask(char *msg, int &pos){    mask_ = (unsigned char)msg[pos] >> 7;    return 0;}int fetch_masking_key(char *msg, int &pos){    if(mask_ != 1)        return 0;    for(int i = 0; i < 4; i++)        masking_key_[i] = msg[pos + i];    pos += 4;    return 0;}int fetch_payload_length(char *msg, int &pos){    payload_length_ = msg[pos] & 0x7f;    pos++;    if(payload_length_ == 126){        uint16_t length = 0;        memcpy(&length, msg + pos, 2);        pos += 2;        payload_length_ = ntohs(length);    }    else if(payload_length_ == 127){        uint32_t length = 0;        memcpy(&length, msg + pos, 4);        pos += 4;        payload_length_ = ntohl(length);    }    return 0;}int fetch_payload(char *msg, int &pos){    memset(payload_, 0, sizeof(payload_));    if(mask_ != 1){        memcpy(payload_, msg + pos, payload_length_);    }    else {        for(uint i = 0; i < payload_length_; i++){            int j = i % 4;            payload_[i] = msg[pos + i] ^ masking_key_[j];        }    }    pos += payload_length_;    return 0;}

  5、总结

  到此为止,完整实现了使用C++对Websocket协议进行解析,握手,数据收发,不借助开源库就实现了websocket相关功能,最大程度的与项目保存兼容。

你可能感兴趣的文章
教你彻底学会c语言基础——文件操作
查看>>
如何使用免费控件将Word表格中的数据导入到Excel中
查看>>
seafile服务器配置
查看>>
HyperLedger Fabric 1.2 区块链应用场景(3.1)
查看>>
也谈谈初创公司的技术团队建设
查看>>
阿里云 APM 解决方案地图
查看>>
中国HBase技术社区第一届MeetUp-HBase2.0研讨圆桌会
查看>>
学渣的模块化之路——50行代码带你手写一个common.js规范
查看>>
python——变量
查看>>
subline上装node.js插件
查看>>
python字符串操作实方法大合集
查看>>
Linux学习(十一):不可忽略的Linux支持的文件系统
查看>>
[转]VC++中操作XML(MFC、SDK)
查看>>
WiFi连接风险造成个人信息外泄 网络安全需加强
查看>>
2017(中国)商博会系列介绍之智能生活展
查看>>
eclipse link方式安装 sts(Spring Tool Suite)
查看>>
数据结构思维 第三章 `ArrayList`
查看>>
CentOS6、7编译安装FFmpeg
查看>>
Android项目实战(二十九):酒店预定日期选择
查看>>
PHP IDE phpstorm 常用快捷键
查看>>