一、服务器配置

  • 服务器在Ubuntu下搭建,使用C++语言实现,由于需要使用WebSocket和前端通讯,同时需要解析JSON格式,因此引入了第三方库:WebSocketppnlonlohmann,这两个库的具体配置方式可以参考我之前的博客,或者自行查找资料

二、WebSocket 连接

这里的WebSocket连接参考案例echo_server改造,具体就是设置好监听端口、对应的回调函数,包括:

  1. 接收到客户端连接
  2. 接收到客户端发送的信息
  3. 接收到客户端断开

对应的框架代码如下:

int main()
{try{// Set logging settings 设置logwebSocket_server.set_access_channels(websocketpp::log::alevel::all);webSocket_server.clear_access_channels(websocketpp::log::alevel::frame_payload);// Initialize Asio 初始化asiowebSocket_server.init_asio();// Register our message handler// 绑定收到消息后的回调webSocket_server.set_message_handler(bind(&on_message, &webSocket_server, ::_1, ::_2));// 当有客户端连接时触发的回调std::function<void(websocketpp::connection_hdl)> f_open;f_open = on_open;webSocket_server.set_open_handler(websocketpp::open_handler(f_open));// 关闭是触发std::function<void(websocketpp::connection_hdl)> f_close(on_close);webSocket_server.set_close_handler(f_close);// Listen on port 9002webSocket_server.listen(LISTEN_PORT); // 监听端口// Start the server accept loopwebSocket_server.start_accept();// Start the ASIO io_service run loopstd::cout << "Server is running on port " << LISTEN_PORT << std::endl;webSocket_server.run();}catch (websocketpp::exception const &e){std::cout << e.what() << std::endl;}catch (...){std::cout << "other exception" << std::endl;}
}

回调函数定义:

void on_close(websocketpp::connection_hdl hdl)
{}void on_open(websocketpp::connection_hdl hdl)
{
}void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{
}

三、房间管理

服务器中需要管理每一个房间内部的成员,包括成员的房间号、uid、以及对应的WebSocket连接句柄,因此我们定义这样的一个结构体Client

struct Client
{std::string uid;int roomId;websocketpp::connection_hdl hdl; // 连接句柄
};

每一个房间,都应该对应一个Client列表,因此我们使用std::map将每一个房间映射到一个容器std::vector<Client>中:

std::map<int, std::vector<Client>> room_map; // roomId - client

我们后面介绍信令的时候还会对房间的操作进行讲解,本质上就是根据业务逻辑对房间进行增删改查

四、信令处理

4.1 信令解析

服务器端定义的信令类型与前端一致,我们定义的信令类型如下:

#pragma once
//加入房间
#define SIGNAL_TYPE_JOIN "join"
//告知加入者是谁,发送给加入的人
#define SIGNAL_TYPE_RESP_JOIN "resp_join"
//离开房间
#define SIGNAL_TYPE_LEAVE  "leave"
//新加入者,发送给在房间的人
#define SIGNAL_TYPE_NEW_PEER "new_peer"
//告知离开者是谁
#define SIGNAL_TYPE_PEER_LEAVE "peer_leave"
//发送offer
#define SIGNAL_TYPE_OFFER "offer"
//对端回复
#define SIGNAL_TYPE_ANSWER "answer"
//发送candidate
#define SIGNAL_TYPE_CANDIDATE "candidate"

客户端接收到消息的时候会触发回调函数on_message,前端传来的消息是JSON格式,我们解析cmd字段后可以得到不同的信令类型,然后对不同类型的信令进行不同的逻辑:

// WebSocket服务器收到消息回调
void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{// 解析客户端的json消息json JMsg;try{JMsg = json::parse(msg->get_payload());std::cout << "on_message called with hdl: " << hdl.lock().get()<< " and message: " << JMsg.dump() << std::endl;std::string cmd = JMsg["cmd"];if (cmd == SIGNAL_TYPE_JOIN){handleJoin(s, hdl, JMsg); // 加入}else if (cmd == SIGNAL_TYPE_LEAVE){handleLeave(s, hdl, JMsg); // 离开}else if (cmd == SIGNAL_TYPE_OFFER){handleOffer(s, hdl, JMsg); // ice候选}else if (cmd == SIGNAL_TYPE_ANSWER){handleAnswer(s, hdl, JMsg);}else if (cmd == SIGNAL_TYPE_CANDIDATE){handleCandidate(s, hdl, JMsg);}}catch (const std::exception &e){std::cout << "JSON解析失败: " << e.what() << std::endl;return;}
}

4.2 join

接收到join这个信令说明此时有客户端加入服务器了,并且信令中携带加入者的房间号和uid,此时:

  1. 查询房间号是否存在,如果不存在,则创建一个房间,然后创建一个用户Client,在room_map中加入该用户的信息

  2. 房间号存在,那么查询房间的人数是否大于2人,如果大于2人,不予加入

  3. 房间人数为1人:

    • 将这个人加入房间room_map
    • 告诉房间里面的另一个人加入者的信息,也就是服务器发送信令peer_join信令给房间里面的人,包含加入者的uid
    • 告诉加入者房间里面的人的信息,也就是服务器发送信令resp_join信令给加入者,信令中包括房间里面的人的uid
// 处理加入房间
void handleJoin(server *s, websocketpp::connection_hdl hdl, json JMsg)
{// 解析JSONstd::string uid = JMsg["uid"];std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::cout << "uid = " << uid << " try to join roomId = " << roomId << std::endl;// 获取房间信息// 房间不存在if (!room_map.count(roomId)){Client client = {uid, roomId, hdl};room_map[roomId].push_back(client);}else{// 房间人数>=2,不允许加入if (room_map[roomId].size() >= 2){std::cout << "roomId = " << roomId << "is full" << std::endl;return;}// 房间人数==1,加入房间,通知对端else if (room_map[roomId].size() == 1){Client client = {uid, roomId, hdl};room_map[roomId].push_back(client);// 处理信令Client remoteClient = room_map[roomId][0];// 告知加入者对端的信息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_RESP_JOIN;sendMsg["remoteUid"] = remoteClient.uid;std::string sendMsgStr = sendMsg.dump();s->send(client.hdl, sendMsgStr, websocketpp::frame::opcode::text);std::cout << "resp_join uid = " << remoteClient.uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr << std::endl;// 告知对端加入者的身份json sendMsg2;sendMsg2["cmd"] = SIGNAL_TYPE_NEW_PEER;sendMsg2["remoteUid"] = uid;std::string sendMsgStr2 = sendMsg2.dump();s->send(remoteClient.hdl, sendMsgStr2, websocketpp::frame::opcode::text);std::cout << "new_peer uid = " << uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr2 << std::endl;}}
}

4.3 offer、answer、candidate

这部分实际上在Web端由浏览器提供的js接口实现具体的媒体协商、网络协商功能,我们的信令服务器只需要实现一件事情:信令转发,我们将收到的消息原封不动的转发给对端即可:

offer

// offer
void handleOffer(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send offer to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发offer到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << "send offer from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}

answer


// answer
void handleAnswer(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send answer to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发answer到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << "send answer from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}

candidate


// candidate
void handleCandidate(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send candidate to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发candidate到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << " send candidate from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}

4.4 leave

当有客户端离开房间的时候,向服务端发送leave信令,此时服务端需要做以下的工作:

  1. 检查房间是否存在,如果房间不存在,则不予处理

  2. 如果房间里面有一个人,那么我们此时直接房间里面这个人以及所在的房间

  3. 如果房间里面有两个人:

    • 我们需要通过查询信令中的uid,并且删除房间里面与uid一样的成员

    • 我们要向在房间里面的那个人发送信令peer_leave,同时包含remoteUid,告诉它房间里面的另一个人离开了

// 处理离开房间
void handleLeave(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::cout << "uid = " << uid << " try to leave roomId = " << roomId << std::endl;// 找不到房间if (!room_map.count(roomId)){std::cout << "房间不存在 !" << std::endl;return;}else{// 房间内只有一个人,删除房间if (room_map[roomId].size() == 1){room_map.erase(roomId);std::cout << "erase roomId = " << roomId << "success" << std::endl;}// 房间有两个人,通知对端离开else if (room_map[roomId].size() == 2){// 删除用户信息auto iter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&uid](const Client &client){ return client.uid == uid; });if (iter != room_map[roomId].end()){room_map[roomId].erase(iter);std::cout << "erase uid = " << uid << "success" << std::endl;}// 发送JSON消息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_PEER_LEAVE;sendMsg["remoteUid"] = uid;std::string sendMsgStr = sendMsg.dump();// 只有一个人了,使用room_map[roomId][0]s->send(room_map[roomId][0].hdl, sendMsgStr, websocketpp::frame::opcode::text);std::cout << "resp_leave uid = " << uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr << std::endl;}}
}

4.5 异常断开

当客户端强制断开的时候,不会向服务器发送leave信令,此时需要在WebSocket的回调函数on_close中处理:

  1. 遍历WebSocket连接链表,断开已经过期或者断开的连接

  2. 遍历room_map,查询出所有房间里面连接句柄与断开句柄一样的Client,找到了就删除它

    • 如果房间里面就只有一个人,那么删除了客户端Client之后,还需要删除这个房间
    • 如果房间里面有两个人,那么删除了它之后还需要告诉另一个人有人离开房间了,那么此时我们要向它发送peer_leave信令,信令中包含离开那个人的uid
// 用户断开连接回调函数
void on_close(websocketpp::connection_hdl hdl)
{std::string msg = "close OK";printf("%s\n", msg.c_str());std::cout << "vgdl size = " << vgdl.size() << std::endl;// 清理连接列表for (auto it = vgdl.begin(); it != vgdl.end();){std::cout << "it = " << it->lock() << std::endl;if (it->expired() || it->lock() == hdl.lock()) //断开自己{it = vgdl.erase(it);std::cout << "vgdl erase" << std::endl;}else{++it;}}// 遍历 room_map,删除对应客户端信息for (auto roomIt = room_map.begin(); roomIt != room_map.end();){auto &clients = roomIt->second;bool isErase = false;for (auto clientIt = clients.begin(); clientIt != clients.end();){if (clientIt->hdl.expired() || clientIt->hdl.lock() == hdl.lock()){ // 连接过期std::cout << "client uid = " << clientIt->uid << " has been removed from roomid = "<< clientIt->roomId << std::endl;clientIt = clients.erase(clientIt);isErase = true;}else{++clientIt;}}if(!isErase){continue;}// 如果房间为空,删除房间if (clients.empty()){std::cout << "roomId = " << (roomIt->first) << " has been removed" << std::endl;roomIt = room_map.erase(roomIt);}else{//向对端发送离开消息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_PEER_LEAVE;sendMsg["remoteUid"] = roomIt->second[0].uid;send_msg(&webSocket_server, sendMsg.dump());++roomIt;}}
}

五、测试结果

运行服务器,监听在9002,浏览器访问http://localhost:5500/index.html,连接本地两个端,效果如下:

在这里插入图片描述

六、完整代码

6.1 signal_type.h

#pragma once//加入房间
#define SIGNAL_TYPE_JOIN "join"//告知加入者是谁,发送给加入的人
#define SIGNAL_TYPE_RESP_JOIN "resp_join"//离开房间
#define SIGNAL_TYPE_LEAVE  "leave"//新加入者,发送给在房间的人
#define SIGNAL_TYPE_NEW_PEER "new_peer"//告知离开者是谁#define SIGNAL_TYPE_PEER_LEAVE "peer_leave"//发送offer
#define SIGNAL_TYPE_OFFER "offer"//对端回复
#define SIGNAL_TYPE_ANSWER "answer"//发送candidate
#define SIGNAL_TYPE_CANDIDATE "candidate"

6.2 main.cpp

// examples目录是官方的一些例子
// 本次使用的是webSocket_server\webSocket_server.cpp
// 该原程序只支持一对一发送后回复
// 改造后可以通知所有连接上来的客户端。
// 编译 g++ main.cpp -o main -lboost_system -lboost_chrono#include <functional>
#include <iostream>
#include <list>
#include <mutex> // 添加互斥锁头文件
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <string>// json解析
#include <nlohmann/json.hpp>#include "signal_type.h"
using json = nlohmann::json;typedef websocketpp::server<websocketpp::config::asio> server;using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;// pull out the type of messages sent by our config
typedef server::message_ptr message_ptr;const int LISTEN_PORT = 9002;
std::list<websocketpp::connection_hdl> vgdl;
std::mutex vgdl_mutex; // 添加互斥锁保护连接列表struct Client
{std::string uid;int roomId;websocketpp::connection_hdl hdl; // 连接句柄
};std::map<int, std::vector<Client>> room_map; // roomId - client// Define a callback to handle incoming messages// Create a server endpoint
server webSocket_server;int totalUser = 0;
void send_msg(server *s, message_ptr msg)
{for (auto it = vgdl.begin(); it != vgdl.end();){if (!it->expired()){try{s->send(*it, msg->get_payload(), msg->get_opcode());}catch (websocketpp::exception const &e){std::cout << "Broadcast failed because: " << e.what()<< std::endl;}++it; // 只有在未删除元素时才递增迭代器}}
}void send_msg(server *s, std::string msg)
{for (auto it = vgdl.begin(); it != vgdl.end();){if (!it->expired()){try{s->send(*it, msg, websocketpp::frame::opcode::text);}catch (websocketpp::exception const &e){std::cout << "Broadcast failed because: " << e.what()<< std::endl;}++it; // 只有在未删除元素时才递增迭代器}}
}// 处理加入房间
void handleJoin(server *s, websocketpp::connection_hdl hdl, json JMsg)
{// 解析JSONstd::string uid = JMsg["uid"];std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::cout << "uid = " << uid << " try to join roomId = " << roomId << std::endl;// 获取房间信息// 房间不存在if (!room_map.count(roomId)){Client client = {uid, roomId, hdl};room_map[roomId].push_back(client);}else{// 房间人数>=2,不允许加入if (room_map[roomId].size() >= 2){std::cout << "roomId = " << roomId << "is full" << std::endl;return;}// 房间人数==1,加入房间,通知对端else if (room_map[roomId].size() == 1){Client client = {uid, roomId, hdl};room_map[roomId].push_back(client);// 处理信令Client remoteClient = room_map[roomId][0];// 告知加入者对端的信息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_RESP_JOIN;sendMsg["remoteUid"] = remoteClient.uid;std::string sendMsgStr = sendMsg.dump();s->send(client.hdl, sendMsgStr, websocketpp::frame::opcode::text);std::cout << "resp_join uid = " << remoteClient.uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr << std::endl;// 告知对端加入者的身份json sendMsg2;sendMsg2["cmd"] = SIGNAL_TYPE_NEW_PEER;sendMsg2["remoteUid"] = uid;std::string sendMsgStr2 = sendMsg2.dump();s->send(remoteClient.hdl, sendMsgStr2, websocketpp::frame::opcode::text);std::cout << "new_peer uid = " << uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr2 << std::endl;}}
}// 处理离开房间
void handleLeave(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::cout << "uid = " << uid << " try to leave roomId = " << roomId << std::endl;// 找不到房间if (!room_map.count(roomId)){std::cout << "房间不存在 !" << std::endl;return;}else{// 房间内只有一个人,删除房间if (room_map[roomId].size() == 1){room_map.erase(roomId);std::cout << "erase roomId = " << roomId << "success" << std::endl;}// 房间有两个人,通知对端离开else if (room_map[roomId].size() == 2){// 删除用户信息auto iter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&uid](const Client &client){ return client.uid == uid; });if (iter != room_map[roomId].end()){room_map[roomId].erase(iter);std::cout << "erase uid = " << uid << "success" << std::endl;}// 发送JSON消息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_PEER_LEAVE;sendMsg["remoteUid"] = uid;std::string sendMsgStr = sendMsg.dump();// 只有一个人了,使用room_map[roomId][0]s->send(room_map[roomId][0].hdl, sendMsgStr, websocketpp::frame::opcode::text);std::cout << "resp_leave uid = " << uid << std::endl;std::cout << "sendMsgStr = " << sendMsgStr << std::endl;}}
}// offer
void handleOffer(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send offer to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发offer到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << "send offer from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}// answer
void handleAnswer(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send answer to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发answer到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << "send answer from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}// candidate
void handleCandidate(server *s, websocketpp::connection_hdl hdl, json JMsg)
{std::string roomStr = JMsg["roomId"];int roomId = stoi(roomStr);std::string uid = JMsg["uid"];std::string remoteUid = JMsg["remoteUid"];std::cout << "uid = " << uid << " try to send candidate to remoteUid = " << remoteUid << std::endl;// 房间号不存在if (!room_map.count(roomId)){std::cout << "roomId = " << roomId << "not exist" << std::endl;return;}// 房间没人else if (room_map[roomId].size() == 0){std::cout << "roomId = " << roomId << "is empty" << std::endl;return;}else{// 转发candidate到对端auto remoteClientIter = std::find_if(room_map[roomId].begin(), room_map[roomId].end(), [&remoteUid](const Client &client){ return client.uid == remoteUid; });if (remoteClientIter != room_map[roomId].end()){std::cout << " send candidate from " << uid << " to " << remoteUid << std::endl;s->send(remoteClientIter->hdl, JMsg.dump(), websocketpp::frame::opcode::text);}else{std::cout << "remoteUid = " << remoteUid << "not exist" << std::endl;}}
}// WebSocket服务器收到消息回调
void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{// 解析客户端的json消息json JMsg;try{JMsg = json::parse(msg->get_payload());std::cout << "on_message called with hdl: " << hdl.lock().get()<< " and message: " << JMsg.dump() << std::endl;std::string cmd = JMsg["cmd"];if (cmd == SIGNAL_TYPE_JOIN){handleJoin(s, hdl, JMsg); // 加入}else if (cmd == SIGNAL_TYPE_LEAVE){handleLeave(s, hdl, JMsg); // 离开}else if (cmd == SIGNAL_TYPE_OFFER){handleOffer(s, hdl, JMsg); // ice候选}else if (cmd == SIGNAL_TYPE_ANSWER){handleAnswer(s, hdl, JMsg);}else if (cmd == SIGNAL_TYPE_CANDIDATE){handleCandidate(s, hdl, JMsg);}}catch (const std::exception &e){std::cout << "JSON解析失败: " << e.what() << std::endl;return;}
}// 当有客户端连接时触发的回调
void on_open(websocketpp::connection_hdl hdl)
{vgdl.push_back(hdl);std::cout << "on_open called with hdl: " << hdl.lock().get() << std::endl;
}// 用户断开连接回调函数
void on_close(websocketpp::connection_hdl hdl)
{std::string msg = "close OK";printf("%s\n", msg.c_str());std::cout << "vgdl size = " << vgdl.size() << std::endl;// 清理连接列表for (auto it = vgdl.begin(); it != vgdl.end();){std::cout << "it = " << it->lock() << std::endl;if (it->expired() || it->lock() == hdl.lock()) //断开自己{it = vgdl.erase(it);std::cout << "vgdl erase" << std::endl;}else{++it;}}// 遍历 room_map,删除对应客户端信息for (auto roomIt = room_map.begin(); roomIt != room_map.end();){auto &clients = roomIt->second;bool isErase = false;for (auto clientIt = clients.begin(); clientIt != clients.end();){if (clientIt->hdl.expired() || clientIt->hdl.lock() == hdl.lock()){ // 连接过期std::cout << "client uid = " << clientIt->uid << " has been removed from roomid = "<< clientIt->roomId << std::endl;clientIt = clients.erase(clientIt);isErase = true;}else{++clientIt;}}if(!isErase){continue;}// 如果房间为空,删除房间if (clients.empty()){std::cout << "roomId = " << (roomIt->first) << " has been removed" << std::endl;roomIt = room_map.erase(roomIt);}else{//向对端发送离开消息json sendMsg;sendMsg["cmd"] = SIGNAL_TYPE_PEER_LEAVE;sendMsg["remoteUid"] = roomIt->second[0].uid;send_msg(&webSocket_server, sendMsg.dump());++roomIt;}}
}int main()
{try{// Set logging settings 设置logwebSocket_server.set_access_channels(websocketpp::log::alevel::all);webSocket_server.clear_access_channels(websocketpp::log::alevel::frame_payload);// Initialize Asio 初始化asiowebSocket_server.init_asio();// Register our message handler// 绑定收到消息后的回调webSocket_server.set_message_handler(bind(&on_message, &webSocket_server, ::_1, ::_2));// 当有客户端连接时触发的回调std::function<void(websocketpp::connection_hdl)> f_open;f_open = on_open;webSocket_server.set_open_handler(websocketpp::open_handler(f_open));// 关闭是触发std::function<void(websocketpp::connection_hdl)> f_close(on_close);webSocket_server.set_close_handler(f_close);// Listen on port 9002webSocket_server.listen(LISTEN_PORT); // 监听端口// Start the server accept loopwebSocket_server.start_accept();// Start the ASIO io_service run loopstd::cout << "Server is running on port " << LISTEN_PORT << std::endl;webSocket_server.run();}catch (websocketpp::exception const &e){std::cout << e.what() << std::endl;}catch (...){std::cout << "other exception" << std::endl;}
}

更多资料:https://github.com/0voice

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring(以 Spring Boot 为核心)与 JDK、Maven、MyBatis-Plus、Tomcat 的版本对应关系及关键注意事项

以下是 Spring&#xff08;以 Spring Boot 为核心&#xff09;与 JDK、Maven、MyBatis-Plus、Tomcat 的版本对应关系及关键注意事项&#xff0c;基于最新技术生态整理&#xff1a; 一、Spring Boot 与 JDK 版本对应 Spring Boot 2.x 系列 最低要求&#xff1a;JDK 1.8推荐版本…

03-基于深度学习的钢铁缺陷检测-yolo11-彩色版界面

目录 项目介绍&#x1f3af; 功能展示&#x1f31f; 一、环境安装&#x1f386; 环境配置说明&#x1f4d8; 安装指南说明&#x1f3a5; 环境安装教学视频 &#x1f31f; 二、系统环境&#xff08;框架/依赖库&#xff09;说明&#x1f9f1; 系统环境与依赖配置说明&#x1f4c…

24. 前端-js框架-Vue

文章目录前言一、Vue介绍1. 学习导图2. 特点3. 安装1. 方式一&#xff1a;独立版本2. 方式二&#xff1a;CDN方法3. 方式三&#xff1a;NPM方法&#xff08;推荐使用&#xff09;4. 搭建Vue的开发环境&#xff08;大纲&#xff09;5. 工程结构6. 安装依赖资源7. 运行项目8. Vue…

Spring 的依赖注入DI是什么?

口语化答案好的&#xff0c;面试官&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是Spring框架实现控制反转&#xff08;IoC&#xff09;的主要手段。DI的核心思想是将对象的依赖关系从对象内部抽离出来&#xff0c;通过外部注入的方式提…

汇川PLC通过ModbusTCP转Profinet网关连接西门子PLC配置案例

本案例是汇川的PLC通过开疆智能研发的ModbusTCP转Profient网关读写西门子1200PLC中的数据。汇川PLC作为ModbusTCP的客户端网关作为服务器&#xff0c;在Profinet一侧网关作为从站接收1200PLC的数据并转成ModbusTCP协议被汇川PLC读取。配置过程&#xff1a;汇川PLC配置Modbus TC…

【计组】数据的表示与运算

机器数与真值机器数真值编码原码特点表示范围求真值方法反码特点补码特点表示范围求真值方法移码特点表示范围求真值方法相互转换原码<->补码补码<->移码原码<->反码反码<->补码移位左移右移逻辑右移算术右移符号扩展零扩展整数小数符号扩展运算器部件…

视频水印技术中的变换域嵌入方法对比分析

1. 引言 随着数字视频技术的快速发展和网络传输的普及,视频内容的版权保护问题日益突出。视频水印技术作为一种有效的版权保护手段,通过在视频中嵌入不可见或半可见的标识信息,实现对视频内容的所有权认证、完整性验证和盗版追踪。在视频水印技术的发展历程中,变换域水印因…

电动汽车电池管理系统设计与实现

电动汽车电池管理系统设计与实现 1. 引言 电动汽车电池管理系统(BMS)是确保电池组安全、高效运行的关键组件。本文将详细介绍一个完整的BMS系统的MATLAB实现,包括状态估计(SOC/SOH)、参数监测、电池平衡和保护功能。系统设计为模块化结构,便于扩展和参数调整。 2. 系统架构…

JVM(Java Virtual Machine,Java 虚拟机)超详细总结

一、JVM的基础概念1、概述JVM是 Java 程序的运行基础环境&#xff0c;是 Java 语言实现 “一次编写&#xff0c;到处运行” &#xff08;"write once , run anywhere. "&#xff09;特性的关键组件&#xff0c;具体从以下几个方面来理解&#xff1a;概念层面JVM 是一…

Balabolka软件调用微软离线自然语音合成进行文字转语音下载安装教程

首先&#xff0c;需要准备安装包 Balabolka NaturalVoiceSAPIAdapterMicrosoftWindows.Voice.zh-CN.Xiaoxiao.1_1.0.9.0_x64__cw5n1h2txyewy.Msix MicrosoftWindows.Voice.zh-CN.Yunxi.1_1.0.4.0_x64__cw5n1h2txyewy.Msix借助上面这个工具&#xff1a;NaturalVoiceSAPIAdapter&…

Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

导航&#xff1a; 【Java笔记踩坑汇总】Java基础JavaWebSSMSpringBootSpringCloud瑞吉外卖/谷粒商城/学成在线设计模式面试题汇总性能调优/架构设计源码解析 推荐视频&#xff1a; 黑马程序员全套Java教程_哔哩哔哩 尚硅谷Java入门视频教程_哔哩哔哩 推荐书籍&#xff1a; 《Ja…

接口测试用例和接口测试模板

一、简介 3天精通Postman接口测试&#xff0c;全套项目实战教程&#xff01;&#xff01;接口测试区别于传统意义上的系统测试&#xff0c;下面介绍接口测试用例和接口测试报告。 二、接口测试用例模板 功能测试用例最重要的两个因素是测试步骤和预期结果&#xff0c;接口测试…

linux查看kafka的消费组里是否有积压

flink消费数据时&#xff0c;有时候需要在页面展示的数据&#xff0c;不能实时展示。那就需要查看下&#xff0c;kafka的消费组里是否有数据积压了。flink的任务flink的消费情况kafka中的信息总结可以看出来&#xff0c;kafka的消费组里的数据&#xff0c;已经实时的消费完了。…

【Unity笔记】Unity 音游模板与免费资源:高效构建节奏游戏开发全指南

Unity 音游模板与免费资源&#xff1a;高效构建节奏游戏开发全指南 文章摘要&#xff1a; 本文为Unity开发者提供一套针对下落式与轨道式音乐游戏的实用模板工程与免费资源指南&#xff0c;内容涵盖项目目录结构、核心功能模块、视觉特效与音效素材、开源脚本框架及辅助打谱工具…

【RabbitMQ】高级特性—持久性、重试机制详解

持久性 我们在前面说了消息端处理消息时&#xff0c;消息如何不丢失&#xff0c;但是如何保证当 RabbitMQ 服务器停掉之后&#xff0c;生产者发送的消息不丢失呢&#xff1f; 默认情况下&#xff0c;RabbitMQ 退出或者由于某种原因崩溃时&#xff0c;会忽视队列和消息&#xff…

零基础人工智能学习规划之路

一、引言&#xff1a;为什么选择人工智能&#xff1f;人工智能&#xff08;AI&#xff09;是当前科技领域最炙手可热的方向之一&#xff0c;涵盖机器学习、深度学习、计算机视觉、自然语言处理等多个分支。无论是就业市场的高需求&#xff0c;还是技术改变生活的潜力&#xff0…

【科研绘图系列】R语言绘制误差棒图

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图 系统信息 参考 介绍 【科研绘图系列】R语言绘制误差棒图 加载R包 library(tidyverse) library(ggplot2) library(ggsignif) library(RColorBrewer) library(waterfalls) library(reshape2

期权定价全解析:从Black-Scholes到量子革命的金融基石

在金融市场中,期权定价如同航海中的罗盘,为风险定价提供方向。本文将深入剖析期权定价的核心逻辑、应用场景及量子计算带来的颠覆性变革,并附实战代码示例。 一、期权定价的本质:风险的时间价值 1. 核心公式解析 C = e^{-rT}\mathbb{E}^\mathbb{Q}[\max(S_T-K,0)] C:期权…

实现div内容的垂直居中

Flexbox 弹性盒子&#xff08;推荐&#xff09; div {display: flex;align-items: center; /* 垂直居中 */justify-content: center;/* 水平居中 */height: 300px; /* 需要指定高度 */ }✅ 现代浏览器首选方案&#xff0c;支持响应式布局 Grid 网格布局 div {displ…

Juc高级篇:可见性,有序性,cas,不可变,设计模式

目录 一.Java内存模型 1.可见性 1.1设计模式 (1.1.1)两阶段终止 (1.1.2)Balking模式 2.有序性 3.volatile原理 3.1保证可见性与有序性 3.2单例模式DCL 3.3 happens-before规则 4.线程安全单例 4.1饿汉式 二.无锁并发 1.原子整数 2.原子引用 2.1 AtomicReference…