基于Reactor的高并發(fā)服務(wù)器,分為反應堆模型,多線程,I/O模型,服務(wù)器,Http請求和響應五部分
?全局
反應堆模型
Channel
描述了文件描述符以及讀寫事件,以及對應的讀寫銷毀回調(diào)函數(shù),對應存儲arg讀寫回調(diào)對應的參數(shù)
?Channel
Channel添加寫和判斷
-
異或 |:相同為0,異為1
-
按位與&:只有11為1,其它組合全部為0,即只有真真為真,其它一假則假
-
去反 ~:二進制全部取反
-
添加寫屬性:若對應為10 想要寫添加寫屬性,與100異或,的110讀寫屬性
-
刪除寫屬性: 第三位清零,若為110,第三位清零,將寫取反011,在按位與& 010只留下讀事件
// C++11 強類型枚舉
enumclass FDEvent
{
TimeOut = 0x01, //十進制1,超時了 1
ReadEvent = 0x02, //十進制2 10
WriteEvent = 0x04//十進制4 二進制 100
};
void Channel::writeEventEnable(bool flag)
{
if (flag) //如果為真,添加寫屬性
{
// 異或 相同為0 異為1
// WriteEvent 從右往左數(shù)第三個標志位1,通過異或 讓channel->events的第三位為1
m_events |= static_cast<int>(FDEvent::WriteEvent); // 按位異或 int events整型32位,0/1,
}
else// 如果不寫,讓channel->events 對應的第三位清零
{
// ~WriteEvent 按位與, ~WriteEvent取反 011 然后與 channel->events按位與&運算 只有11 為 1,其它皆為0只有同為真時則真,一假則假,1為真,0為假
m_events = m_events & ~static_cast<int>(FDEvent::WriteEvent); //channel->events 第三位清零之后,寫事件就不再檢測
}
}
//判斷文件描述符是否有寫事件
bool Channel::isWriteEventEnable()
{
return m_events & static_cast<int>(FDEvent::WriteEvent); //按位與 ,第三位都是1,則是寫,如果成立,最后大于0,如果不成立,最后為0
}
Dispatcher
Dispatcher作為父類函數(shù),對應Epoll,Poll,Select模型。
?
反應堆模型
選擇反應堆模型
在EventLoop初始化時,針對全局EventLoop,將m_dispatcher初始化為EpollDispatcher.
使用多態(tài)性,父類建立虛函數(shù),子類繼承復函數(shù),使用override取代父類虛函數(shù)。達到選擇反應堆模型。
m_dispatcher = new EpollDispatcher(this); //選擇模型
//Dispatcher類為父類
virtual ~Dispatcher(); //也虛函數(shù),在多態(tài)時
virtual int add(); //等于 = 0純虛函數(shù),就不用定義
//刪除 將某一個節(jié)點從epoll樹上刪除
virtual int remove();
//修改
virtual int modify();
//事件檢測, 用于檢測待檢測三者之一模型epoll_wait等的一系列事件上是否有事件被激活,讀/寫事件
virtual int dispatch(int timeout = 2);//單位 S 超時時長
//Epoll子類繼承父類,override多態(tài)性覆蓋父類函數(shù),同時public繼承,繼承Dispatcher的私有變量
class EpollDispatcher :public Dispatcher //繼承父類Dispatcher
{
public:
EpollDispatcher(struct EventLoop* evLoop);
~EpollDispatcher(); //也虛函數(shù),在多態(tài)時
// override修飾前面的函數(shù),表示此函數(shù)是從父類繼承過來的函數(shù),子類將重寫父類虛函數(shù)
// override會自動對前面的名字進行檢查,
int add() override; //等于 =純虛函數(shù),就不用定義
//刪除 將某一個節(jié)點從epoll樹上刪除
int remove() override;
//修改
int modify() override;
//事件檢測, 用于檢測待檢測三者之一模型epoll_wait等的一系列事件上是否有事件被激活,讀/寫事件
int dispatch(int timeout = 2) override;//單位 S 超時時長
// 不改變的不寫,直接繼承父類
EventLoop
處理所有的事件,啟動反應堆模型,處理機會文件描述符后的事件,添加任務(wù),處理任務(wù)隊列 調(diào)用dispatcher中的添加移除,修改操作 存儲著任務(wù)隊列m_taskQ 存儲fd和對應channel對應關(guān)系:m_channelmap
相關(guān)視頻推薦
6種epoll的設(shè)計方法(單線程epoll、多線程epoll、多進程epoll)及每種epoll的應用場景
手寫一個reactor網(wǎng)絡(luò)模型,準備好linux開發(fā)環(huán)境
手把手實現(xiàn)線程池(120行),實現(xiàn)異步操作,解決項目性能問題
免費學習地址:c/c++ linux服務(wù)器開發(fā)/后臺架構(gòu)師
需要C/C++ Linux服務(wù)器架構(gòu)師學習資料加qun812855908獲?。ㄙY料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費分享
?
私有函數(shù)變量
// CHannelElement結(jié)構(gòu)體
//定義任務(wù)隊列的節(jié)點 類型,文件描述符信息
struct ChannelElement
{
ElemType type; //如何處理該節(jié)點中Channel
Channel* channel; //文件描述符信息
};
//私有函數(shù)變量
//加入開關(guān) EventLoop是否工作
bool m_isQuit;
//該指針指向之類的實例epoll,poll,select
Dispatcher* m_dispatcher;
//任務(wù)隊列,存儲任務(wù),遍歷任務(wù)隊列就可以修改dispatcher檢測的文件描述符
//任務(wù)隊列
queue<ChannelElement*>m_taskQ;
//map 文件描述符和Channel之間的對應關(guān)系 通過數(shù)組實現(xiàn)
map<int,Channel*> m_channelmap;
// 線程相關(guān),線程ID,name
thread::id m_threadID;
string m_threadName; //主線程只有一個,固定名稱,初始化要分為兩個
//互斥鎖,保護任務(wù)隊列
mutex m_mutex;
// 整型數(shù)組
int m_socketPair[2]; //存儲本地通信fd通過socketpair初始化
?EventLoop事件處理
?m_channelmap
?任務(wù)隊列ChannelElement
?
任務(wù)隊列
反應堆運行
反應堆模型啟動之后將會在while循環(huán)中一直執(zhí)行下去。首先調(diào)用dispatcher調(diào)用Epoll的wait函數(shù),等待內(nèi)核回應,根據(jù)其讀寫請求調(diào)用evLoop的enactive函數(shù)進行相關(guān)的讀寫操作。
int EventLoop::Run()
{
m_isQuit = false; //不退出
//比較線程ID,當前線程ID與我們保存的線程ID是否相等
if (m_threadID != this_thread::get_id())
{
//不相等時 直接返回-1
return-1;
}
// 循環(huán)進行時間處理
while (!m_isQuit) //只要沒有停止 死循環(huán)
{
//調(diào)用初始化時選中的模型Epoll,Poll,Select
m_dispatcher->dispatch(); //
ProcessTaskQ(); //處理任務(wù)隊列
}
return0;
}
enactive
根據(jù)傳入的event調(diào)用對應Channel對應的讀寫回調(diào)函數(shù)
int EventLoop::eventActive(int fd, int event)
{
// 判斷函數(shù)傳入的參數(shù)是否為有效
if (fd < 0)
{
return-1;
}
//基于fd從EventLoop取出對應的Channel
Channel* channel = m_channelmap[fd]; //channelmap根據(jù)對應的fd取出對應的channel
// 判斷取出channel的fd與當前的fd是否相同
assert(channel->getSocket() == fd); //如果為假,打印出報錯信息
if (event & (int)FDEvent::ReadEvent && channel->readCallback) //channel->readCallback不等于空
{
//調(diào)用channel的讀回調(diào)函數(shù)
channel->readCallback(const_cast<void*>(channel->getArg()));
}
if (event & (int)FDEvent::WriteEvent && channel->writeCallback)
{
channel->writeCallback(const_cast<void*>(channel->getArg()));
}
return0;
}
添加任務(wù)
int EventLoop::AddTask(Channel* channel, ElemType type)
{
//加鎖,有可能是當前線程,也有可能是主線程
m_mutex.lock();
// 創(chuàng)建新節(jié)點
ChannelElement* node = new ChannelElement;
node->channel = channel;
node->type = type;
m_taskQ.push(node);
m_mutex.unlock();
// 處理節(jié)點
/*
* 如當前EventLoop反應堆屬于子線程
* 1,對于鏈表節(jié)點的添加:可能是當前線程也可能是其它線程(主線程)
* 1),修改fd的事件,可能是當前線程發(fā)起的,還是當前子線程進行處理
* 2),添加新的fd,和新的客戶端發(fā)起連接,添加任務(wù)節(jié)點的操作由主線程發(fā)起
* 2,主線程只負責和客戶端建立連接,判斷當前線程,不讓主線程進行處理,分給子線程
* 不能讓主線程處理任務(wù)隊列,需要由當前的子線程處理
*/
if (m_threadID == this_thread::get_id())
{
//當前子線程
// 直接處理任務(wù)隊列中的任務(wù)
ProcessTaskQ();
}
else
{
//主線程 -- 告訴子線程處理任務(wù)隊列中的任務(wù)
// 1,子線程在工作 2,子線程被阻塞了:1,select,poll,epoll,如何解除其阻塞,在本代碼阻塞時長是2s
// 在檢測集合中添加屬于自己(額外)的文件描述,不負責套接字通信,目的控制文件描述符什么時候有數(shù)據(jù),輔助解除阻塞
// 滿足條件,兩個文件描述符,可以相互通信,//1,使用pipe進程間通信,進程更可,//2,socketpair 文件描述符進行通信
taskWakeup(); //主線程調(diào)用,相當于向socket添加了數(shù)據(jù)
}
return0;
}
處理任務(wù)
從任務(wù)隊列中取出一個任務(wù),根據(jù)其任務(wù)類型,調(diào)用反應堆模型對應,將channel在內(nèi)核中的檢測進行刪除,修改,或添加
int EventLoop::ProcessTaskQ()
{
//遍歷鏈表
while (!m_taskQ.empty())
{
//將處理后的task從當前鏈表中刪除,(需要加鎖)
// 取出頭結(jié)點
m_mutex.lock();
ChannelElement* node = m_taskQ.front(); //從頭部
m_taskQ.pop(); //把頭結(jié)點彈出,相當于刪除
m_mutex.unlock();
//讀鏈表中的Channel,根據(jù)Channel進行處理
Channel* channel = node->channel;
// 判斷任務(wù)類型
if (node->type == ElemType::ADD)
{
// 需要channel里面的文件描述符evLoop里面的數(shù)據(jù)
//添加 -- 每個功能對應一個任務(wù)函數(shù),更利于維護
Add(channel);
}
elseif (node->type == ElemType::DELETE)
{
//Debug("斷開了連接");
//刪除
Remove(channel);
// 需要資源釋放channel 關(guān)掉文件描述符,地址堆內(nèi)存釋放,channel和dispatcher的關(guān)系需要刪除
}
elseif (node->type == ElemType::MODIFY)
{
//修改 的文件描述符事件
Modify(channel);
}
delete node;
}
return0;
}
int EventLoop::Add(Channel* channel)
{
//把任務(wù)節(jié)點中的任務(wù)添加到dispatcher對應的檢測集合里面,
int fd = channel->getSocket();
//找到fd對應數(shù)組元素的位置,并存儲
if (m_channelmap.find(fd) == m_channelmap.end())
{
m_channelmap.insert(make_pair(fd, channel)); //將當前fd和channel添加到map
m_dispatcher->setChannel(channel); //設(shè)置當前channel
int ret = m_dispatcher->add(); //加入
return ret;
}
return-1;
}
int EventLoop::Remove(Channel* channel)
{
//調(diào)用dispatcher的remove函數(shù)進行刪除
// 將要刪除的文件描述符
int fd = channel->getSocket();
// 判斷文件描述符是否已經(jīng)在檢測的集合了
if (m_channelmap.find(fd) == m_channelmap.end())
{
return-1;
}
//從檢測集合中刪除 封裝了poll,epoll select
m_dispatcher->setChannel(channel);
int ret = m_dispatcher->remove();
return ret;
}
int EventLoop::Modify(Channel* channel)
{
// 將要修改的文件描述符
int fd = channel->getSocket();
// TODO判斷
if (m_channelmap.find(fd) == m_channelmap.end())
{
return-1;
}
//從檢測集合中刪除
m_dispatcher->setChannel(channel);
int ret = m_dispatcher->modify();
return ret;
}
多線程
ThreadPool
定義線程池,運行線程池,public函數(shù)取出線程池中某個子線程的反應堆實例EventLoop,線程池的EventLoop反應堆模型事件由主線程傳入,屬于主線程,其內(nèi)部,任務(wù)隊列,fd和Channel對應關(guān)系,ChannelElement都是所有線程需要使用的數(shù)據(jù)
?
線程池工作
線程池運行創(chuàng)建子工作線程
線程池運行語句在主線層運行,根據(jù)當前線程數(shù)量,申請響應的工作線程池,并將工作線程運行起來,將工作線程加入到線程池的vector數(shù)組當中。
void ThreadPool::Run()
{
assert(!m_isStart); //運行期間此條件不能錯
//判斷是不是主線程
if(m_mainLoop->getTHreadID() != this_thread::get_id())
{
exit(0);
}
// 將線程池設(shè)置狀態(tài)標志為啟動
m_isStart = true;
// 子線程數(shù)量大于0
if (m_threadNum > 0)
{
for (int i = 0; i < m_threadNum; ++i)
{
WorkerThread* subThread = new WorkerThread(i); // 調(diào)用子線程
subThread->Run();
m_workerThreads.push_back(subThread);
}
}
}
取出工作線程池中的EventLoop
EventLoop* ThreadPool::takeWorkerEventLoop()
{
//由主線程來調(diào)用線程池取出反應堆模型
assert(m_isStart); //當前程序必須是運行的
//判斷是不是主線程
if (m_mainLoop->getTHreadID() != this_thread::get_id())
{
exit(0);
}
//從線程池中找到一個子線層,然后取出里面的反應堆實例
EventLoop* evLoop = m_mainLoop; //將主線程實例初始化
if (m_threadNum > 0)
{
evLoop = m_workerThreads[m_index]->getEventLoop();
//雨露均沾,不能一直是一個pool->index線程
m_index = ++m_index % m_threadNum;
}
return evLoop;
}
工作線程運行
在子線程中申請反應堆模型,供子線程在客戶端連接時取出 ,供類Connection使用
void WorkerThread::Run()
{
//創(chuàng)建子線程,3,4子線程的回調(diào)函數(shù)以及傳入的參數(shù)
//調(diào)用的函數(shù),以及此函數(shù)的所有者this
m_thread = new thread(&WorkerThread::Running,this);
// 阻塞主線程,讓當前函數(shù)不會直接結(jié)束,不知道當前子線程是否運行結(jié)束
// 如果為空,子線程還沒有初始化完畢,讓主線程等一會,等到初始化完畢
unique_lock<mutex> locker(m_mutex);
while (m_evLoop == nullptr)
{
m_cond.wait(locker);
}
}
void* WorkerThread::Running()
{
m_mutex.lock();
//對evLoop做初始化
m_evLoop = new EventLoop(m_name);
m_mutex.unlock();
m_cond.notify_one(); //喚醒一個主線程的條件變量等待解除阻塞
// 啟動反應堆模型
m_evLoop->Run();
}
IO 模型
Buffer
讀寫內(nèi)存結(jié)構(gòu)體,添加字符串,接受套接字數(shù)據(jù),將寫緩存區(qū)數(shù)據(jù)發(fā)送
?
讀寫位置移動
發(fā)送目錄
int Buffer::sendData(int socket)
{
// 判斷buffer里面是否有需要發(fā)送的數(shù)據(jù) 得到未讀數(shù)據(jù)即待發(fā)送
int readable = readableSize();
if (readable > 0)
{
//發(fā)送出去buffer->data + buffer->readPos 緩存區(qū)的位置+已經(jīng)讀到的位置
// 管道破裂 -- 連接已經(jīng)斷開,服務(wù)器繼續(xù)發(fā)數(shù)據(jù),出現(xiàn)管道破裂 -- TCP協(xié)議
// 當內(nèi)核產(chǎn)生信號時,MSG_NOSIGNAL忽略,繼續(xù)保持連接
// Linux的信號級別高,Linux大多數(shù)信號都會終止信號
int count = send(socket, m_data + m_readPos, readable, MSG_NOSIGNAL);
if (count > 0)
{
// 往后移動未讀緩存區(qū)位置
m_readPos += count;
// 稍微休眠一下
usleep(1); // 1微妙
}
return count;
}
return0;
}
發(fā)送文件
發(fā)送文件是不需要將讀取到的文件放入緩存的,直接內(nèi)核發(fā)送提高文件IO效率。
int Buffer::sendData(int cfd, int fd, off_t offset, int size)
{
int count = 0;
while (offset < size)
{
//系統(tǒng)函數(shù),發(fā)送文件,linux內(nèi)核提供的sendfile 也能減少拷貝次數(shù)
// sendfile發(fā)送文件效率高,而文件目錄使用send
//通信文件描述符,打開文件描述符,fd對應的文件偏移量一般為空,
//單獨單文件出現(xiàn)發(fā)送不全,offset會自動修改當前讀取位置
int ret = (int)sendfile(cfd, fd, &offset, (size_t)(size - offset));
if (ret == -1 && errno == EAGAIN)
{
printf("not data ....");
perror("sendfile");
}
count += (int)offset;
}
return count;
}
TcpConnection
負責子線程與客戶端進行通信,分別存儲這讀寫銷毀回調(diào)函數(shù)->調(diào)用相關(guān)buffer函數(shù)完成相關(guān)的通信功能
?TcpConnection
?
主線程
初始化
申請讀寫緩存區(qū),并初始化Channel,初始化子線程與客戶端與服務(wù)器進行通信時回調(diào)函數(shù)
TcpConnection::TcpConnection(int fd, EventLoop* evloop)
{
//并沒有創(chuàng)建evloop,當前的TcpConnect都是在子線程中完成的
m_evLoop = evloop;
m_readBuf = new Buffer(10240); //10K
m_writeBuf = new Buffer(10240);
// 初始化
m_request = new HttpRequest;
m_response = new HttpResponse;
m_name = "Connection-" + to_string(fd);
// 服務(wù)器最迫切想知道的時候,客戶端有沒有數(shù)據(jù)到達
m_channel =new Channel(fd,FDEvent::ReadEvent, processRead, processWrite, destory, this);
// 把channel放到任務(wù)循環(huán)的任務(wù)隊列里面
evloop->AddTask(m_channel, ElemType::ADD);
}
讀寫回調(diào)
讀事件將調(diào)用HttpRequest解析,客戶端發(fā)送的讀取請求。寫事件,將針對讀事件將對應的數(shù)據(jù)寫入緩存區(qū),由寫事件進行發(fā)送。但由于效率的考慮,在讀事件時,已經(jīng)設(shè)置成邊讀變發(fā)送提高效率,發(fā)送文件也將采用Linux內(nèi)核提供的sendfile方法,不讀取內(nèi)核直接發(fā)送,比send的效率快了,很多,在很大程度上,寫事件的寫功能基本被架空。
int TcpConnection::processRead(void* arg)
{
TcpConnection* conn = static_cast<TcpConnection*>(arg);
// 接受數(shù)據(jù) 最后要存儲到readBuf里面
int socket = conn->m_channel->getSocket();
int count = conn->m_readBuf->socketRead(socket);
// data起始地址 readPos該讀的地址位置
Debug("接收到的http請求數(shù)據(jù): %s", conn->m_readBuf->data());
if (count > 0)
{
// 接受了http請求,解析http請求
#ifdef MSG_SEND_AUTO
//添加檢測寫事件
conn->m_channel->writeEventEnable(true);
// MODIFY修改檢測讀寫事件
conn->m_evLoop->AddTask(conn->m_channel, ElemType::MODIFY);
#endif
bool flag = conn->m_request->parseHttpRequest(
conn->m_readBuf, conn->m_response,
conn->m_writeBuf, socket);
if (!flag)
{
//解析失敗,回復一個簡單的HTML
string errMsg = "Http/1.1 400 Bad Request\r\n\r\n";
conn->m_writeBuf->appendString(errMsg);
}
}
else
{
#ifdef MSG_SEND_AUTO //如果被定義,
//斷開連接
conn->m_evLoop->AddTask(conn->m_channel, ElemType::DELETE);
#endif
}
// 斷開連接 完全寫入緩存區(qū)再發(fā)送不能立即關(guān)閉,還沒有發(fā)送
#ifndef MSG_SEND_AUTO //如果沒有被定義,
conn->m_evLoop->AddTask(conn->m_channel, ElemType::DELETE);
#endif
return0;
}
//寫回調(diào)函數(shù),處理寫事件,將writeBuf中的數(shù)據(jù)發(fā)送給客戶端
int TcpConnection::processWrite(void* arg)
{
Debug("開始發(fā)送數(shù)據(jù)了(基于寫事件發(fā)送)....");
TcpConnection* conn = static_cast<TcpConnection*>(arg);
// 發(fā)送數(shù)據(jù)
int count = conn->m_writeBuf->sendData(conn->m_channel->getSocket());
if (count > 0)
{
// 判斷數(shù)據(jù)是否全部被發(fā)送出去
if (conn->m_writeBuf->readableSize() == 0)
{
// 數(shù)據(jù)發(fā)送完畢
// 1,不再檢測寫事件 --修改channel中保存的事件
conn->m_channel->writeEventEnable(false);
// 2, 修改dispatcher中檢測的集合,往enentLoop反映模型認為隊列節(jié)點標記為modify
conn->m_evLoop->AddTask(conn->m_channel, ElemType::MODIFY);
//3,若不通信,刪除這個節(jié)點
conn->m_evLoop->AddTask(conn->m_channel, ElemType::DELETE);
}
}
return0;
}
HttpRequest
定義http 請求結(jié)構(gòu)體添加請求頭結(jié)點,解析請求行,頭,解析/處理http請求協(xié)議,獲取文件類型 發(fā)送文件/目錄 設(shè)置請求url,Method,Version ,state
處理客戶端解析請求
在while循環(huán)內(nèi)部,完成對請求行和請求頭的解析。解析完成之后,根據(jù)請求行,讀取客戶端需要的數(shù)據(jù),并對應進行操作
bool HttpRequest::parseHttpRequest(Buffer* readBuf, HttpResponse* response, Buffer* sendBuf, int socket)
{
bool flag = true;
// 先解析請求行
while (m_curState !=PressState::ParseReqDone)
{
// 根據(jù)請求頭目前的請求狀態(tài)進行選擇
switch (m_curState)
{
case PressState::ParseReqLine:
flag = parseRequestLine(readBuf);
break;
case PressState::ParseReqHeaders:
flag = parseRequestHeader(readBuf);
break;
case PressState::ParseReqBody: //post的請求,咱不做處理
// 讀取post數(shù)據(jù)
break;
default:
break;
}
if (!flag)
{
return flag;
}
//判斷是否解析完畢,如果完畢,需要準備回復的數(shù)據(jù)
if (m_curState == PressState::ParseReqDone)
{
// 1,根據(jù)解析出的原始數(shù)據(jù),對客戶端請求做出處理
processHttpRequest(response);
// 2,組織響應數(shù)據(jù)并發(fā)送
response->prepareMsg(sendBuf, socket);
}
}
// 狀態(tài)還原,保證還能繼續(xù)處理第二條及以后的請求
m_curState = PressState::ParseReqLine;
//再解析請求頭
return flag;
}
處理客戶端請求
根據(jù)請求行規(guī)則判斷是請求目錄,還是請求文件,調(diào)用Buffer相關(guān)發(fā)送目錄,和發(fā)送文件重載函數(shù),完成通信任務(wù)。
bool HttpRequest::processHttpRequest(HttpResponse* response)
{
if (strcasecmp(m_method.data(), "get") != 0) //strcasecmp比較時不區(qū)分大小寫
{
//非get請求不處理
return-1;
}
m_url = decodeMsg(m_url); // 避免中文的編碼問題 將請求的路徑轉(zhuǎn)碼 linux會轉(zhuǎn)成utf8
//處理客戶端請求的靜態(tài)資源(目錄或文件)
constchar* file = NULL;
if (strcmp(m_url.data(), "/") == 0) //判斷是不是根目錄
{
file = "./";
}
else
{
file = m_url.data() + 1; // 指針+1 把開始的 /去掉吧
}
//判斷file屬性,是文件還是目錄
struct stat st;
int ret = stat(file, &st); // file文件屬性,同時將信息傳入st保存了文件的大小
if (ret == -1)
{
//文件不存在 -- 回復404
//sendHeadMsg(cfd, 404, "Not Found", getFileType(".html"), -1);
//sendFile("404.html", cfd); //發(fā)送404對應的html文件
response->setFileName("404.html");
response->setStatusCode(StatusCode::NotFound);
// 響應頭
response->addHeader("Content-type", getFileType(".html"));
response->sendDataFunc = sendFile;
return0;
}
response->setFileName(file);
response->setStatusCode(StatusCode::OK);
//判斷文件類型
if (S_ISDIR(st.st_mode)) //如果時目錄返回1,不是返回0
{
//把這個目錄中的內(nèi)容發(fā)送給客戶端
//sendHeadMsg(cfd, 200, "OK", getFileType(".html"), (int)st.st_size);
//sendDir(file, cfd);
// 響應頭
response->addHeader("Content-type", getFileType(".html"));
response->sendDataFunc = sendDir;
}
else
{
//把這個文件的內(nèi)容發(fā)給客戶端
//sendHeadMsg(cfd, 200, "OK", getFileType(file), (int)st.st_size);
//sendFile(file, cfd);
// 響應頭
response->addHeader("Content-type", getFileType(file));
response->addHeader("Content-length", to_string(st.st_size));
response->sendDataFunc = sendFile;
}
returnfalse;
}
HttpResponse
定義http響應,添加響應頭,準備響應的數(shù)據(jù)
服務(wù)器
TcpServer
服務(wù)器類,復制服務(wù)器的初始化,設(shè)置監(jiān)聽,啟動服務(wù)器,并接受主線程的連接請求
?
TcpServer工作流程
主函數(shù)
-
傳入用戶輸入的端口和文件夾
-
端口將作為服務(wù)器端口,文件夾將作為瀏覽器訪問的文件夾
-
初始化TcpServer服務(wù)器實例 - 傳入端口和初始化線程個數(shù)
-
運行服務(wù)器
#include <stdlib.h>
#include <unistd.h>
#include "TcpServer.h"
//初始化監(jiān)聽的套接字
// argc 輸入?yún)?shù)的個數(shù)
// argv[0]可執(zhí)行程序的名稱
// argv[1]傳入的第一個參數(shù), port
// argv[2]傳入的第二個參數(shù) path
int main(int argc, char* argv[])
{
#if 0
if (argc < 3)
{
printf("./a.out port path\n");
return-1;
}
unsigned short port = (unsigned short)atoi(argv[1]);
//切換服務(wù)器的根目錄,將根目錄當前目錄切換到其它目錄
chdir(argv[2]);
// 啟動服務(wù)器
#else
// VS code 調(diào)試
unsigned short port = 8080;
chdir("/home/foryouos/blog");
#endif
// 創(chuàng)建服務(wù)器實例
TcpServer* server = new TcpServer(port, 4);
// 服務(wù)器運行 - 啟動線程池-對監(jiān)聽的套接字進行封裝,并放到主線程的任務(wù)隊列,啟動反應堆模型
// 底層的epoll也運行起來,
server->Run();
return0;
}
初始化TcpServer
?
初始化TcpServer
啟動TcpServer
?
啟動TcpServer
檢測到客戶端請求
?
客戶端請求文章來源:http://www.zghlxwxcb.cn/news/detail-653943.html
詳細代碼:https://github.com/foryouos/cppserver-linux/tree/main/c_simple_server/cpp_server文章來源地址http://www.zghlxwxcb.cn/news/detail-653943.html
到了這里,關(guān)于【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!