c++ websocket 客户端
Posted qianbo_insist
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ websocket 客户端相关的知识,希望对你有一定的参考价值。
本次创建一个c++ 的websocket客户端,不依赖于其他库
头文件
#ifndef _WS_CLIENT_H
#define _WS_CLIENT_H
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#include <fcntl.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment( lib, "ws2_32" )
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <io.h>
#ifndef _SSIZE_T_DEFINED
typedef int ssize_t;
#define _SSIZE_T_DEFINED
#endif
#ifndef _SOCKET_T_DEFINED
typedef SOCKET socket_t;
#define _SOCKET_T_DEFINED
#endif
#ifndef snprintf
#define snprintf _snprintf_s
#endif
#include <stdint.h>
#define socketerrno WSAGetLastError()
#define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS
#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
#else
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#ifndef _SOCKET_T_DEFINED
typedef int socket_t;
#define _SOCKET_T_DEFINED
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (-1)
#endif
#ifndef SOCKET_ERROR
#define SOCKET_ERROR (-1)
#endif
#define closesocket(s) ::close(s)
#include <errno.h>
#define socketerrno errno
#define SOCKET_EAGAIN_EINPROGRESS EAGAIN
#define SOCKET_EWOULDBLOCK EWOULDBLOCK
#endif
#include <string>
#include <vector>
#include <memory>
#include <thread>
#include <condition_variable>
using namespace std;
typedef void(*callback_message_recv)(void * pUser,
const char * message, int len, int type);
#define ws_text 1
#define ws_binary 2
typedef enum readyStateValues
{
CLOSING,
CLOSED,
CONNECTING,
OPEN
}readyStateValues;
class WebSocket
{
struct wsheader_type {
unsigned header_size = 0;
bool fin = true;
bool mask = true;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0 = 0;
uint64_t N = 0;
uint8_t masking_key[4];
};
private:
bool useMask = true;
socket_t sockfd = INVALID_SOCKET;
std::vector<uint8_t> rxbuf;
std::vector<uint8_t> txbuf;
template <class Iterator>
void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end);
public:
readyStateValues readyState = OPEN;
// http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-------+-+-------------+-------------------------------+
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
// |I|S|S|S| (4) |A| (7) | (16/64) |
// |N|V|V|V| |S| | (if payload len==126/127) |
// | |1|2|3| |K| | |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// | Extended payload length continued, if payload len == 127 |
// + - - - - - - - - - - - - - - - +-------------------------------+
// | |Masking-key, if MASK set to 1 |
// +-------------------------------+-------------------------------+
// | Masking-key (continued) | Payload Data |
// +-------------------------------- - - - - - - - - - - - - - - - +
// : Payload Data continued ... :
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// | Payload Data continued ... |
// +---------------------------------------------------------------+
readyStateValues getReadyState() const {
return readyState;
}
void initSize(int sendsize, int recvsize);
void pollRecv(int timeout);
void sendPing();
void send(const char *message);
void sendBinary(uint8_t *data, int len);
void close();
int connect(const std::string& url);
void dispatch(callback_message_recv callable);
protected:
void pollSend(int timeout);
};
实现cpp
#include "wsclient.h"
#include <vector>
#include <string>
socket_t hostname_connect(const std::string& hostname, int port) {
struct addrinfo hints;
struct addrinfo *result;
struct addrinfo *p;
int ret;
socket_t sockfd = INVALID_SOCKET;
char sport[16];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(sport, 16, "%d", port);
if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0)
{
printf("error get addrinfo\\n");
//fprintf(stderr, "getaddrinfo: %s\\n", gai_strerror(ret));
return -1;
}
for (p = result; p != NULL; p = p->ai_next)
{
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sockfd == INVALID_SOCKET) { continue; }
if (connect(sockfd, p->ai_addr, (int)p->ai_addrlen) != SOCKET_ERROR) {
break;
}
closesocket(sockfd);
sockfd = INVALID_SOCKET;
}
freeaddrinfo(result);
return sockfd;
}
void WebSocket::initSize(int sendsize, int recvsize)
{
if (sendsize == 0)
sendsize = 1024 * 1024 * 2;
if (recvsize == 0)
recvsize = 1024 * 1024 * 2;
rxbuf.reserve(recvsize);
}
void WebSocket::pollSend(int timeout)
{
if (readyState == CLOSED) {
if (timeout > 0) {
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
select(0, NULL, NULL, NULL, &tv);
}
return;
}
if (timeout != 0) {
//fd_set rfds;
fd_set wfds;
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
//FD_ZERO(&rfds);
FD_ZERO(&wfds);
//FD_SET(sockfd, &rfds);
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
select(sockfd + 1, NULL, &wfds, 0, timeout > 0 ? &tv : 0);
}
while (txbuf.size()) {
int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0);
if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS))
{
break;
}
else if (ret <= 0) {
closesocket(sockfd);
readyState = CLOSED;
fputs(ret < 0 ? "Connection error!\\n" : "Connection closed!\\n", stderr);
break;
}
else {
txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
}
}
if (!txbuf.size() && readyState == CLOSING) {
closesocket(sockfd);
readyState = CLOSED;
}
}
void WebSocket::pollRecv(int timeout)
{
if (readyState == CLOSED) {
if (timeout > 0) {
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
select(0, NULL, NULL, NULL, &tv);
}
return;
}
if (timeout > 0) {
fd_set rfds;
//fd_set wfds;
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
select(sockfd + 1, &rfds, NULL, 0, timeout > 0 ? &tv : 0);
}
while (true) {
// FD_ISSET(0, &rfds) will be true
int N = rxbuf.size();
ssize_t ret;
//钱波 64K 一个IP包长
rxbuf.resize(N + 64000);
ret = recv(sockfd, (char*)&rxbuf[0] + N, 64000, 0);
if (false) {}
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
rxbuf.resize(N);
break;
}
else if (ret <= 0) {
rxbuf.resize(N);
closesocket(sockfd);
readyState = CLOSED;
fputs(ret < 0 ? "Connection error!\\n" : "Connection closed!\\n", stderr);
break;
}
else {
rxbuf.resize(N + ret);
}
}
}
void WebSocket::dispatch(callback_message_recv callable) {
// TODO: consider acquiring a lock on rxbuf...
while (true) {
wsheader_type ws;
if (rxbuf.size() < 2) { return; /* Need at least 2 */ }
const uint8_t * data = (uint8_t *)&rxbuf[0]; // peek, but don't consume
ws.fin = (data[0] & 0x80) == 0x80;
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
//RFC 6455 文档 接收服务器数据 maks应该为false 钱波
ws.mask = (data[1] & 0x80) == 0x80;
ws.N0 = (data[1] & 0x7f);
ws.header_size += 2;
switch (ws.N0)
{
case 126:
ws.header_size += 2;
break;
case 127:
ws.header_size += 8;
break;
default:
//
break;
}
if (ws.mask)
ws.header_size += 4;
//ws.header_size = 2 + (ws.N0 == 126 ? 2 : 0) + (ws.N0 == 127 ? 8 : 0) + (ws.mask ? 4 : 0);
if (rxbuf.size() < ws.header_size)
{
return; /* 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.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;
}
if (rxbuf.size() < ws.header_size + ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ }
// We got a whole message, now do something with it:
if (false) {}
else if (
ws.opcode == wsheader_type:以上是关于c++ websocket 客户端的主要内容,如果未能解决你的问题,请参考以下文章