C++websocket调用

文章记录我学习C++实现websocket服务端与客户端

准备

工具准备

libwebsockets库下载

通过git下载libwebsockets库

git clone https://github.com/warmcat/libwebsockets.git

CMake工具下载

为了可以进行多平台编译,websockets项目采用CMake作为编译工具,所以如果机器上没有CMake需要去安装CMake
官网下载地址:https://cmake.org/download/

编译准备

CMake安装完成之后我们需要进入libwebsockets源码目录下的build目录,然后执行cmake …即开始源码的编译。
在这里插入图片描述

客户端代码实现

初始化websocket


int __stdcall LoadCfg(const char* cfg, int devId)
{
	CString str;
	USES_CONVERSION;
	// 用于创建vhost或者context的参数
	ctx_info.port = CONTEXT_PORT_NO_LISTEN;
	ctx_info.iface = NULL;
	ctx_info.protocols = protocols;
	ctx_info.gid = -1;
	ctx_info.uid = -1;
	//ctx_info.pt_serv_buf_size = 4096 * 2;
	//ssl支持(指定CA证书、客户端证书及私钥路径,打开ssl支持)
	ctx_info.client_ssl_ca_filepath = Ca_path;//如果服务器有CA证书则需要这行代码
	//#endif
	ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
	// 创建一个WebSocket处理器
	char *address = Ip_addr;//地址
	int port = atoi(Sev_Port);//端口
	char addr_port[256] = { 0 };
	sprintf(addr_port, "%s:%u", address, port & 65535);

	// 客户端连接参数
	context = lws_create_context(&ctx_info);

	conn_info.context = context;
	conn_info.address = address;
	conn_info.port = port;
	
	conn_info.ssl_connection = LCCSCF_USE_SSL | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_EXPIRED;//ca证书连接属性,如果没CA证书则屏蔽
	conn_info.path = "./";
	conn_info.host = addr_port;
	conn_info.origin = addr_port;
	conn_info.protocol = protocols[0].name;

	// 下面的调用触发LWS_CALLBACK_PROTOCOL_INIT事件
	// 创建一个客户端连接
	Client_wsi = lws_client_connect_via_info(&conn_info);

	DWORD  threadId;
	exit_sig = 0;
	DWORD dwExitCode = 0;
	GetExitCodeThread(hStart, &dwExitCode);
	if (dwExitCode == STILL_ACTIVE) {
		LOG(INFO) << "线程存在";
		return 0;
	}
	else {
		hStart = CreateThread(NULL, 0, ConnectFunc, 0, 0, &threadId); // 创建线程
	}
	return 0;
}

DWORD WINAPI ConnectFunc(LPVOID p)
{
	int n = 0;
	was_closed = 0;
	while (n >= 0 && !was_closed /*&& !exit_sig*/) {
		// 执行一次事件循环(Poll),最长等待1000毫秒
		n = lws_service(context, 0);
		if (Client_wsi)
			continue;
		Client_wsi = lws_client_connect_via_info(&conn_info);
		if (Client_wsi == NULL) {
			break;
		}
	}
	// 销毁上下文对象
	return 0;
}

在这我使用了线程来实现长连接

客户端回调函数

/**
* 某个协议下的连接发生事件时,执行的回调函数
* wsi:指向WebSocket实例的指针
* reason:导致回调的事件
* user 库为每个WebSocket会话分配的内存空间
* in 某些事件使用此参数,作为传入数据的指针
* len 某些事件使用此参数,说明传入数据的长度
*/
int callback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len) {
	HANDLE hThread;
	DWORD  threadId;
	struct session_data* data = (struct session_data*) user;

	int rlen = 0;

	switch (reason) {
	case LWS_CALLBACK_CLIENT_CLOSED:
		lwsl_notice("Closed to server ok!\n");
		was_closed = 1;
		break;
	case LWS_CALLBACK_CLIENT_ESTABLISHED:   // 连接到服务器后的回调
		lwsl_notice("Connected to server ok!\n");
		//lws_callback_on_writable(wsi);
		ConnectServer = 2;
		break;
	case LWS_CALLBACK_CLIENT_RECEIVE:       //接收到服务器数据后的回调,数据为in,其长度为len
		lwsl_notice("Rx: %s\n", (char*)in);
		char c_res[10256];
		memcpy(c_res, in, len);
		memcpy(ResBuf, U2G((char*)c_res, len), len);
		ResLen = len;
		break;
	case LWS_CALLBACK_CLIENT_WRITEABLE: 
//		CallBackSendMsg(wsi);
		b_IsWriteAble = TRUE;
		break;
	}
	return 0;
}

