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的時候進行處理。起到一個添加事件的作用