实现c++轻量级别websocket协议客户端

news2024/9/24 1:26:37

1 websocket 轻量客户端

因以前发过这个代码,但是一直没有整理,这次整理了一下,持续修改,主要是要使用在arm的linux上,发送接收的数据压缩成图片发送出去。

要达到轻量websocket 使用,必须要达到几个方面才能足够简单,
1、不用加入其他的库
2、只需要使用头文件包含就可以
3、跨平台
如果正常应用,可以使用websocketpp等库,问题就是比较麻烦,要使用boost或者asio库,当然asio也是足够简单,头文件包含,编译通过要设置参数,问题不大,不过不够简单

2 应用场景

1 windows 使用
2 linux使用
3 linux arm 板子上使用
在arm上编译的时候,就不用编译那么多的库文件了

3 原理

使用select模型 和原始操作系统的socket来直接编写代码,select模型比较简单,非长时间阻塞模式,以下是webscoket协议字节示意图
在这里插入图片描述
本文函数根据上图实现了websocket协议。定义的主要数据结构如下所示,websocket协议里面包含两种数据,一种是二进制,一种是文本,是可以指定的

	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链接

websocket链接使用的是http协议,所不同的是必须做upgrade

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

上面把http协议升级为websocket协议的内容写出,把客户端的内容填充过去发送到服务端就行,从下面代码可以看出我们需要哪些内容

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

发送和接收

使用select来做异步的模式,发送的时候指定参数
很简单,就如下所示

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

当然,如果要将socket置为非阻塞,开始的时候还是要设置的

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

下面是发送的函数,参数为毫秒

//参数是毫秒
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;
		}
	}

下面是接收函数

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

可以看出,我们使用select 仅仅是不阻塞,简单使用FD_ISSET宏去判决是否有数据达到,如果我们没有收到数据,我们就直接返回。

为了简单使用程序,我们封装一个class来使用接收和发送

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

以上为封装的外层线程代码,里面同时也封装了断线重连。

我们常常使用python或者使用nodejs来做测试,这里使用nodejs写一个简单的服务器程序,接收到数据以后发回。

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);
  });//当收到消息时,在控制台打印出来,并回复一条信息
});

测试结果

服务端nodejs显示
在这里插入图片描述
客户端链接后显示
在这里插入图片描述

整个的头文件代码和测试代码在gitee上面
gitee地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL:数学函数和字符串函数

目录 前言&#xff1a; 数学函数&#xff1a; 求绝对值&#xff1a; 求PI&#xff1a; 求平方根&#xff1a; 求余数&#xff1a; 取整&#xff1a; 随机数&#xff1a; 四舍五入&#xff1a; 只舍不入&#xff1a; 返回参数符号&#xff1a; 幂运算&#xff1a; …

Illustrator如何编辑图形对象之实例演示?

文章目录 0.引言1.绘制海浪插画2.绘制时尚波浪发型3.绘制一条鲸鱼 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对图形…

快速上手Pytorch实现BERT,以及BERT后接CNN/LSTM

快速上手Pytorch实现BERT&#xff0c;以及BERT后接CNN/LSTM 本项目采用HuggingFace提供的工具实现BERT模型案例&#xff0c;并在BERT后接CNN、LSTM等 HuggingFace官网 一、实现BERT&#xff08;后接线性层&#xff09; 1.引用案例源码&#xff1a; from transformers impo…

开关电源基础01:电源变换器基础(2)

说在开头&#xff1a;关于德布罗意的电子波&#xff08;3&#xff09; 1923年&#xff0c;德布罗意在求出他的相波之前&#xff0c;康普顿刚好用光子说解释了康普顿效应&#xff08;记性好的胖友们应该还记得&#xff1a;散射波的波长变长问题&#xff09;&#xff0c;从而带领…

开关电源基础02:基本开关电源拓扑(2)-BOOST-BUCKBOOST拓扑

说在开头&#xff1a;关于海森堡的矩阵&#xff08;2&#xff09; 海森堡写完论文就回到了哥廷根大学&#xff0c;他一看见玻恩就把这份论文拿出来请老师把关&#xff0c;还说要趁着假期去趟英国剑桥大学讲课交流。玻恩拿过论文一看&#xff0c;海森堡画的这个表格是啥玩意啊&…

【操作系统】高性能网络模式:Reactor 和 Proactor

【操作系统】高性能网络模式&#xff1a;Reactor 和 Proactor 参考资料&#xff1a; 高性能 RPC 通信的实现- 巧用 reactor 模式 高性能网络模式&#xff1a;Reactor 和 Proactor NIO Reactor模型 Netty「基石」之Reactor模式 高性能IO模型分析-Reactor模式和Proactor模式 【…

