一、服務器配置
- 服務器在
Ubuntu
下搭建,使用C++
語言實現,由于需要使用WebSocket
和前端通訊,同時需要解析JSON
格式,因此引入了第三方庫:WebSocketpp
和nlonlohmann
,這兩個庫的具體配置方式可以參考我之前的博客,或者自行查找資料
二、WebSocket 連接
這里的WebSocket
連接參考案例echo_server
改造,具體就是設置好監聽端口、對應的回調函數,包括:
- 接收到客戶端連接
- 接收到客戶端發送的信息
- 接收到客戶端斷開
對應的框架代碼如下:
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
,此時:
-
查詢房間號是否存在,如果不存在,則創建一個房間,然后創建一個用戶
Client
,在room_map
中加入該用戶的信息 -
房間號存在,那么查詢房間的人數是否大于
2
人,如果大于2
人,不予加入 -
房間人數為
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
信令,此時服務端需要做以下的工作:
-
檢查房間是否存在,如果房間不存在,則不予處理
-
如果房間里面有一個人,那么我們此時直接房間里面這個人以及所在的房間
-
如果房間里面有兩個人:
-
我們需要通過查詢信令中的
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
中處理:
-
遍歷
WebSocket
連接鏈表,斷開已經過期或者斷開的連接 -
遍歷
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