WebSocket的C++服务器端实现

本文介绍了如何从头使用C++实现WebSocket协议,包括服务器监听、epoll控制多路并发、WebSocket握手连接和数据收发等关键步骤。作者分享了自己的GitHub代码,提供了一个不依赖开源库的WebSocket服务器实现方案。
摘要由CSDN通过智能技术生成

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

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

一、原理

Websocket协议解析,已经在前面博客里面详细讲解过,可以参考博客,这里就不详细细说。

服务器端实现就是使用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、发送数据

//发送数据
void Websocket_Handler::send_content_data(char *charb){
	char buf[1024] = "";
	int first = 0x00;
	int tmp = 0;
	int length = 0;
	length = strlen(charb);
	if (true) {
		first = first + 0x80;
		first = first + 0x1;
	}
	buf[0] = first;
	tmp = 1;
	unsigned int nuNum = (unsigned)length;
	if (length < 126) {
		buf[1] = length;
		tmp = 2;
	}else if (length < 65536) {
		buf[1] = 126;
		buf[2] = nuNum >> 8;
		buf[3] = length & 0xFF;
		tmp = 4;
	}else {
		//数据长度超过65536
		buf[1] = 127;
		buf[2] = 0;
		buf[3] = 0;
		buf[4] = 0;
		buf[5] = 0;
		buf[6] = nuNum >> 24;
		buf[7] = nuNum >> 16;
		buf[8] = nuNum >> 8;
		buf[9] = nuNum & 0xFF;
		tmp = 10;
	}
	
	for (int i = 0; i < length; i++)
	{
		buf[tmp+i]= charb[i];
		printf("要发送的数据字节:%d\n", charb[i]);
	}
	
	char charbuf[1024] = "";
	memcpy(charbuf, buf, length + tmp);
	send_data(charbuf);
}

6、总结

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

要在Linux C++实现WebSocket服务器端转发行情数据,可以使用以下步骤: 1. 使用第三方C++库,如libwebsockets或uWebSockets等,来实现WebSocket服务器端。 2. 在服务器端编写代码,以便将行情数据发送到WebSocket客户端。可以使用网络库,如boost::asio或ACE等,来管理网络连接和数据传输。 3. 在WebSocket服务器端中,使用JSON格式将行情数据打包并发送给客户端。可以使用第三方JSON库,如RapidJSON或nlohmann/json等。 4. 为了便于管理和扩展,可以将WebSocket服务器端和行情数据转发逻辑分离到不同的模块中。 下面是一个简单的示例代码,展示如何使用libwebsockets库实现WebSocket服务器端转发行情数据: ```c++ #include <libwebsockets.h> #include <iostream> #include <string> #include <vector> using namespace std; // 行情数据结构体 struct Quote { string symbol; double price; }; // 行情数据源 class QuoteSource { public: void addQuote(const Quote& quote) { quotes.push_back(quote); } const vector<Quote>& getQuotes() const { return quotes; } private: vector<Quote> quotes; }; // WebSocket服务器端 class WebSocketServer { public: WebSocketServer(int port) : port(port), context(NULL), listen_socket(NULL), quote_source(NULL) {} ~WebSocketServer() { if (listen_socket) { libwebsocket_close_and_free_session(context, listen_socket, LWS_CLOSE_STATUS_GOINGAWAY); } if (context) { libwebsocket_context_destroy(context); } } void setQuoteSource(QuoteSource* qs) { quote_source = qs; } bool start() { // 创建WebSocket协议上下文 struct libwebsocket_protocols protocols[] = { { "http-only", callback_http, 0 }, { "quote", callback_quote, sizeof(struct Quote) }, { NULL, NULL, 0 } }; struct libwebsocket_context_creation_info info = { .port = port, .protocols = protocols, .gid = -1, .uid = -1 }; context = libwebsocket_create_context(&info); if (!context) { cerr << "Failed to create WebSocket context" << endl; return false; } // 监听WebSocket连接 listen_socket = libwebsocket_create_server(context, 0, protocols, NULL, NULL, NULL, NULL, -1, -1); if (!listen_socket) { cerr << "Failed to create WebSocket server" << endl; libwebsocket_context_destroy(context); return false; } cout << "WebSocket server started on port " << port << endl; return true; } private: // HTTP回调函数 static int callback_http(struct libwebsocket_context* context, struct libwebsocket* wsi, enum libwebsocket_callback_reasons reason, void* user, void* in, size_t len) { return 0; } // 行情数据回调函数 static int callback_quote(struct libwebsocket_context* context, struct libwebsocket* wsi, enum libwebsocket_callback_reasons reason, void* user, void* in, size_t len) { switch (reason) { case LWS_CALLBACK_ESTABLISHED: // 新客户端连接 cout << "WebSocket client connected" << endl; break; case LWS_CALLBACK_SERVER_WRITEABLE: // 发送行情数据到客户端 if (quote_source) { const vector<Quote>& quotes = quote_source->getQuotes(); for (size_t i = 0; i < quotes.size(); ++i) { libwebsocket_write(wsi, (unsigned char*)&quotes[i], sizeof(Quote), LWS_WRITE_BINARY); } } break; case LWS_CALLBACK_CLOSED: // 客户端断开连接 cout << "WebSocket client disconnected" << endl; break; default: break; } return 0; } private: int port; struct libwebsocket_context* context; struct libwebsocket* listen_socket; QuoteSource* quote_source; }; int main() { // 创建WebSocket服务器 WebSocketServer server(8080); // 创建行情数据源 QuoteSource quote_source; // 添加行情数据 quote_source.addQuote({ "AAPL", 123.45 }); quote_source.addQuote({ "GOOGL", 234.56 }); // 设置行情数据源 server.setQuoteSource(&quote_source); // 启动WebSocket服务器 if (!server.start()) { return 1; } // 运行事件循环 while (true) { libwebsocket_service(server.getContext(), 50); } return 0; } ``` 在上面的示例代码中,我们使用libwebsockets库创建了一个WebSocket服务器,并且在服务器端实现了一个行情数据源。在行情数据源中,我们添加了两个行情数据(AAPL和GOOGL),然后将行情数据源设置为WebSocket服务器的属性。在回调函数中,我们使用libwebsocket_write函数将行情数据发送到WebSocket客户端。 当我们运行这个示例程序时,它将启动一个WebSocket服务器,监听8080端口。如果我们使用浏览器或其他WebSocket客户端连接到这个服务器,它将发送行情数据到客户端。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值