You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
18 KiB
C++

/*
* Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Util/uv_errno.h"
#include "Util/onceToken.h"
#include "UdpServer.h"
using namespace std;
namespace toolkit {
static const uint8_t s_in6_addr_maped[]
= { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 };
static constexpr auto kUdpDelayCloseMS = 3 * 1000;
static UdpServer::PeerIdType makeSockId(sockaddr *addr, int) {
UdpServer::PeerIdType ret;
switch (addr->sa_family) {
case AF_INET : {
ret.resize(18);
ret[0] = ((struct sockaddr_in *) addr)->sin_port >> 8;
ret[1] = ((struct sockaddr_in *) addr)->sin_port & 0xFF;
//ipv4地址统一转换为ipv6方式处理 [AUTO-TRANSLATED:ad7cf8c3]
//Convert ipv4 addresses to ipv6 for unified processing
memcpy(&ret[2], &s_in6_addr_maped, 12);
memcpy(&ret[14], &(((struct sockaddr_in *) addr)->sin_addr), 4);
return ret;
}
case AF_INET6 : {
ret.resize(18);
ret[0] = ((struct sockaddr_in6 *) addr)->sin6_port >> 8;
ret[1] = ((struct sockaddr_in6 *) addr)->sin6_port & 0xFF;
memcpy(&ret[2], &(((struct sockaddr_in6 *)addr)->sin6_addr), 16);
return ret;
}
default: assert(0); return "";
}
}
UdpServer::UdpServer(const EventPoller::Ptr &poller) : Server(poller) {
_multi_poller = !poller;
setOnCreateSocket(nullptr);
}
void UdpServer::setupEvent() {
_socket = createSocket(_poller);
std::weak_ptr<UdpServer> weak_self = std::static_pointer_cast<UdpServer>(shared_from_this());
_socket->setOnRead([weak_self](Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
if (auto strong_self = weak_self.lock()) {
strong_self->onRead(buf, addr, addr_len);
}
});
}
UdpServer::~UdpServer() {
if (!_cloned && _socket && _socket->rawFD() != -1) {
InfoL << "Close udp server [" << _socket->get_local_ip() << "]: " << _socket->get_local_port();
}
_timer.reset();
_socket.reset();
_cloned_server.clear();
if (!_cloned && _session_mutex && _session_map) {
lock_guard<std::recursive_mutex> lck(*_session_mutex);
_session_map->clear();
}
}
void UdpServer::start_l(uint16_t port, const std::string &host) {
setupEvent();
//主server才创建session map其他cloned server共享之 [AUTO-TRANSLATED:113cf4fd]
//Only the main server creates a session map, other cloned servers share it
_session_mutex = std::make_shared<std::recursive_mutex>();
_session_map = std::make_shared<std::unordered_map<PeerIdType, SessionHelper::Ptr> >();
// 新建一个定时器定时管理这些 udp 会话,这些对象只由主server做超时管理cloned server不管理 [AUTO-TRANSLATED:d20478a2]
//Create a timer to manage these udp sessions periodically, these objects are only managed by the main server, cloned servers do not manage them
std::weak_ptr<UdpServer> weak_self = std::static_pointer_cast<UdpServer>(shared_from_this());
_timer = std::make_shared<Timer>(2.0f, [weak_self]() -> bool {
if (auto strong_self = weak_self.lock()) {
strong_self->onManagerSession();
return true;
}
return false;
}, _poller);
if (_multi_poller) {
// clone server至不同线程让udp server支持多线程 [AUTO-TRANSLATED:15a85c8f]
//Clone the server to different threads to support multi-threading for the udp server
EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor) {
auto poller = std::static_pointer_cast<EventPoller>(executor);
if (poller == _poller) {
return;
}
auto &serverRef = _cloned_server[poller.get()];
if (!serverRef) {
serverRef = onCreatServer(poller);
}
if (serverRef) {
serverRef->cloneFrom(*this);
}
});
}
if (!_socket->bindUdpSock(port, host.c_str())) {
// udp 绑定端口失败, 可能是由于端口占用或权限问题 [AUTO-TRANSLATED:c31eedba]
//Failed to bind udp port, possibly due to port occupation or permission issues
std::string err = (StrPrinter << "Bind udp socket on " << host << " " << port << " failed: " << get_uv_errmsg(true));
throw std::runtime_error(err);
}
for (auto &pr: _cloned_server) {
// 启动子Server [AUTO-TRANSLATED:1820131c]
//Start the child server
#if 0
pr.second->_socket->cloneSocket(*_socket);
#else
// 实验发现cloneSocket方式虽然可以节省fd资源但是在某些系统上线程漂移问题更严重 [AUTO-TRANSLATED:d6a88e17]
//Experiments have found that the cloneSocket method can save fd resources, but the thread drift problem is more serious on some systems
pr.second->_socket->bindUdpSock(_socket->get_local_port(), _socket->get_local_ip());
#endif
}
InfoL << "UDP server bind to [" << host << "]: " << port;
}
UdpServer::Ptr UdpServer::onCreatServer(const EventPoller::Ptr &poller) {
return Ptr(new UdpServer(poller), [poller](UdpServer *ptr) { poller->async([ptr]() { delete ptr; }); });
}
void UdpServer::cloneFrom(const UdpServer &that) {
if (!that._socket) {
throw std::invalid_argument("UdpServer::cloneFrom other with null socket");
}
setupEvent();
_cloned = true;
// clone callbacks
_on_create_socket = that._on_create_socket;
_session_alloc = that._session_alloc;
_session_mutex = that._session_mutex;
_session_map = that._session_map;
// clone properties
this->mINI::operator=(that);
}
void UdpServer::onRead(Buffer::Ptr &buf, sockaddr *addr, int addr_len) {
const auto id = makeSockId(addr, addr_len);
onRead_l(true, id, buf, addr, addr_len);
}
static void emitSessionRecv(const SessionHelper::Ptr &helper, const Buffer::Ptr &buf) {
if (!helper->enable) {
// 延时销毁中 [AUTO-TRANSLATED:24d3d333]
//Delayed destruction in progress
return;
}
try {
helper->session()->onRecv(buf);
} catch (SockException &ex) {
helper->session()->shutdown(ex);
} catch (exception &ex) {
helper->session()->shutdown(SockException(Err_shutdown, ex.what()));
}
}
void UdpServer::onRead_l(bool is_server_fd, const UdpServer::PeerIdType &id, Buffer::Ptr &buf, sockaddr *addr, int addr_len) {
// udp server fd收到数据时触发此函数大部分情况下数据应该在peer fd触发此函数应该不是热点函数 [AUTO-TRANSLATED:f347ff20]
//This function is triggered when the udp server fd receives data; in most cases, the data should be triggered by the peer fd, and this function should not be a hot spot
bool is_new = false;
if (auto helper = getOrCreateSession(id, buf, addr, addr_len, is_new)) {
if (helper->session()->getPoller()->isCurrentThread()) {
//当前线程收到数据,直接处理数据 [AUTO-TRANSLATED:07e5a596]
//The current thread receives data and processes it directly
emitSessionRecv(helper, buf);
} else {
//数据漂移到其他线程,需要先切换线程 [AUTO-TRANSLATED:15235f6f]
//Data migration to another thread requires switching threads first
WarnL << "UDP packet incoming from other thread";
std::weak_ptr<SessionHelper> weak_helper = helper;
//由于socket读buffer是该线程上所有socket共享复用的所以不能跨线程使用必须先转移走 [AUTO-TRANSLATED:1134538b]
//Since the socket read buffer is shared and reused by all sockets on this thread, it cannot be used across threads and must be transferred first
auto cacheable_buf = std::move(buf);
helper->session()->async([weak_helper, cacheable_buf]() {
if (auto strong_helper = weak_helper.lock()) {
emitSessionRecv(strong_helper, cacheable_buf);
}
});
}
#if !defined(NDEBUG)
if (!is_new) {
TraceL << "UDP packet incoming from " << (is_server_fd ? "server fd" : "other peer fd");
}
#endif
}
}
void UdpServer::onManagerSession() {
decltype(_session_map) copy_map;
{
std::lock_guard<std::recursive_mutex> lock(*_session_mutex);
//拷贝map防止遍历时移除对象 [AUTO-TRANSLATED:ebbc7595]
//Copy the map to prevent objects from being removed during traversal
copy_map = std::make_shared<std::unordered_map<PeerIdType, SessionHelper::Ptr> >(*_session_map);
}
auto lam = [copy_map]() {
for (auto &pr : *copy_map) {
auto &session = pr.second->session();
if (!session->getPoller()->isCurrentThread()) {
// 该session不归属该poller管理 [AUTO-TRANSLATED:d5edb552]
//This session does not belong to the management of this poller
continue;
}
try {
// UDP 会话需要处理超时 [AUTO-TRANSLATED:0a51f8a1]
//UDP sessions need to handle timeouts
session->onManager();
} catch (exception &ex) {
WarnL << "Exception occurred when emit onManager: " << ex.what();
}
}
};
if (_multi_poller){
EventPollerPool::Instance().for_each([lam](const TaskExecutor::Ptr &executor) {
std::static_pointer_cast<EventPoller>(executor)->async(lam);
});
} else {
lam();
}
}
SessionHelper::Ptr UdpServer::getOrCreateSession(const UdpServer::PeerIdType &id, Buffer::Ptr &buf, sockaddr *addr, int addr_len, bool &is_new) {
{
//减小临界区 [AUTO-TRANSLATED:3d6089d8]
//Reduce the critical section
std::lock_guard<std::recursive_mutex> lock(*_session_mutex);
auto it = _session_map->find(id);
if (it != _session_map->end()) {
return it->second;
}
}
is_new = true;
return createSession(id, buf, addr, addr_len);
}
SessionHelper::Ptr UdpServer::createSession(const PeerIdType &id, Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
// 此处改成自定义获取poller对象防止负载不均衡 [AUTO-TRANSLATED:194e8460]
//Change to custom acquisition of poller objects to prevent load imbalance
auto socket = createSocket(_multi_poller ? EventPollerPool::Instance().getPoller(false) : _poller, buf, addr, addr_len);
if (!socket) {
//创建socket失败本次onRead事件收到的数据直接丢弃 [AUTO-TRANSLATED:b218d68c]
//Socket creation failed, the data received by this onRead event is discarded
return nullptr;
}
auto addr_str = string((char *) addr, addr_len);
std::weak_ptr<UdpServer> weak_self = std::static_pointer_cast<UdpServer>(shared_from_this());
auto helper_creator = [this, weak_self, socket, addr_str, id]() -> SessionHelper::Ptr {
auto server = weak_self.lock();
if (!server) {
return nullptr;
}
//如果已经创建该客户端对应的UdpSession类那么直接返回 [AUTO-TRANSLATED:c57a0d71]
//If the UdpSession class corresponding to this client has already been created, return directly
lock_guard<std::recursive_mutex> lck(*_session_mutex);
auto it = _session_map->find(id);
if (it != _session_map->end()) {
return it->second;
}
assert(_socket);
socket->bindUdpSock(_socket->get_local_port(), _socket->get_local_ip());
socket->bindPeerAddr((struct sockaddr *) addr_str.data(), addr_str.size());
auto helper = _session_alloc(server, socket);
// 把本服务器的配置传递给 Session [AUTO-TRANSLATED:e3ed95ab]
//Pass the configuration of this server to the Session
helper->session()->attachServer(*this);
std::weak_ptr<SessionHelper> weak_helper = helper;
socket->setOnRead([weak_self, weak_helper, id](Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
//快速判断是否为本会话的的数据, 通常应该成立 [AUTO-TRANSLATED:d5d147e4]
//Quickly determine if it's data for the current session, usually should be true
if (id == makeSockId(addr, addr_len)) {
if (auto strong_helper = weak_helper.lock()) {
emitSessionRecv(strong_helper, buf);
}
return;
}
//收到非本peer fd的数据让server去派发此数据到合适的session对象 [AUTO-TRANSLATED:e5f44445]
//Received data from a non-current peer fd, let the server dispatch this data to the appropriate session object
strong_self->onRead_l(false, id, buf, addr, addr_len);
});
socket->setOnErr([weak_self, weak_helper, id](const SockException &err) {
// 在本函数作用域结束时移除会话对象 [AUTO-TRANSLATED:b2ade305]
//Remove the session object when this function scope ends
// 目的是确保移除会话前执行其 onError 函数 [AUTO-TRANSLATED:7d0329d7]
//The purpose is to ensure the onError function is executed before removing the session
// 同时避免其 onError 函数抛异常时没有移除会话对象 [AUTO-TRANSLATED:354191bd]
//And avoid not removing the session object when its onError function throws an exception
onceToken token(nullptr, [&]() {
// 移除掉会话 [AUTO-TRANSLATED:1d786335]
//Remove the session
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
// 延时移除udp session, 防止频繁快速重建对象 [AUTO-TRANSLATED:50dbd694]
//Delay removing the UDP session to prevent frequent and rapid object reconstruction
strong_self->_poller->doDelayTask(kUdpDelayCloseMS, [weak_self, id]() {
if (auto strong_self = weak_self.lock()) {
// 从共享map中移除本session对象 [AUTO-TRANSLATED:47ecbf11]
//Remove the current session object from the shared map
lock_guard<std::recursive_mutex> lck(*strong_self->_session_mutex);
strong_self->_session_map->erase(id);
}
return 0;
});
});
// 获取会话强应用 [AUTO-TRANSLATED:42283ea0]
//Get a strong reference to the session
if (auto strong_helper = weak_helper.lock()) {
// 触发 onError 事件回调 [AUTO-TRANSLATED:82070c3c]
//Trigger the onError event callback
TraceP(strong_helper->session()) << strong_helper->className() << " on err: " << err;
strong_helper->enable = false;
strong_helper->session()->onError(err);
}
});
auto pr = _session_map->emplace(id, std::move(helper));
assert(pr.second);
return pr.first->second;
};
if (socket->getPoller()->isCurrentThread()) {
// 该socket分配在本线程直接创建helper对象 [AUTO-TRANSLATED:18c9d95b]
//This socket is allocated in this thread, directly create a helper object
return helper_creator();
}
// 该socket分配在其他线程需要先转移走buffer然后在其所在线程创建helper对象并处理数据 [AUTO-TRANSLATED:7816a13f]
//This socket is allocated in another thread, need to transfer the buffer first, then create a helper object in its thread and process the data
auto cacheable_buf = std::move(buf);
socket->getPoller()->async([helper_creator, cacheable_buf]() {
// 在该socket所在线程创建helper对象 [AUTO-TRANSLATED:db8d6622]
//Create a helper object in the thread where the socket is located
auto helper = helper_creator();
if (helper) {
// 可能未实质创建hlepr对象成功可能获取到其他线程创建的helper对象 [AUTO-TRANSLATED:091f648e]
//May not have actually created a helper object successfully, may have obtained a helper object created by another thread
helper->session()->getPoller()->async([helper, cacheable_buf]() {
// 该数据不能丢弃给session对象消费 [AUTO-TRANSLATED:6941e5fa]
//This data cannot be discarded, provided to the session object for consumption
emitSessionRecv(helper, cacheable_buf);
});
}
});
return nullptr;
}
void UdpServer::setOnCreateSocket(onCreateSocket cb) {
if (cb) {
_on_create_socket = std::move(cb);
} else {
_on_create_socket = [](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
return Socket::createSocket(poller, false);
};
}
for (auto &pr : _cloned_server) {
pr.second->setOnCreateSocket(cb);
}
}
uint16_t UdpServer::getPort() {
if (!_socket) {
return 0;
}
return _socket->get_local_port();
}
Socket::Ptr UdpServer::createSocket(const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
return _on_create_socket(poller, buf, addr, addr_len);
}
StatisticImp(UdpServer)
} // namespace toolkit