一、服务器配置
- 服务器在
Ubuntu
下搭建,使用C++
语言实现,由于需要使用WebSocket
和前端通讯,同时需要解析JSON
格式,因此引入了第三方库:WebSocketpp
和nlonlohmann
,这两个库的具体配置方式可以参考我之前的博客,或者自行查找资料
二、WebSocket 连接
这里的WebSocket
连接参考案例echo_server
改造,具体就是设置好监听端口、对应的回调函数,包括:
- 接收到客户端连接
- 接收到客户端发送的信息
- 接收到客户端断开
对应的框架代码如下:
cpp
int main()
{
try
{
// Set logging settings 设置log
webSocket_server.set_access_channels(websocketpp::log::alevel::all);
webSocket_server.clear_access_channels(
websocketpp::log::alevel::frame_payload);
// Initialize Asio 初始化asio
webSocket_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 9002
webSocket_server.listen(LISTEN_PORT); // 监听端口
// Start the server accept loop
webSocket_server.start_accept();
// Start the ASIO io_service run loop
std::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;
}
}
回调函数定义:
cpp
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
cpp
struct Client
{
std::string uid;
int roomId;
websocketpp::connection_hdl hdl; // 连接句柄
};
每一个房间,都应该对应一个Client
列表,因此我们使用std::map
将每一个房间映射到一个容器std::vector<Client>
中:
cpp
std::map<int, std::vector<Client>> room_map; // roomId - client
我们后面介绍信令的时候还会对房间的操作进行讲解,本质上就是根据业务逻辑对房间进行增删改查
四、信令处理
4.1 信令解析
服务器端定义的信令类型与前端一致,我们定义的信令类型如下:
cpp
#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
字段后可以得到不同的信令类型,然后对不同类型的信令进行不同的逻辑:
cpp
// 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
- 将这个人加入房间
cpp
// 处理加入房间
void handleJoin(server *s, websocketpp::connection_hdl hdl, json JMsg)
{
// 解析JSON
std::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:
cpp
// 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:
cpp
// 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:
cpp
// 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
,告诉它房间里面的另一个人离开了
-
cpp
// 处理离开房间
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
- 如果房间里面就只有一个人,那么删除了客户端
cpp
// 用户断开连接回调函数
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
cpp
#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
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)
{
// 解析JSON
std::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 设置log
webSocket_server.set_access_channels(websocketpp::log::alevel::all);
webSocket_server.clear_access_channels(
websocketpp::log::alevel::frame_payload);
// Initialize Asio 初始化asio
webSocket_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 9002
webSocket_server.listen(LISTEN_PORT); // 监听端口
// Start the server accept loop
webSocket_server.start_accept();
// Start the ASIO io_service run loop
std::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;
}
}