目录
先看看官网关于网络的问答
Q:CBEngine使用什么网络模型?
A:由于正式运营环境在Linux下,所以网络模型选择了epoll, 生产环境在Windows比较方便所以CBEngine也支持了Windows系统,但是网络模型只是简单的select。
看看epoll用到的API, epoll_create epoll_ctl epoll_wait
一.epoll_create
直接搜索关键字 epoll_create,创建epoll实例
EpollPoller::EpollPoller(int expectedSize) :
epfd_(epoll_create(expectedSize))
{
if (epfd_ == -1)
{
ERROR_MSG(fmt::format("EpollPoller::EpollPoller: epoll_create failed: {}\n",
kbe_strerror()));
}
};
二.epoll_ctl
epoll_ctl, 设置epoll的事件
bool EpollPoller::doRegister(int fd, bool isRead, bool isRegister)
{
struct epoll_event ev;
memset(&ev, 0, sizeof(ev)); // stop valgrind warning
int op;
ev.data.fd = fd;
// Handle the case where the file is already registered for the opposite
// action.
if (this->isRegistered(fd, !isRead))
{
op = EPOLL_CTL_MOD;
ev.events = isRegister ? EPOLLIN|EPOLLOUT :
isRead ? EPOLLOUT : EPOLLIN;
}
else
{
// TODO: Could be good to use EPOLLET (leave like select for now).
ev.events = isRead ? EPOLLIN : EPOLLOUT;
op = isRegister ? EPOLL_CTL_ADD : EPOLL_CTL_DEL;
}
if (epoll_ctl(epfd_, op, fd, &ev) < 0)
{
const char* MESSAGE = "EpollPoller::doRegister: Failed to {} {} file "
"descriptor {} ({})\n";
if (errno == EBADF)
{
WARNING_MSG(fmt::format(MESSAGE,
(isRegister ? "add" : "remove"),
(isRead ? "read" : "write"),
fd,
kbe_strerror()));
}
else
{
ERROR_MSG(fmt::format(MESSAGE,
(isRegister ? "add" : "remove"),
(isRead ? "read" : "write"),
fd,
kbe_strerror()));
}
return false;
}
return true;
}
三.epoll_wait
epoll_wait 等待事件
int EpollPoller::processPendingEvents(double maxWait)
{
const int MAX_EVENTS = 10;
struct epoll_event events[ MAX_EVENTS ];
int maxWaitInMilliseconds = int(ceil(maxWait * 1000));
#if ENABLE_WATCHERS
g_idleProfile.start();
#else
uint64 startTime = timestamp();
#endif
KBEConcurrency::onStartMainThreadIdling();
int nfds = epoll_wait(epfd_, events, MAX_EVENTS, maxWaitInMilliseconds);
KBEConcurrency::onEndMainThreadIdling();
#if ENABLE_WATCHERS
g_idleProfile.stop();
spareTime_ += g_idleProfile.lastTime_;
#else
spareTime_ += timestamp() - startTime;
#endif
for (int i = 0; i < nfds; ++i)
{
if (events[i].events & (EPOLLERR|EPOLLHUP))
{
this->triggerError(events[i].data.fd);
}
else
{
if (events[i].events & EPOLLIN)
{
this->triggerRead(events[i].data.fd);
}
if (events[i].events & EPOLLOUT)
{
this->triggerWrite(events[i].data.fd);
}
}
}
return nfds;
}
四.accept
accept,在这里等待连接
INLINE EndPoint * EndPoint::accept(u_int16_t * networkPort, u_int32_t * networkAddr, bool autosetflags)
{
sockaddr_in sin;
socklen_t sinLen = sizeof(sin);
int ret = (int)::accept(socket_, (sockaddr*)&sin, &sinLen);
#if KBE_PLATFORM == PLATFORM_UNIX
if (ret < 0) return NULL;
#else
if (ret == INVALID_SOCKET) return NULL;
#endif
EndPoint * pNew = EndPoint::createPoolObject(OBJECTPOOL_POINT);
pNew->setFileDescriptor(ret);
pNew->addr(sin.sin_port, sin.sin_addr.s_addr);
if(autosetflags)
{
pNew->setnonblocking(true);
pNew->setnodelay(true);
}
if (networkPort != NULL) *networkPort = sin.sin_port;
if (networkAddr != NULL) *networkAddr = sin.sin_addr.s_addr;
return pNew;
}
五.recv
接收消息的 recv 函数
INLINE int EndPoint::recv(void * gramData, int gramSize)
{
if (isSSL())
return SSL_read(sslHandle_, (char*)gramData, gramSize);
return ::recv(socket_, (char*)gramData, gramSize, 0);
}
接收消息,从线程工作函数 到系统API调用堆栈
从这里可以看到消息的接收的运行在主线程上的
读取的数据存放到类 MemoryStream上的成员变量 std::vector<uint8> data_;上
int len = ep.recv(data() + wpos(), (int)(size() - wpos()));
uint8 *data() { return &data_[0]; }
接收到的消息后面是如何处理的,去登录的比较上层的地方下个断点看看
红框内就是消息从recv 到 loginapp类
经过一路分发,最终送到登录的逻辑处理函数
void Loginapp::login(Network::Channel* pChannel, MemoryStream& s)
{
AUTO_SCOPED_PROFILE("login");
COMPONENT_CLIENT_TYPE ctype;
CLIENT_CTYPE tctype = UNKNOWN_CLIENT_COMPONENT_TYPE;
std::string loginName;
std::string password;
std::string datas;
bool forceInternalLogin = false;
// 前端类别
s >> tctype;
ctype = static_cast<COMPONENT_CLIENT_TYPE>(tctype);
// 附带数据
s.readBlob(datas);
// 帐号登录名
s >> loginName;
// 密码
s >> password;
loginName = KBEngine::strutil::kbe_trim(loginName);
在login内经过校验,最后通过网络发送给dbmgr进行数据库查询
// 向dbmgr查询用户合法性
Network::Bundle* pBundle = Network::Bundle::createPoolObject(OBJECTPOOL_POINT);
(*pBundle).newMessage(DbmgrInterface::onAccountLogin);
(*pBundle) << loginName << password;
(*pBundle).appendBlob(datas);
dbmgrinfos->pChannel->send(pBundle);
六.send
去API,send下个断点看看是如何一路走到这里的, 红框内就是login一路到send的调用。
调用API send的实现
INLINE int EndPoint::send(const void * gramData, int gramSize)
{
if (isSSL())
return SSL_write(sslHandle_, (char*)gramData, gramSize);
return ::send(socket_, (char*)gramData, gramSize, 0);
}
总结
自此可以看到epoll的初始化,一条消息从recv进来,再到send出去的过程。整个过程都是在主线程内运行的。