linux下C/C++语言socket.io库的编译和使用


前言

本文主要讲述linux下C++语言socket.io库的编译及使用


一、什么是socket.io

socket.io是一个基于WebSocket的CS的实时通信库,可以在客户端和服务器之间实现实时,双向和基于事件的通信。它底层基于engine.io。engine.io使用WebSocket和xhr-polling(或jsonp)封装了一套自己的协议,在不支持WebSocket的低版本浏览器中使用了长轮询(long polling)来代替。socket.io在engine.io的基础上增加了namespace,room,自动重连等特性。Websocket可以说是socket.io的一个子集。

二、版本特点

  • Socket.io目前已经发展到v5版本。
  • 从Socket.IO 3.1.0 开始,v3 服务器现在能够与 v2 客户端通信。但是v3 客户端仍然无法连接到 v2 服务器。
  • Socket.IO 4.0.0 重大更改仅影响服务器端的 API。Socket.IO 协议本身没有更新,因此 v3 客户端将能够访问 v4 服务器,反之亦然。此外,当 allowEIO3:true 标志存在,则Socket.IO v2 客户端和 Socket.IO v4 服务器之间仍然可以使用兼容模式 。

三、版本判断

Socket.io依赖Engine.IO版本,关系如下: Engine.IO v2 包含在 Socket.IO v0.9 中, v3 包含在 Socket.IO v1/v2 中,v4包含在 Socket.IO v3 中。通常拿到一个数据包怎么判断Socket.io版本,通过看EIO版本号,如下所示,Socket.IO请求报文中EIO=3表示Engine.IO版本为3,则Socket.io的版本为v1/v2,不能与Socket.IO v3服务器通信。

在这里插入图片描述

四、socket.io库相关资源

  • 官网:https://socket.io/
  • 中文介绍网站:https://www.w3cschool.cn/socket/
  • C++库源码下载地址: https://github.com/socketio/socket.io-client-cpp,根据版本特点介绍的内容,在release版本中选择合适的源代下载。 -如需要对接v3版本服务器,则需要下载3.X.X版本,否则下载2.X.X版本。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、socket.io库编译

  1. Socket.io client源码下载好后,需要下载依赖的库资源,这里以下载 socket.io-client-cpp-2.1.0版本为例,在主干中点击lib, 则可以看到依赖的库资源及版本,如下图所示,点击下载即可,然后把资源解压到socket.io的源码lib目录下。(除以上库之外,socket.io也依赖openssl库,openssl下载和编译参考我的其他文章)
    在这里插入图片描述
    在这里插入图片描述

  2. Socket.io使用Cmake编译,创建交叉编译文件toolChain.cmake,这里放到与源码同一级目录,如下图
    在这里插入图片描述
    toolChain.cmake内容参考如下,注意修改交叉编译工具链及openssl依赖:

#PROJECT_SOURCE_DIR为执行cmake命令后顶层CMakeLists.txt所处路径
SET(TOOLCHAIN_FILE_PATH  "${PROJECT_SOURCE_DIR}")
message(STATUS "TOOLCHAIN_FILE_PATH=${TOOLCHAIN_FILE_PATH}")

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

#设置交叉编译工具链
set(compilerPrefix /opt/arm-linux-) 
set(CMAKE_C_COMPILER ${compilerPrefix}gcc)
set(CMAKE_CXX_COMPILER ${compilerPrefix}g++)

#选择编译模式:Debug、Release
set(CMAKE_BUILD_TYPE Release CACHE STRING "set build type manually")

#设置Openssl库位置
set(OPENSSL_INCLUDE_DIR "${TOOLCHAIN_FILE_PATH}/../../../openssl-1.0.2t/openssl-1.0.2t/install/include")
set(OPENSSL_ROOT_DIR "${TOOLCHAIN_FILE_PATH}/openssl-1.0.2t-ai3/openssl-1.0.2t/install")
set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_ROOT_DIR}/libs/libcrypto.a")
set(OPENSSL_SSL_LIBRARY "${OPENSSL_ROOT_DIR}/libs/libssl.a")

#指定处目标系统搜索目录外的搜索目录,这里暂时用不到
#set(CMAKE_FIND_ROOT_PATH "")

# ONLY表示只在目标系统目录查找,NEVER表示不查找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
  1. 切到socket.io-client-cpp/build目录后执行,执行完成后会在build目录下生成Makefile文件。
cmake -DCMAKE_TOOLCHAIN_FILE=../toolChain.cmake .. -DCMAKE_INSTALL_PREFIX=../build/

在这里插入图片描述

  1. 然后执行make命令,会在build目录下生成libsioclient.a、libsioclient_tls.a两个静态库(libsioclient_tls.a为支持TLS的库,另一个不支持TLS,根据服务器是否使用TLS加密传输选择),make DESTDIR=./install/ install,在install下会生成用户使用的头文件及lib库。
    在这里插入图片描述
    在这里插入图片描述
  2. 完成以上内容就完成了socket.io库的编译工作。