/**
* 支持的WebSocket子协议数组
* 子协议即JavaScript客户端WebSocket(url, protocols)第2参数数组的元素
* 你需要为每种协议提供回调函数
*/
struct lws_protocols protocols[] = {
	{
		//协议名称,协议回调,接收缓冲区大小
		"ws", callback, sizeof(struct session_data), MAX_PAYLOAD_SIZE,
	},
	{
		NULL, NULL,   0 // 最后一个元素固定为此格式
	}
};

在这里我会在回调函数里面有标记是否连接,因为用的线程

报文发送与接收

Json::Value CmdFuntion(Json::Value js_CmdSendBuf,int SendLen){

	js_CallBackSendMsg = js_CmdSendBuf;
	LOG(INFO) << "发送长度:" << SendLen;
	b_IsWriteAble = FALSE;

	if (Client_wsi != NULL){
		lws_callback_on_writable(Client_wsi);
	}
			
	Json::Value ReturnBuf;
	int i,j =0;
	for (i = atoi(Wait_Time);i > 0;i--) {
		if (b_IsWriteAble && !j){
			CallBackSendMsg(Client_wsi);	
			j = 1;
		}
		if (ResLen) {
			ResLen = 0;
			Json::Reader reader;
			ReturnBuf.empty();
			LOG(INFO) << "接收报文:" << ResBuf;
			string req_str((char*)ResBuf);
			memset(ResBuf, 0x00, sizeof(ResBuf));
			if (!reader.parse(req_str, ReturnBuf)) {
				ReturnBuf["error"] = "-1";
			}
			break;
		}
		Sleep(100);
	}
	if (i == 0)
	{
		LOG(INFO) << "接收超时:";
		ReturnBuf["error"] = "-1";
	}
	js_CallBackSendMsg.empty();
	return ReturnBuf;
}

下面是发送实现

static void CallBackSendMsg(struct lws* Client_wsi)
{
	USES_CONVERSION;
	js_CallBackSendMsg["id"] = "101";

	string a,b;
	a = G2U(js_CallBackSendMsg.toStyledString().c_str(), js_CallBackSendMsg.toStyledString().length());
	///
	if (js_CallBackSendMsg["method"] == "printCard")
	{
		Json::Value  js_FirstMsg;
		CString length;
		//int alen= a.length();
		int alen = js_CallBackSendMsg.toStyledString().length();
		js_FirstMsg["len"] = alen;
		js_FirstMsg["id"] = "101";
		b = G2U(js_FirstMsg.toStyledString().c_str(), js_FirstMsg.toStyledString().length());
		memset(SendBuf_b, 0x00, sizeof(SendBuf_b));
		memcpy(SendBuf_b, b.c_str(), b.length());
		LOG(INFO) << "发送报文:" << SendBuf_b;/*
		int befor = js_CallBackSendMsg.toStyledString().length();
		LOG(INFO) << "发送报文之前长度:" << befor << ",之后长度" << b.length();*/
		lws_write(Client_wsi, SendBuf_b, b.length(), LWS_WRITE_TEXT);
		Sleep(200);
	}
	
	//
	memset(SendBuf, 0x00, sizeof(SendBuf));
	memcpy(SendBuf, a.c_str(), a.length());
	//CString qa(SendBuf);
	LOG(INFO) << "发送报文:" << SendBuf << ",length:" << a.length();
	lws_write(Client_wsi, SendBuf, a.length(), LWS_WRITE_TEXT);

}

因为系统会对过长的websocket报文进行剪切,所以在有长报文发送之前我会发送一个长度的报文,所以这里就是连续发送两次报文,而我服务器则对带长度的报文进行处理并不反回成功或失败的报文。

客户端简单调用

int __stdcall Open(int devId)
{
	LOG(INFO) << "开始执行打开设备操作";

	LOG(INFO) << "执行打开操作";
	int ret = -1;
	Json::Value js_SendMsg, js_Return;
	js_SendMsg.clear();
	js_SendMsg["method"] = "connect";
	js_SendMsg["params"]["uuid"] = "1001";
	isHaveCard = TRUE;
	if (js_SendMsg.isMember("method")) {
		js_Return = CmdFuntion(js_SendMsg, js_SendMsg.toStyledString().length());
		if (js_Return.isMember("error")) {
			ret = -1;
			LOG(INFO) << "执行打开操作出错";
		}
		else if (js_Return.isMember("result")) {
			ret = 0;
			LOG(INFO) << "打开设备成功";
		}
	}
	else
	{
		ret = 0;
		LOG(INFO) << "打开设备成功";
	}
	isHaveCard = FALSE;
	return ret;
}

服务器代码实现


初始化

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值