国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器

這篇具有很好參考價值的文章主要介紹了【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

基于Reactor的高并發(fā)服務(wù)器,分為反應堆模型,多線程,I/O模型,服務(wù)器,Http請求和響應五部分

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?全局

反應堆模型

Channel

描述了文件描述符以及讀寫事件,以及對應的讀寫銷毀回調(diào)函數(shù),對應存儲arg讀寫回調(diào)對應的參數(shù)

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?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模型。

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

反應堆模型

選擇反應堆模型

在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等),免費分享

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

私有函數(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初始化

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?EventLoop事件處理

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?m_channelmap

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?任務(wù)隊列ChannelElement

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

任務(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ù)

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

線程池工作

線程池運行創(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ā)送

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

讀寫位置移動

發(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)的通信功能

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?TcpConnection

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

主線程

初始化

申請讀寫緩存區(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ù)器,并接受主線程的連接請求

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

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

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

初始化TcpServer

啟動TcpServer

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

啟動TcpServer

檢測到客戶端請求

【實戰(zhàn)項目】c++實現(xiàn)基于reactor的高并發(fā)服務(wù)器,c++,服務(wù)器,reactor,epoll,網(wǎng)絡(luò)編程

?

客戶端請求

詳細代碼: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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)HttpRequest模塊 解析http請求協(xié)議

    基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)HttpRequest模塊 解析http請求協(xié)議

    一、HTTP響應報文格式 二、根據(jù)解析出的原始數(shù)據(jù),對客戶端的請求做出處理? processHttpRequest? 1.解碼字符串?? 解決瀏覽器無法訪問帶特殊字符的文件得到問題 2.判斷文件擴展名,返回對應的 Content-Type(Mime-Type) 3.發(fā)送文件??sendFile 4.發(fā)送目錄 三、解析http請求協(xié)議? parseHttpR

    2024年02月02日
    瀏覽(25)
  • 基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)在TcpConnection 中接收并解析Http請求消息

    基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)在TcpConnection 中接收并解析Http請求消息

    一、在TcpConnection 中多添加和http協(xié)議相關(guān)的request和response 二、給客戶端回復數(shù)據(jù)(方法一) 1.在 Buffer.h 文件中添加 bufferSendData 函數(shù): ?2.在 TcpConnection.c 文件中添加 processWrite 函數(shù): 3.修改 tcpConnectionInit 函數(shù)中調(diào)用的 channelInit函數(shù) 的寫回調(diào)函數(shù)為 processWrite函數(shù) 三、給客戶端

    2024年01月23日
    瀏覽(17)
  • 基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)在EventLoop中處理被激活的文件描述符的事件

    基于多反應堆的高并發(fā)服務(wù)器【C/C++/Reactor】(中)在EventLoop中處理被激活的文件描述符的事件

    文件描述符處理與回調(diào)函數(shù) 一、主要概念 反應堆模型:一種處理系統(tǒng)事件或網(wǎng)絡(luò)事件的模型,當文件描述符被激活時,可以檢測到 文件描述符:在操作系統(tǒng)中,用于標識打開的文件、套接字等的一種數(shù)據(jù)類型? 處理激活的文件描述符的函數(shù):當文件描述符被激活時,需要有

    2024年02月03日
    瀏覽(23)
  • C++從0實現(xiàn)百萬并發(fā)Reactor服務(wù)器

    C++從0實現(xiàn)百萬并發(fā)Reactor服務(wù)器

    C++從0實現(xiàn)百萬并發(fā)Reactor服務(wù)器 // \\\"xia讠果URI\\\"》uкооu·??Μ C++從0實現(xiàn)百萬并發(fā)Reactor服務(wù)器 - 網(wǎng)絡(luò)編程基礎(chǔ) 網(wǎng)絡(luò)編程中有許多基礎(chǔ)概念必須了解,比如 OSI,TCP/IP,字節(jié)序列等,這些都是開發(fā)網(wǎng)絡(luò)應用的基礎(chǔ),可以幫助我們更好的理解網(wǎng)絡(luò)程序的工作原理,來一起學習下一些

    2024年03月17日
    瀏覽(24)
  • 基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- 模擬實現(xiàn) ls-l 命令

    基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- 模擬實現(xiàn) ls-l 命令

    ?這一小節(jié)會用到上面兩張圖的紅色框里面的變量 任務(wù): 模擬實現(xiàn) ls -l 指令 -rw-rw-r-- 1 nowcoder nowcoder 12 12月 ?3 15:48 a.txt ? ?

    2024年02月16日
    瀏覽(93)
  • 【Linux】C++項目實戰(zhàn)-高并發(fā)服務(wù)器詳析

    【Linux】C++項目實戰(zhàn)-高并發(fā)服務(wù)器詳析

    橙色 server_process.c文件內(nèi)容如下: 注意第70行的if(errno == EINTR),如果沒有這個if判斷的話,當同時多個客戶端鏈接進來,停掉一個客戶端,然后再啟動一個客戶端,就會發(fā)現(xiàn)沒法連接了,accept會報一個錯誤。因為一個客戶端停掉,在服務(wù)器端就相當于一個子進程終止執(zhí)行,會發(fā)

    2024年02月09日
    瀏覽(20)
  • 項目---基于TCP的高并發(fā)聊天系統(tǒng)

    項目---基于TCP的高并發(fā)聊天系統(tǒng)

    目錄 服務(wù)端 ?服務(wù)端視角下的流程圖 一、數(shù)據(jù)庫管理模塊 1.1 數(shù)據(jù)庫表的創(chuàng)建 1.2 .對于數(shù)據(jù)庫的操作 1.2.1首先得連接數(shù)據(jù)庫 1.2.2執(zhí)行數(shù)據(jù)庫語句 ?1.2.3 返回數(shù)據(jù)庫中存放的所有用戶的信息 ?1.2.4返回數(shù)據(jù)庫中存放的所有用戶的好友信息 ?二、用戶管理模塊 2.1、UserInfo類:描述

    2023年04月13日
    瀏覽(19)
  • 基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- fcntl函數(shù)

    基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- fcntl函數(shù)

    #include unistd.h #include fcntl.h int fcntl(int fd, int cmd, ...); 參數(shù): ?? ?fd : 表示需要操作的文件描述符 ?? ?cmd: 表示對文件描述符進行如何操作 ?? ??? ? - F_DUPFD : 復制文件描述符,復制的是第一個參數(shù)fd, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 得到一個新的文件描述符(返回值) ??

    2024年02月16日
    瀏覽(22)
  • 基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- 目錄操作函數(shù)

    基于linux下的高并發(fā)服務(wù)器開發(fā)(第一章)- 目錄操作函數(shù)

    ?(1)int mkdir(const char* pathname,mode_t mode); #include sys/stat.h #include sys/types.h int mkdir(const char *pathname, mode_t mode); ?? ?作用:創(chuàng)建一個目錄 ?? ?參數(shù): ?? ??? ? pathname: 創(chuàng)建的目錄的路徑 ?? ??? ?mode: 權(quán)限,八進制的數(shù) ?? ?返回值: ?? ??? ? 成功返回0, 失敗返回-1 ?(

    2024年02月16日
    瀏覽(27)
  • 【項目實戰(zhàn)】基于高并發(fā)服務(wù)器的搜索引擎

    【項目實戰(zhàn)】基于高并發(fā)服務(wù)器的搜索引擎

    作者:愛寫代碼的剛子 時間:2024.4.24 前言:基于高并發(fā)服務(wù)器的搜索引擎,引用了第三方庫cpp-httplib,cppjieba,項目的要點在代碼注釋中了 index.html index.hpp log.hpp parser.cc(用于對網(wǎng)頁的html文件切分且存儲索引關(guān)系) searcher.hpp util.hpp http_server.cc(用于啟動服務(wù)器和搜索引擎)

    2024年04月28日
    瀏覽(35)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包