六、socket.io程序编译调试

编写一个测试程序 sio_test.cpp,文件内容如下所示:

#include <iostream>
#include "string.h"
#include <unistd.h>
#include "posixWrapper.h"
#include "sio_client.h"    //socket.io库编译生成的头文件

using namespace std;
using namespace sio;

class connection_listener
{
	sio::client &handler;

public:

	connection_listener(sio::client& h):

	handler(h)
	{
		
	}

	void on_connected()
	{
		std::cout<<"[SOCKET.IO]sio con "<<std::endl;
	}

	void on_close(client::close_reason const& reason)
	{
		std::cout<<"[SOCKET.IO]sio closed "<<std::endl;
	}

	void on_fail()
	{
		std::cout<<"[SOCKET.IO]sio failed "<<std::endl;
	}
};

void OnMessage_deal_example(const std::string& name, message::ptr const& data, bool isAck, message::list& ack_resp)
{
	std::cout<<"[SOCKET.IO]OnMessage_deal_example:" << name <<std::endl;

	if(isAck)
	{
		std::string msg = "{success}";
		ack_resp.push(msg);
	}

	if (sio::message::flag_integer == data->get_flag())
	{
		std::cout<< "int:" << data->get_int() <<std::endl;
	}
	else if (sio::message::flag_double == data->get_flag())
	{
		std::cout<< "double:%lf\n" << data->get_double() <<std::endl;
	}
	else if (sio::message::flag_string == data->get_flag())
	{
		std::cout<< "string:" << data->get_string() <<std::endl;
	}
	else if (sio::message::flag_binary == data->get_flag())
	{
		std::cout<<"[SOCKET.IO]binary!"<<std::endl;
	}
	else if (sio::message::flag_object == data->get_flag())
	{
		std::cout<<"[SOCKET.IO]object!"<<std::endl;

		/* 以解析消息内容为: {“deviceIds”:“test12345”}为例 */
		std::map<std::string, message::ptr> MessageObject;
		map<std::string, message::ptr>::iterator iter;

		MessageObject = data->get_map();
		iter = MessageObject.find("deviceIds");
		if(iter != MessageObject.end())
		{
			if (sio::message::flag_string == iter->second->get_flag())
			{
				std::cout<< "deviceIds:" << iter->second->get_string() <<std::endl;
			}
			else
			{
				std::cout<< "[SOCKET.IO]unsupport data type:" << iter->second->get_flag() <<std::endl;
			}
		}
		else
		{
			std::cout<<"[SOCKET.IO]Do not Find deviceIds" <<std::endl;
		}
	}
	else
	{
		std::cout<<"[SOCKET.IO]unsupport data type!"<<std::endl;
	}
}

int socketio_thread(void)
{
	sio::client h;
	connection_listener l(h);
	std::map<std::string, std::string> header;
	std::map<std::string, std::string> query;
	pair<map<int, string>::iterator, bool> retPair;
	char devId[32] = {0};
	char NewServerAddr[256] = {0};
	char buf[512] = {0};
	int isFirstRun = 1;
	int isNeedReconn = 0;

	snprintf(devId, sizeof(devId), "%s", "test12345");

	/* 绑定状态监听函数 */
	h.set_open_listener(std::bind(&connection_listener::on_connected, &l));
	h.set_close_listener(std::bind(&connection_listener::on_close, &l,std::placeholders::_1));
	h.set_fail_listener(std::bind(&connection_listener::on_fail, &l));
	
	/* 监听namespace为/entrance的消息, 消息名为:push_message */
	h.socket("/entrance")->on("push_message", &OnMessage_deal_example);
	
	/* 设置连接参数,连接服务器 */
	/* 添加报文头 */
	header.insert(std::pair<std::string, std::string>("Origin", "null"));
	header.insert(std::pair<std::string, std::string>("Accept", "*/*"));
	/* 添加请求报文体(URL之后的内容,如  http://xxxxxxx?devId) */
	query.insert(std::pair<std::string, std::string>("deviceid", devId));

	while(1)
	{
		if(1 == isFirstRun || (0 == isFirstRun && 1 == isNeedReconn))
		{
			std::cout<<"[SOCKET.IO]socketio run!" <<std::endl;

			snprintf(NewServerAddr, sizeof(NewServerAddr), "%s", "http://10.6.124.181:10000");
			snprintf(buf, sizeof(buf), "%s/entrance", NewServerAddr);
			/* 连接socket.io服务器 */
			h.connect(buf, query, header);
			isFirstRun = 0;
			isNeedReconn = 0;
		}

		/* 使用emit发送消息 */
		//h.socket("/entrance")->emit("ping");

		sleep(5);
	}

	h.socket("/entrance")->close();
	h.close();
	h.clear_con_listeners();
	h.clear_socket_listeners();

	return 0;
}

