Reactor/lib/TcpServer.hpp
2025-06-27 18:01:00 -05:00

237 lines
6.5 KiB
C++

#pragma once
#include "Core.hpp"
#include "Socket.hpp"
#include "TcpConnection.hpp"
#include "EventLoopThread.hpp"
#include "Utilities.hpp"
#include <unordered_map>
#include <string>
#include <atomic>
#include <functional>
namespace reactor {
using NewConnectionCallback = std::function<void(int, const InetAddress&)>;
class Acceptor : public NonCopyable
{
private:
EventLoop* loop_;
Socket acceptSocket_;
std::unique_ptr<Channel> acceptChannel_;
NewConnectionCallback newConnectionCallback_;
bool listening_;
int idleFd_;
void handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
int connfd = acceptSocket_.accept(peerAddr);
if (connfd >= 0) {
if (newConnectionCallback_) {
newConnectionCallback_(connfd, peerAddr);
} else {
close(connfd);
LOG_WARN << "Acceptor no callback for new connection, closing fd=" << connfd;
}
} else {
LOG_ERROR << "Acceptor accept failed: " << strerror(errno);
if (errno == EMFILE) {
close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), nullptr, nullptr);
close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}
public:
Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reusePort = true)
: loop_(loop), acceptSocket_(Socket::createTcp(listenAddr.isIpV6())),
acceptChannel_(std::make_unique<Channel>(loop, acceptSocket_.fd())),
listening_(false), idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
acceptSocket_.setReuseAddr(true);
if (reusePort) {
acceptSocket_.setReusePort(true);
}
acceptSocket_.bind(listenAddr);
acceptChannel_->setReadCallback([this]() { handleRead(); });
LOG_INFO << "Acceptor created for " << listenAddr.toIpPort();
}
~Acceptor()
{
acceptChannel_->disableAll();
acceptChannel_->remove();
close(idleFd_);
LOG_INFO << "Acceptor destroyed";
}
void listen()
{
loop_->assertInLoopThread();
listening_ = true;
acceptSocket_.listen();
acceptChannel_->enableReading();
LOG_INFO << "Acceptor listening";
}
bool listening() const { return listening_; }
void setNewConnectionCallback(NewConnectionCallback cb)
{
newConnectionCallback_ = std::move(cb);
}
};
class TcpServer : public NonCopyable
{
private:
EventLoop* loop_;
std::string name_;
std::unique_ptr<Acceptor> acceptor_;
std::unique_ptr<EventLoopThreadPool> threadPool_;
MessageCallback messageCallback_;
ConnectionCallback connectionCallback_;
WriteCompleteCallback writeCompleteCallback_;
std::unordered_map<std::string, TcpConnectionPtr> connections_;
std::atomic<int> nextConnId_;
bool started_;
void newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();
if (!ioLoop) ioLoop = loop_;
std::string connName = name_ + "-" + peerAddr.toIpPort() + "#" + std::to_string(nextConnId_++);
InetAddress localAddr = Socket::getLocalAddr(sockfd);
LOG_INFO << "TcpServer new connection " << connName << " from " << peerAddr.toIpPort();
auto conn = std::make_shared<TcpConnection>(ioLoop, connName, sockfd, localAddr, peerAddr);
connections_[connName] = conn;
conn->setMessageCallback(messageCallback_);
conn->setConnectionCallback(connectionCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback([this](const TcpConnectionPtr& conn) {
removeConnection(conn);
});
ioLoop->runInLoop([conn]() { conn->connectEstablished(); });
}
void removeConnection(const TcpConnectionPtr& conn)
{
loop_->runInLoop([this, conn]() {
LOG_INFO << "TcpServer removing connection " << conn->name();
size_t n = connections_.erase(conn->name());
assert(n == 1);
EventLoop* ioLoop = conn->getLoop();
ioLoop->queueInLoop([conn]() { conn->connectDestroyed(); });
});
}
void removeConnectionInLoop(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer removing connection " << conn->name();
size_t n = connections_.erase(conn->name());
assert(n == 1);
EventLoop* ioLoop = conn->getLoop();
ioLoop->queueInLoop([conn]() { conn->connectDestroyed(); });
}
public:
TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& name,
bool reusePort = true)
: loop_(loop), name_(name),
acceptor_(std::make_unique<Acceptor>(loop, listenAddr, reusePort)),
threadPool_(std::make_unique<EventLoopThreadPool>(0, name + "-EventLoop")),
nextConnId_(1), started_(false)
{
acceptor_->setNewConnectionCallback([this](int sockfd, const InetAddress& addr) {
newConnection(sockfd, addr);
});
LOG_INFO << "TcpServer " << name_ << " created for " << listenAddr.toIpPort();
}
~TcpServer()
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer " << name_ << " destructing with " << connections_.size() << " connections";
for (auto& item : connections_) {
auto conn = item.second;
auto ioLoop = conn->getLoop();
ioLoop->runInLoop([conn]() { conn->forceClose(); });
}
}
void setThreadNum(int numThreads)
{
assert(0 <= numThreads);
threadPool_ = std::make_unique<EventLoopThreadPool>(numThreads, name_ + "-EventLoop");
LOG_INFO << "TcpServer " << name_ << " set thread pool size to " << numThreads;
}
void start()
{
if (!started_) {
started_ = true;
if (!acceptor_->listening()) {
loop_->runInLoop([this]() { acceptor_->listen(); });
}
LOG_INFO << "TcpServer " << name_ << " started with " << threadPool_->size() << " threads";
}
}
void setMessageCallback(MessageCallback cb) { messageCallback_ = std::move(cb); }
void setConnectionCallback(ConnectionCallback cb) { connectionCallback_ = std::move(cb); }
void setWriteCompleteCallback(WriteCompleteCallback cb) { writeCompleteCallback_ = std::move(cb); }
const std::string& name() const { return name_; }
const char* ipPort() const { return acceptor_ ? "listening" : "not-listening"; }
EventLoop* getLoop() const { return loop_; }
size_t numConnections() const
{
return connections_.size();
}
std::vector<TcpConnectionPtr> getConnections() const
{
std::vector<TcpConnectionPtr> result;
result.reserve(connections_.size());
for (const auto& item : connections_) {
result.push_back(item.second);
}
return result;
}
TcpConnectionPtr getConnection(const std::string& name) const
{
auto it = connections_.find(name);
return it != connections_.end() ? it->second : TcpConnectionPtr();
}
void forceCloseAllConnections()
{
for (auto& item : connections_) {
auto conn = item.second;
auto ioLoop = conn->getLoop();
ioLoop->runInLoop([conn]() { conn->forceClose(); });
}
}
};
} // namespace reactor