WebSocket的C++服務器端實現


由於需要在項目中增加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<const unsigned char*>(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相關功能,最大程度的與項目保存兼容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM