C++无依赖库的websocket实现

1. 简单websocket 客户端实现

参考源码:

见:

GitHub - hank4187yan/light-websocket-client: 非常轻量级的websocket客户端,无第三方依赖库非常轻量级的websocket客户端,无第三方依赖库. Contribute to hank4187yan/light-websocket-client development by creating an account on GitHub.https://github.com/hank4187yan/light-websocket-client

GitHub - dhbaird/easywsclient: A short and sweet WebSocket client for C++A short and sweet WebSocket client for C++. Contribute to dhbaird/easywsclient development by creating an account on GitHub.https://github.com/dhbaird/easywsclient

1.1 核心源码解析

1.1.1 创建客户端对象并连接服务端

CWebsocket* ws_cli = new CWebsocket("ws://localhost:20600/ar/9999", true);

CWebsocket::CWebsocket(const string& url, bool use_mask) {

m_url = url;

sscanf(url.c_str(), "ws://%[^:/]:%d/%s", m_host, &m_port, m_path);

}

ret = ws_cli->connect_hostname();

int CWebsocket::connect_hostname() {

struct addrinfo hints;

struct addrinfo* result;

struct addrinfo* p;

int ret = 0;

std::string origin;

m_sock_fd = INVALID_SOCKET;

char sport[16];

memset(&hints, 0, sizeof(hints));

hints.ai_family = AF_UNSPEC;

hints.ai_socktype = SOCK_STREAM;

snprintf(sport, 16, "%d", m_port);

ret = getaddrinfo(m_host, sport, &hints, &result);

if (ret != 0) {

LIGHTWS_LOG("[ERR]Failed to getaddrinfo:%s\n", gai_strerror(ret));

return LIGHTWS_ERR_CONNECT;

}

for (p = result; p != NULL; p = p->ai_next) {

m_sock_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);

if (m_sock_fd == INVALID_SOCKET) {

continue;

}

ret = connect(m_sock_fd, p->ai_addr, p->ai_addrlen);

if (ret != SOCKET_ERROR) {

break;

}

closesocket(m_sock_fd);

m_sock_fd = INVALID_SOCKET;

}

freeaddrinfo(result);

if (m_sock_fd == INVALID_SOCKET) {

LIGHTWS_LOG("[ERR]Unable to connect to %s:%d\n", m_host, m_port);

return LIGHTWS_ERR_CONNECT;

}

}

1.1.2 和服务端进行websocket握手

// Websocket handshake

{

char line[1024];

int status;

int i;

snprintf(line, 1024, "GET /%s HTTP/1.1\r\n", m_path);

::send(m_sock_fd, line, strlen(line), 0);

if (m_port == 80) {

snprintf(line, 1024, "Host: %s\r\n", m_host);

::send(m_sock_fd, line, strlen(line), 0);

}

else {

snprintf(line, 1024, "Host: %s:%d\r\n", m_host, m_port);

::send(m_sock_fd, line, strlen(line), 0);

}

snprintf(line, 1024, "Upgrade: websocket\r\n");

::send(m_sock_fd, line, strlen(line), 0);

snprintf(line, 1024, "Connection: Upgrade\r\n");

::send(m_sock_fd, line, strlen(line), 0);

if (!origin.empty()) {

snprintf(line, 1024, "Origin: %s\r\n", origin.c_str());

::send(m_sock_fd, line, strlen(line), 0);

}

snprintf(line, 1024, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n");

::send(m_sock_fd, line, strlen(line), 0);

snprintf(line, 1024, "Sec-WebSocket-Version: 13\r\n");

::send(m_sock_fd, line, strlen(line), 0);

snprintf(line, 1024, "\r\n");

::send(m_sock_fd, line, strlen(line), 0);

for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) {

if (recv(m_sock_fd, line+i, 1, 0) == 0) {

return LIGTHWS_ERR_HANDSHAKE;

}

}

line[i] = 0;

if (i == 1023) {

LIGHTWS_LOG("ERROR: Got invalid status line connecting to: %s\n", m_url.c_str());

return LIGTHWS_ERR_HANDSHAKE;

}

if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) {

LIGHTWS_LOG("ERROR: Got bad status connecting to %s: %s", m_url.c_str(), line);

return LIGTHWS_ERR_HANDSHAKE;

}

// TODO: verify response headers,

while (true) {

for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) {

if (recv(m_sock_fd, line+i, 1, 0) == 0) {

return LIGTHWS_ERR_HANDSHAKE;

}

}

if (line[0] == '\r' && line[1] == '\n') {

break;

}

}

}

1.1.3 对要发送数据进行mask处理

const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };

// TODO: consider acquiring a lock on txbuf...

if (m_ws_state == CLOSING || m_ws_state == CLOSED) {

return LIGHTWS_ERR_CLOSED;

}

std::vector<uint8_t> header;

header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (m_use_mask ? 4 : 0), 0);

header[0] = 0x80 | type;

if (message_size < 126) {

header[1] = (message_size & 0xff) | (m_use_mask ? 0x80 : 0);

if (m_use_mask) {

header[2] = masking_key[0];

header[3] = masking_key[1];

header[4] = masking_key[2];

header[5] = masking_key[3];

}

}

else if (message_size < 65536) {

header[1] = 126 | (m_use_mask ? 0x80 : 0);

header[2] = (message_size >> 8) & 0xff;

header[3] = (message_size >> 0) & 0xff;

if (m_use_mask) {

header[4] = masking_key[0];

header[5] = masking_key[1];

header[6] = masking_key[2];

header[7] = masking_key[3];

}

}

else { // TODO: run coverage testing here

header[1] = 127 | (m_use_mask ? 0x80 : 0);

header[2] = (message_size >> 56) & 0xff;

header[3] = (message_size >> 48) & 0xff;

header[4] = (message_size >> 40) & 0xff;

header[5] = (message_size >> 32) & 0xff;

header[6] = (message_size >> 24) & 0xff;

header[7] = (message_size >> 16) & 0xff;

header[8] = (message_size >> 8) & 0xff;

header[9] = (message_size >> 0) & 0xff;

if (m_use_mask) {

header[10] = masking_key[0];

header[11] = masking_key[1];

header[12] = masking_key[2];

header[13] = masking_key[3];

}

}

// N.B. - txbuf will keep growing until it can be transmitted over the socket:

m_txbuf.insert(m_txbuf.end(), header.begin(), header.end());

m_txbuf.insert(m_txbuf.end(), message_begin, message_end);

if (m_use_mask) {

size_t message_offset = m_txbuf.size() - message_size;

for (size_t i = 0; i != message_size; ++i) {

m_txbuf[message_offset + i] ^= masking_key[i&0x3];

}

}

1.1.4 通过socket发送mask处理后的数据

// 发送request

while (m_txbuf.size()) {

int ret = ::send(m_sock_fd, (char*)&m_txbuf[0], m_txbuf.size(), 0);

if (false) { } //

else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {

break;

}

else if (ret <= 0) {

closesocket(m_sock_fd);

m_ws_state = CLOSED;

fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);

break;

}

else {

m_txbuf.erase(m_txbuf.begin(), m_txbuf.begin() + ret);

}

}

1.1.5 接收response

// 接收服务端的response

while (true) {

// FD_ISSET(0, &rfds) will be true

int N = m_rxbuf.size();

ssize_t ret;

m_rxbuf.resize(N + 1500);

ret = recv(m_sock_fd, (char*)&m_rxbuf[0] + N, 1500, 0);

if (false) { }

else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {

m_rxbuf.resize(N);

break;

}

else if (ret <= 0) {

m_rxbuf.resize(N);

closesocket(m_sock_fd);

m_ws_state = CLOSED;

fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);

break;

}

else {

m_rxbuf.resize(N + ret);

}

}

1.1.6 解析response数据

while (true) {

ws_header_type_t ws;

if (m_rxbuf.size() < 2) {

return LIGHTWS_ERR_DISPATCH; /* Need at least 2 */

}

const uint8_t * data = (uint8_t *) &m_rxbuf[0]; // peek, but don't consume

ws.fin = (data[0] & 0x80) == 0x80;

ws.opcode = (ws_header_type_t::EOpcodeType) (data[0] & 0x0f);

ws.mask = (data[1] & 0x80) == 0x80;

ws.N0 = (data[1] & 0x7f);

ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);

if (m_rxbuf.size() < ws.header_size) {

return LIGHTWS_ERR_DISPATCH; /* Need: ws.header_size - rxbuf.size() */

}

int i = 0;

if (ws.N0 < 126) {

ws.N = ws.N0;

i = 2;

}

else if (ws.N0 == 126) {

ws.N = 0;

ws.N |= ((uint64_t) data[2]) << 8;

ws.N |= ((uint64_t) data[3]) << 0;

i = 4;

}

else if (ws.N0 == 127) {

ws.N = 0;

ws.N |= ((uint64_t) data[2]) << 56;

ws.N |= ((uint64_t) data[3]) << 48;

ws.N |= ((uint64_t) data[4]) << 40;

ws.N |= ((uint64_t) data[5]) << 32;

ws.N |= ((uint64_t) data[6]) << 24;

ws.N |= ((uint64_t) data[7]) << 16;

ws.N |= ((uint64_t) data[8]) << 8;

ws.N |= ((uint64_t) data[9]) << 0;

i = 10;

if (ws.N & 0x8000000000000000ull) {

// https://tools.ietf.org/html/rfc6455 writes the "the most

// significant bit MUST be 0."

//

// We can't drop the frame, because (1) we don't we don't

// know how much data to skip over to find the next header,

// and (2) this would be an impractically long length, even

// if it were valid. So just close() and return immediately

// for now.

m_is_rx_bad = true;

LIGHTWS_LOG("ERROR: Frame has invalid frame length. Closing.\n");

close();

return LIGHTWS_ERR_DISPATCH;

}

}

if (ws.mask) {

ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;

ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;

ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;

ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;

}

else {

ws.masking_key[0] = 0;

ws.masking_key[1] = 0;

ws.masking_key[2] = 0;

ws.masking_key[3] = 0;

}

// Note: The checks above should hopefully ensure this addition

// cannot overflow:

if (m_rxbuf.size() < ws.header_size+ws.N) {

return LIGHTWS_ERR_DISPATCH; /* Need: ws.header_size+ws.N - rxbuf.size() */

}

// We got a whole message, now do something with it:

if (ws.opcode == ws_header_type_t::EOpcodeType::TEXT_FRAME

|| ws.opcode == ws_header_type_t::EOpcodeType::BINARY_FRAME

|| ws.opcode == ws_header_type_t::EOpcodeType::CONTINUATION) {

if (ws.mask) {

for (size_t i = 0; i != ws.N; ++i) {

m_rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3];

}

}

m_recved_data.insert(m_recved_data.end(), m_rxbuf.begin()+ws.header_size,

m_rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed

if (ws.fin) {

//callable((const std::vector<uint8_t>) m_recved_data);

std::string stringMessage(m_recved_data.begin(), m_recved_data.end());

printf(">>> %s\n", stringMessage.c_str());

m_recved_data.erase(m_recved_data.begin(), m_recved_data.end());

std::vector<uint8_t> ().swap(m_recved_data);// free memory

}

}

else if (ws.opcode == ws_header_type_t::EOpcodeType::PING) {

if (ws.mask) {

for (size_t i = 0; i != ws.N; ++i) {

m_rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3];

}

}

std::string data(m_rxbuf.begin()+ws.header_size, m_rxbuf.begin()+ws.header_size+(size_t)ws.N);

send_data(ws_header_type_t::PONG, data.size(), data.begin(), data.end());

}

else if (ws.opcode == ws_header_type_t::EOpcodeType::PONG) { }

else if (ws.opcode == ws_header_type_t::EOpcodeType::CLOSE) {

close();

}

else {

fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n");

close();

}

m_rxbuf.erase(m_rxbuf.begin(), m_rxbuf.begin() + ws.header_size+(size_t)ws.N);

}

2. 简单websocket 服务端实现

完整源码:

见:GitHub - javapretty/websocket: C++解析websocket协议C++解析websocket协议. Contribute to javapretty/websocket development by creating an account on GitHub.https://github.com/javapretty/websocket.git

2.1 核心源码解析

2.1.1 TCP服务端端口监听

// socket创建

listenfd_ = socket(AF_INET, SOCK_STREAM, 0);

// 绑定端口并启动监听

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

bind(listenfd_, (struct sockaddr *)(&server_addr), sizeof(server_addr))

listen(listenfd_, 5)

2.1.2 epoll创建并注册事件

/*

typedef std::map<int, Websocket_Handler *> WEB_SOCKET_HANDLER_MAP;

WEB_SOCKET_HANDLER_MAP websocket_handler_map_;

websocket_handler_map_ 是:

以socket的句柄为索引的Websocket_handler对象映射表,

以实现服务端对每个socket连接都有一个对应的对象来处理;

*/

// epoll句柄创建

epollfd_ = epoll_create(MAXEVENTSSIZE);

/* 注册监听端口socket的 EPOLLIN事件到epoll句柄 */

ctl_event(listenfd_, true);

void Network_Interface::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);

}

}

2.1.3 epoll主循环

#define TIMEWAIT 100

int Network_Interface::epoll_loop(){

struct sockaddr_in client_addr;

struct epoll_event events[MAXEVENTSSIZE];

while(true){

nfds = epoll_wait(epollfd_, events, MAXEVENTSSIZE, TIMEWAIT);

for(int i = 0; i < nfds; i++){

// 接受客户端的连接请求,

// 并注册这个连接socket句柄的EPOLLIN事件监听到epoll

if(events[i].data.fd == listenfd_){

fd = accept(listenfd_, (struct sockaddr *)&client_addr, &clilen);

ctl_event(fd, true);

}

// 接收到了客户端发送的数据,

// 将数据读取到句柄的buffer, 然后进行处理

else if(events[i].events & EPOLLIN){

Websocket_Handler *handler = websocket_handler_map_[fd];

bufflen = read(fd, handler->getbuff(), BUFFLEN)

handler->process();

}

}

}

return 0;

}

2.1.4 websocket的 request处理

int Websocket_Handler::process(){

// 第一次时需要进行websocket握手

if(status_ == WEBSOCKET_UNCONNECT){

return handshark();

}

// 非第一次时,则进行数据处理:

// 提取数据,并印出来

request_->fetch_websocket_info(buff_);

request_->print();

memset(buff_, 0, sizeof(buff_));

return 0;

}

2.1.4.1 websocket的握手

主要是根据Websocket握手包进行解析,

然后根据Sec-WebSocket-Key进行SHA1哈希,生成相应的key,

返回给客户端,与客户端进行握手;

int Websocket_Handler::handshark(){

char request[1024] = {};

status_ = WEBSOCKET_HANDSHARKED;

fetch_http_info(); // 提取出HTTP头的每行

parse_str(request); // 解析头,并生成新的key

memset(buff_, 0, sizeof(buff_));

// 将新的握手数据发给客户端,完成握手;

return send_data(request);

}

// 逐行解析,获得Websocket的数据

int Websocket_Handler::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;

}

// 检查每一项具体HTTP头的内容,重新生成新的key

void Websocket_Handler::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");

}

int Websocket_Handler::send_data(char *buff){

return write(fd_, buff, strlen(buff));

}

2.1.4.2 非握手的数据处理

提取数据头和数据body;

int Websocket_Request::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);

}

2.1.5 websocket的response处理

没有实现;

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
下面是使用 hvlib 实现 WebSocket 服务端和客户端的 C++ 代码示例: WebSocket 服务端: ```cpp #include "HttpServer.h" #include "WebSocket.h" using namespace hv; class EchoWebSocketHandler : public WebSocketHandler { public: virtual void onOpen(WebSocketChannel* channel) override { WebSocketHandler::onOpen(channel); printf("WebSocket onOpen: %s\n", channel->getPeerAddr().c_str()); } virtual void onClose(WebSocketChannel* channel) override { WebSocketHandler::onClose(channel); printf("WebSocket onClose: %s\n", channel->getPeerAddr().c_str()); } virtual void onMessage(WebSocketChannel* channel, const std::string& payload, bool isBinary) override { WebSocketHandler::onMessage(channel, payload, isBinary); printf("WebSocket onMessage: %s, isBinary=%d\n", payload.c_str(), isBinary); channel->send(payload, isBinary); } }; int main() { HttpServer server; server.setReusePort(true); server.get("/hello", [](HttpRequest* req, HttpResponse* resp) { resp->body = "Hello, world!"; }); server.websocket("/echo", [](WebSocketChannel* channel) { channel->setHandler(new EchoWebSocketHandler); }); server.listen("0.0.0.0", 8080); server.run(); return 0; } ``` WebSocket 客户端: ```cpp #include "WebSocketClient.h" using namespace hv; class EchoWebSocketClient : public WebSocketClient { public: virtual void onOpen() override { WebSocketClient::onOpen(); printf("WebSocket onOpen\n"); send("Hello, WebSocket!", false); } virtual void onClose() override { WebSocketClient::onClose(); printf("WebSocket onClose\n"); } virtual void onMessage(const std::string& payload, bool isBinary) override { WebSocketClient::onMessage(payload, isBinary); printf("WebSocket onMessage: %s, isBinary=%d\n", payload.c_str(), isBinary); close(); } }; int main() { EventLoop loop; EchoWebSocketClient client; client.connect(&loop, "ws://127.0.0.1:8080/echo"); loop.loop(); return 0; } ``` 需要注意的是,在服务端使用 WebSocket 时,需要将连接升级为 WebSocket 连接,可以使用 `HttpServer` 类中的 `websocket` 方法来实现;在客户端使用 WebSocket 时,需要先建立连接,然后才能发送和接收消息。另外,需要注意正确处理 WebSocket 的生命周期事件,如 `onOpen`、`onClose`、`onMessage` 等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北雨南萍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值