Implement c++ lightweight websocket protocol client

1 websocket light client

Because this code has been sent before, but it has not been sorted out. This time I sorted it out and continued to modify it. The main purpose is to use it on arm linux, and compress the sent and received data into pictures and send it out.

To achieve lightweight websocket use, several aspects must be achieved to be simple enough.
1. No need to add other libraries
2. Only need to use header files to include
3.
If cross-platform is used normally, libraries such as websocketpp can be used. The problem is It's more troublesome. You need to use boost or asio library. Of course, asio is simple enough. The header file contains, and you need to set parameters when compiling. It's not a big problem, but it's not simple enough.

2 application scenarios

1 windows use
2 linux use
3 linux arm board use
When compiling on arm, there is no need to compile so many library files

3 principles

Use the select model and the socket of the original operating system to directly write the code. The select model is relatively simple and non-blocking for a long time. The following is the byte diagram of the webscoket protocol. This function
insert image description here
implements the websocket protocol according to the above figure. The main data structure defined is as follows. The websocket protocol contains two kinds of data, one is binary and the other is text, which can be specified

	struct wsheader_type {
    
    
		unsigned header_size;
		bool fin;
		bool mask;
		enum opcode_type {
    
    
			CONTINUATION = 0x0,
			TEXT_FRAME = 0x1,
			BINARY_FRAME = 0x2,
			CLOSE = 8,
			PING = 9,
			PONG = 0xa,
		} opcode;
		int N0;
		uint64_t N;
		uint8_t masking_key[4];
	};

websocket link

The websocket link uses the http protocol, the difference is that it must be upgraded

static const char* desthttp = "GET /%s HTTP/1.1\r\n"
			"Host: %s:%d\r\n"
			"Upgrade: websocket\r\n"
			"Connection: Upgrade\r\n"
			"Origin: %s\r\n"
			"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
			"Sec-WebSocket-Version: 13\r\n\r\n";

The above upgrades the http protocol to the websocket protocol and writes out the content, fills in the content of the client and sends it to the server. From the code below, we can see what content we need

char line[256];
int status;
int i;
sprintf(line, desthttp, path, host, port, origin.c_str());
::send(sockfd, line, (int)strlen(line), 0);

send and receive

Use select to do asynchronous mode, specifying parameters when sending
is very simple, as shown below

	fd_set wfds;
	timeval tv = {
    
     timeout / 1000, (timeout % 1000) * 1000 };
	FD_ZERO(&wfds);
	if (txbuf.size()) {
    
     FD_SET(sockfd, &wfds); }
	select((int)(sockfd + 1), NULL, &wfds, 0, timeout > 0 ? &tv : 0);

Of course, if you want to set the socket as non-blocking, you still need to set it at the beginning

#ifdef _WIN32
		u_long on = 1;
		ioctlsocket(sockfd, FIONBIO, &on);
#else
		fcntl(sockfd, F_SETFL, O_NONBLOCK);
#endif

The following is the function sent, the parameter is milliseconds

//参数是毫秒
void pollSend(int timeout)
	{
    
    
		if (v_state == CLOSED) {
    
    
			if (timeout > 0) {
    
    
				timeval tv = {
    
     timeout / 1000, (timeout % 1000) * 1000 };
				select(0, NULL, NULL, NULL, &tv);
			}
			return;
		}
		if (timeout != 0) {
    
    
			fd_set wfds;
			timeval tv = {
    
     timeout / 1000, (timeout % 1000) * 1000 };
			FD_ZERO(&wfds);

			if (txbuf.size()) {
    
     FD_SET(sockfd, &wfds); }
			select((int)(sockfd + 1), NULL, &wfds, 0, timeout > 0 ? &tv : 0);
		}
		while (txbuf.size()) {
    
    
			int ret = ::send(sockfd, (char*)&txbuf[0], (int)txbuf.size(), 0);
			if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
    
    
				break;
			}
			else if (ret <= 0) {
    
    
				closesocket(sockfd);
				v_state = CLOSED;
				fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
				break;
			}
			else {
    
    
				txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
			}
		}
		if (!txbuf.size() && v_state == CLOSING) {
    
    
			closesocket(sockfd);
			v_state = CLOSED;
		}
	}

Here is the receive function