【服务器】无公网IP,异地远程连接威联通NAS

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1. 威联通安装cpolar内网穿透 2. 内网穿透 2.1 创建隧道 2.2 测试公网远程访问 3. 配置固定二级子域名 3.1 保留二级子域名 3.2 配置二级子域名 4. 使用固定二级子…

Linux诊断原因:生产环境服务器变慢,诊断思路和性能评估

Linux诊断原因&#xff0c;生产环境服务器变慢&#xff0c;诊断思路和性能评估 1 整机&#xff1a;top&#xff0c;查看整机系统性能 使用top命令的话&#xff0c;重点关注的是 %CPU、%MEM 、load average 三个指标 load average三个指标&#xff1a;分别代表1、5、15时分钟系…

2022年NOC大赛编程马拉松赛道初赛图形化低年级A卷-正式卷,包含答案

目录 选择题: 下载文档打印做题: 2022年NOC大赛编程马拉松赛道【初赛】图形化低年级A卷-正式卷 2022NOC-图形化初赛低年级A卷正式卷 选择题: 1、答案:B 禾木是一个军事迷,他打算利用业余时间制作一款射击游戏。在游戏中,玩家可以通过鼠标控制手枪移动。请问,给手枪…

springboot+vue体质测试数据分析及可视化设计(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的体质测试数据分析及可视化设计。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&a…

[svg-icon]引入vue项目后,use标签为0,已解决

这个bug我之前遇到过一次&#xff0c;解决了也就没记录 但是好记性不如烂笔头&#xff0c;这次重新遇到&#xff0c;又重新排查bug花了1个多小时 svg引入vue项目&#xff0c;需要依赖svg-sprite-loader npm install svg-sprite-loader在vue.config.js中 chainWebpack(conf…

NOC大赛·核桃编程马拉松赛道知识点大纲(高年级及初中组)

NOC核桃编程马拉松知识点大纲(高年级及初中组) (一)基础语法 1.掌握运动积木的用法。 包括“移动 10 步”、“左/右转 X 度”、“面向 X 方向/鼠标指针/ 角色”、“移到 XY 坐标/鼠标/角色”、“X/Y 坐标的设定和增加”、 “滑行到 XY/鼠标/角色”等积木用法,详细如下。 1…

【数据结构】链表OJ:力扣160. 相交链表

最近这几篇内容都有关链表&#xff0c;接下来几篇内容会分享一些链表的OJ题&#xff0c;希望对你有所帮助。 今天要分享的内容是力扣160. 相交链表&#xff1a;160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 目录 题目&#xff1a; 题解 题目&#xff1a; 给你…

数据分析02——numpy模块的在jupyter中的使用

0、numpy&#xff1a; 在计算机中会把数学当中的矩阵叫做数组&#xff0c;在很多应用中的表格也就是通过矩阵表示的&#xff0c;所以numpy广泛用于机器学习&#xff0c;数据分析&#xff0c;和图像处理领域。 1、numpy常用方法和函数&#xff1a; 前言&#xff1a;在使用nump…

腾讯云轻量应用服务器使用限制说明(十大限制)

腾讯云轻量应用服务器和云服务器CVM相比具有一些限制&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;轻量服务器只能套餐整体升级且不支持降配&#xff0c;轻量不支持用户自定义配置私有网络VPC&#xff0c;还有如实例配额、云硬盘配额、备案限制和内网连通性等限…

Ububtu20.04 无法连接外屏(显卡驱动问题导致)

Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; 1. 问题描述2. 解决方案 1. 问题描述 一开始我的ububt…

JavaGuide复习1——常见面试题总结部分

1、关于包装类的缓存机制 两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。 对应源码&#xff1a; 拆装箱 都是调用了包装类的方法&#xff1a;valueOf&#xff08;基本转包装&#xff09;、xxxValue&#xff08;包装转基本&#xff09; 注意char char在Java中占用…

Illustrator如何进行文本的创建与编辑之实例演示?

文章目录 0.引言1.创建点文字2.串接文本3.石刻文字 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对文本的创建与编辑进…

shell脚本编程基础

shell 文件的建立和执行 shell脚本是将shell命令在文件中编写&#xff0c;从而实现一次执行很多shell命令。如&#xff1a; # ab.sh # !/bin/bash echo "Welcome Linux" cd mkdir myjsjsj echo "Your dir is" pwd可以看到这就是多个shell命令。 执行前需…

【数据结构】选择排序(详细)

选择排序 1. 直接选择排序2. 堆排序2.1 堆2.2 堆的实现&#xff08;以大根堆为例&#xff09;2.3 堆排序 3. 堆排序&#xff08;topK问题&#xff09; 1. 直接选择排序 思想 以排升序为例。以a[i]为最大值&#xff08;或最小值&#xff09;&#xff0c;从a[i1]到a[n-1-i]比较选…