Linux c++服务端设计
菜而不自知才是无药可救 —> 说我呢/(ㄒoㄒ)/
这几天一直在看muduo库的代码,结合着陈硕老师的书《Linux多线程服务端编程 – 使用muduo c++ 网络库》。还记得我第一次使用网络库是在大二下学期的时候,用了boost库,但是用的我也是一脸懵逼,很多都是照着官网的例子去做修改,最后反正能完成服务功能就是谢天谢地。
这次学习成功的让我又多了一位崇拜的大牛,这本书其实我是大三的时候就买了,但是当时的我忙着准备面试很浮躁看不进去。现在我回头看发现要是自己沉下心来把这个网络库的内容好好去看看说不定都去上了。我大学的时候要是看看源码该有多好,可惜了。
以上都是废话
异常处理
- 对于字节的处理:字节顺序的处理 ,因为网络和本地可能不一样,所以需要进行转换,这就是比较底层的功能。
- 返回值出现的异常
- 设置socket的非阻塞和close-on-exec
- 关于socket的设置和特点
字节序转换的一些系统函数
#include <stdint.h>
#include <endian.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wold-style-cast"
inline uint64_t hostToNetwork64(uint64_t host64)
{
return htobe64(host64);
}
inline uint32_t hostToNetwork32(uint32_t host32)
{
return htobe32(host32);
}
inline uint16_t hostToNetwork16(uint16_t host16)
{
return htobe16(host16);
}
inline uint64_t networkToHost64(uint64_t net64)
{
return be64toh(net64);
}
inline uint32_t networkToHost32(uint32_t net32)
{
return be32toh(net32);
}
inline uint16_t networkToHost16(uint16_t net16)
{
return be16toh(net16);
}
#pragma GCC diagnostic pop
使用eventloop的时候将socket设置为非阻塞
a设置为非阻塞的原因就是就是一是这样相应比较快不会直接的阻塞读取等待,其次是如果accept的时候出现了对方已经关闭fd这种情况,可能会导致问题出现,一直处于阻塞的状态。所以使用非阻塞的socket出现问题也比较好解决,设置超时时间去处理相应的问题就好。
使用close-on-exec,其实就是让该socket在子进程调用exec的时候就自动的关闭。
无名namespace的使用
其实就是希望将这一部分的内容作为static的部分,是一种一比较好用的保护文件里面的变量和函数方法。
- TCPserver
class TcpServer {
public:
private:
Acceptor acceptor_;
threadpool_
TCPServer::newConnection 这个是bind在acceptor里面的,每次当有出现connection的时候都会调用这个
connectionCallback_ //用于初始化conn里面的回调函数
messageCallback_
};
/**
这个函数其实就是初始化的新的连接
1.给这个连接一个名字比较方便管理
2.存放在server的连接数组里面
3,初始化TCPConnection的回调函数
**/
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();
char buf[64];
snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
++nextConnId_;
string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}
loop里面会执行的回调函数
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this()); //每个TcpConnection内部都含有锁
channel_->enableReading();
connectionCallback_(shared_from_this()); //调用连接回调的函数
}
connectionEstablish里面回调函数函数里面会继续执行初始化时注册的回调函数,接下来就是看一下connectionCallback里面做了什么。初始化的时候注册的是一个default函数,这里面没什么特别的,就是打印一下现在的执行情况。
在TcpConnection初始化的时候,讲TcpConnection被调用的时候就是会使用EventLoop的runInLoop将其放入执行队列里面
void EventLoop::runInLoop(Functor cb)
{
if (isInLoopThread())
{
cb(); //如果正处于LoopThread中,调用的该回调函数
}
else
{
queueInLoop(std::move(cb)); //否则放入回调函数队列里面
}
}
在eventLoop内部是使用loop函数不停循环的处理所有的事件。
在poller里面,有新的事件(连接,读,写等)在这种情况下poller就会更新一个channel,并且将这个channel放在activechanel里面
void EventLoop::loop()
{
assert(!looping_);
assertInLoopThread();
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear();
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels();
}
// TODO sort channel by priority
eventHandling_ = true;
for (Channel* channel : activeChannels_)
{
currentActiveChannel_ = channel;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
doPendingFunctors(); //回调函数在执行完处理之后都会被执行。
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
handleEvent就是对于poll里面的不同的情况进行处理
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event(connections_) POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
这上面的所有的callback在哪里进行的初始化?
就在TcpConnection初始化的时候…
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true);
所以说到底每次创建一个TcpConnecton的时候你注册的那些函数会在事件执行的时候给你处理事件。
handleRead里面也是使用读取函数的。在channel里面的enableReading起到的作用是将channel里面的events增加一个读的事件,更新一下,之后在channel不断调用handleRead的时候进行处理。起到一个添加事件的作用