void pollRecv(int timeout)
	{
    
    
		if (v_state == 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((int)(sockfd + 1), &rfds, NULL, 0, timeout > 0 ? &tv : 0);
			if (!FD_ISSET(sockfd, &rfds))
			{
    
    
				printf("out of here ,no data\n");
				return;
			}
		}
		while (true) {
    
    
			// FD_ISSET(0, &rfds) will be true
			int N = (int)rxbuf.size();
			ssize_t ret;
			//钱波 64K 一个IP包长
			rxbuf.resize(N + 64000);
			ret = recv(sockfd, (char*)&rxbuf[0] + N, 64000, 0);
			if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
    
    
				rxbuf.resize(N);
				break;
			}
			else if (ret <= 0) {
    
    
				rxbuf.resize(N);
				closesocket(sockfd);
				v_state = CLOSED;
				fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
				break;
			}
			else {
    
    //接收到的数据
				rxbuf.resize(N + ret);
			}
		}
	}

It can be seen that we use select only to not block, and simply use the FD_ISSET macro to determine whether there is data arriving. If we do not receive data, we will return directly.

In order to use the program easily, we encapsulate a class to use receiving and sending

class c_ws_class //:public TThreadRunable
{
    
    
	thread v_thread;
	std::mutex v_mutex;
	std::condition_variable v_cond;
	WebSocket v_ws;
	int v_stop = 1;
	string v_url;
	callback_message_recv v_recv = NULL;
	//已经
	//bool v_is_working = false;
public:

	static int InitSock()
	{
    
    
#ifdef _WIN32
		INT rc;
		WSADATA wsaData;

		rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (rc) {
    
    
			printf("WSAStartup Failed.\n");
			return -1;
		}
#endif
		return 0;
	}


	static void UnInitSock()
	{
    
    
		WSACleanup();
	}


	c_ws_class()
	{
    
    }
	~c_ws_class()
	{
    
    
		v_ws.close();
	}
	
public:


	void set_url(const char * url)
	{
    
    
		v_url = url;
	}
	int connect()
	{
    
    
		if (v_url.empty())
			return -1;
		return v_ws.connect(v_url);
	}
	void Start(callback_message_recv recv)
	{
    
    
		//because we will connect all over the time, so v_stop is zero
		v_stop = 0;
		v_ws.initSize(0, 0);
		v_recv = recv;
		v_thread = std::thread(std::bind(&c_ws_class::Run, this));
	}

	bool send(const char * str)
	{
    
    
		if (str != NULL)
		{
    
    
			if (v_ws.getReadyState() != CLOSED)
			{
    
    
				v_ws.send(str);
				v_ws.pollSend(10);
				return true;
			}
			return false;
		}
		return false;
	}

	void sendBinary(uint8_t *data, int len)
	{
    
    
		if (v_ws.getReadyState() != CLOSED)
		{
    
    
			v_ws.sendBinary(data, len);
			v_ws.pollSend(5);
		}
	}
	void Stop()
	{
    
    
		v_stop = 1;
	}
	int isStop()
	{
    
    
		return v_stop;
	}
	void Join()
	{
    
    
		if (v_thread.joinable())
			v_thread.join();
	}
	void Run()
	{
    
    
		
		while (v_stop == 0) {
    
    
			//WebSocket::pointer wsp = &*ws; // <-- because a unique_ptr cannot be copied into a lambda
			if (v_stop == 1)
				break;
			if (v_ws.getReadyState() == CLOSED)
			{
    
    
				//断线重连
				if (connect() != 0)
				{
    
    
					for (int i = 0; i < 20; i++)
					{
    
    
						std::this_thread::sleep_for(std::chrono::milliseconds(100));
						if (v_stop == 1)
							break;
					}
				}
			}
			else
			{
    
    
				v_ws.pollRecv(10);
				v_ws.dispatch(v_recv);
			}

		}
		v_ws.close();
		//std::cout << "server exit" << endl;
		v_stop = 1;
	}

	void WaitForSignal()
	{
    
    
		std::unique_lock<std::mutex> ul(v_mutex);
		v_cond.wait(ul);
	}
	void Notify()
	{
    
    
		v_cond.notify_one();
	}
};

The above is the encapsulated outer thread code, which also encapsulates disconnection reconnection.

We often use python or nodejs for testing. Here we use nodejs to write a simple server program and send it back after receiving the data.

const WebSocket = require('ws');

const wss = new WebSocket.Server({
    
     port: 8000 });

wss.on('connection', function connection(ws) {
    
    
  ws.on('message', function incoming(message) {
    
    
    console.log('received: %s', message);
    ws.send('recv:'+message);
  });//当收到消息时,在控制台打印出来,并回复一条信息
});

Test Results

server nodejs display
insert image description here
client link display
insert image description here

The entire header file code and test code are in
the gitee address above gitee

Guess you like

Origin blog.csdn.net/qianbo042311/article/details/130541129