编译测试程序 sio_test.cpp,arm-linux-g++为本文所用编译工具链,看开发者情况修改,~/nfs/lib为本文测试存放libpthread、libm、libdl等基础库的路径,看开发者情况修改, ./lib 为存放libssl、libcrypto库路径,看开发者情况修改:

(1)支持ssl加密的编译链接方式(即链接libsioclient_tls.a及openssl库相关资源):

arm-linux-g++ sio_test.cpp -o sio_test -lsioclient_tls -lssl -lcrypto -lpthread -lm -ldl -I~/xxx/ssl/include -I~/xxx/ssl/include/openssl -I./include -L~/nfs/lib -L./lib

(2)非加密的编译链接方式(即链接libsioclient.a):

arm-linux-g++ sio_test.cpp -o sio_test -lsioclient  -lpthread -lm -ldl -L/lib -L./lib

这里以python socket.io脚本作为陪测服务器,由于本文使用的是V2.X.X socket.io版本,选用 python-socketio-1.9.0.zip 作为陪测,不同版本使用的 python-socketio 版本不同,需要注意,调试步骤如下:

  • 解压 python-socketio-1.9.0.zip 文件,进入python-socketio-1.9.0\examples\wsgi目录,打开app.py脚本,修改脚本内容如下:
# set async_mode to 'threading', 'eventlet', 'gevent' or 'gevent_uwsgi' to
# force a mode else, the best mode is selected automatically from what's
# installed
async_mode = None

import time
from flask import Flask, render_template
import socketio

sio = socketio.Server(logger=True, async_mode=async_mode)
app = Flask(__name__)
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app)
app.config['SECRET_KEY'] = 'secret!'
thread = None

def background_thread():
   """Example of how to send server generated events to clients."""
   count = 0
   while True:
       sio.sleep(10)
       count += 1
       sio.emit('my response', {'data': 'Server generated event'},
                namespace='/test')

@app.route('/')
def index():
   global thread
   if thread is None:
       thread = sio.start_background_task(background_thread)
   return render_template('index.html')

@sio.on('connect', namespace=None)
def connect(sid, message):
   print('connect')

@sio.on('disconnect', namespace=None)
def disconnect(sid, message):
   print('Client disconnected')

@sio.on('ping', namespace='/entrance')
def ping(sid, message):
   print('ping')
   sio.emit('push_message', {"deviceIds":"test12345"}, room=sid, namespace='/entrance')

if __name__ == '__main__':
   if sio.async_mode == 'eventlet':
       print('eventlet')
       # deploy with eventlet
       import eventlet
       import eventlet.wsgi
       eventlet.wsgi.server(eventlet.listen(('', 10000)), app)
   else:
       print('Unknown async_mode: ' + sio.async_mode)

  • 运行app.py脚本,启动socket.io服务器,服务器此时订阅了namespace为 "/entrance"消息名为"ping"的消息,如果收到此消息,则发送"namespace为 "/entrance"消息名为"push_message"的消息到客户端,为了完成测试,所以需要客户端增加每隔几秒发送"ping"消息功能,上文客户端代码未添加,自己加一下即可完成测试。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的 C++ Crow Asio 应用程序示例,它基于异步 IO 编写,并使用 Boost 进行编译: ```c++ #include "crow_all.h" #include <boost/asio.hpp> #include <memory> int main() { // 创建一个 io_context 对象 auto io = std::make_shared<boost::asio::io_context>(); // 创建一个 Crow 应用程序对象,并将 io_context 对象传递给它 crow::SimpleApp app; CROW_ROUTE(app, "/")([](){ return "Hello, World!"; }); // 创建一个 TCP 服务器对象,并将应用程序对象和 io_context 对象传递给它 boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 8080); boost::asio::ip::tcp::acceptor acceptor(*io, endpoint); acceptor.listen(); // 开始监听客户端请求,并启动 io_context 对象的事件循环 boost::asio::spawn(acceptor.get_executor(), [&](boost::asio::yield_context yield){ while(true){ boost::system::error_code ec; boost::asio::ip::tcp::socket socket(*io); acceptor.async_accept(socket, yield[ec]); if(!ec){ boost::asio::spawn(socket.get_executor(), [&](boost::asio::yield_context yield){ try{ // 处理客户端请求 auto conn = std::make_shared<crow::Connection>(app, std::move(socket)); conn->start(); }catch(const std::exception& e){ std::cerr << "Exception in connection: " << e.what() << std::endl; } }); } } }); // 启动应用程序的主循环 app.port(8080).run(); // 启动 io_context 对象的事件循环 io->run(); return 0; } ``` 这个程序创建了一个简单的 Crow 应用程序,监听 8080 端口,并在客户端请求时返回 "Hello, World!"。其中 `boost::asio::spawn()` 方法用于在异步 IO 模式下处理客户端请求,同时启动了 `io_context` 对象的事件循环。在程序运行时,需要在终端中使用编译器和 Boost 进行编译,并执行编译后的可执行文件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值