【音視頻】WebRTC 一對一通話-信令服

一、服務器配置

  • 服務器在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…