伺服器端的高效能實現(六) 狀態機和執行緒池的引入

2021-04-15 17:30:51 字數 4534 閱讀 5833

上一次,我新增了排程佇列。這次,我將加入狀態機以及執行緒池。

我先來說一說狀態機,然後再說執行緒池。

寫**也是在幹工作。既然幹工作,就是為了解決問題。那加入狀態機,有什麼作用呢?或者說解決了什麼問題呢?

首先,沒有狀態機可以不?當然可以,而且沒有任何問題,就像我前面的**一樣。加入狀態機,就是為了讓程式的邏輯更加清楚,執行更加靈活,同時也可以帶來更高的效率。不過事情總是兩面的,加入狀態機後,程式的執行狀態變得更多了,**的行為更加難以掌控,出現錯誤後,排查起來也更困難了。總之一句話,風險還是很大的。但是我還是要加的,因為收益也是很大很大的。

目前,我的**裡面有3個主要的執行緒:乙個是管理socket的,主要工作是遍歷處於等待佇列裡面的socket;乙個是監聽客戶端連線請求的;乙個是遍歷接收佇列裡面的socket,接收客戶端發來的資訊的。

讓我先從第乙個,也就是管理socket的執行緒說起。

它在剛剛啟動的時候,自動進入idle狀態,在這個狀態裡面,執行緒會掛起,直到被喚醒為止。執行緒被喚醒後,可能:

1,再次進入idle狀態,繼續掛起等待;

2,進入running狀態;

3,進入shutdown狀態。

當執行緒進入running狀態後,它首先檢測當前socket的個數,如果為0,就重新進入到idle狀態下面。如果不為0,就像先前做的那樣,從等待佇列裡面取出乙個socket進行判斷,如果可以接收資料,就把socket放到接收佇列裡面。這個處理過程還是跟以前一樣,沒有什麼變化。

如果執行緒進入了shutdown狀態,就清空佇列裡面的socket,然後執行緒退出。

這個執行緒的**在類connectionmanager裡面,主要變化的部分如下:

首先,新增了乙個列舉型別——connectionstates,包括3個狀態,就是上面提到的那3個。

然後,給這個類新增乙個私有成員:

private manualresetevent _event = new manualresetevent(false);

這個event,用來控制線程的執行和掛起。

接著新增乙個方法:

public void signalevent(connectionstates state)

這個方法,主要是提供給外部呼叫者使用的,用來改變執行緒的狀態。但是這裡有乙個陷阱,一定要先改變state,再呼叫event的set方法,喚醒執行緒。否則很可能會出現很多古怪的問題,原因在後面會有解釋。

修改一下addsocket方法,在儲存socket的**之後,新增下面的**:

signalevent(connectionstates.running);

這行**的目的,就是當有新的連線的時候,就啟用當前執行緒,進入running狀態。

現在,來看一下加入了狀態機後的startwait方法:

主要改變在while裡面,如下:

其中被我用...標註的地方,表示沒有變化。

private void startwait()

try}

catch(...)

break;

case connectionstates.shut_down:

//釋放socket,然後退出

_start = false;

removeallsockets();

break;}}

}connectionmanager主要的改變就是這些了,此外,還對功能做了一些完善,新增了下面的東西:

public void stop()

現在,我來解釋一下signalevent裡面那兩行**的順序問題。

假如反過來,像下面這樣:

this._event.set();

this._state = state;

會有什麼問題呢?舉個可能的例子,如下:

執行緒1:進入while迴圈,然後進入case connectionstates.idle,執行case下面的_event.reset()和_event.waitone(),開始等待

執行緒2:呼叫signalevent(connectionstates.shut_down),執行到this._event.set();

執行緒1:被啟用,從新進入switch,這時候狀態還是idle,重新開始等待。

執行緒2:執行this._state = state,改變狀態。

好了,現在問題來了。雖然呼叫了signalevent,但是沒起作用。那難到後執行set就可以了麼?其實也不是100%安全,但是我們可以想辦法去避免。

現在把順序調整過來,像**裡那樣:

this._state = state;

this._event.set();

看看此時的漏洞:

執行緒1:進入while迴圈,然後進入case connectionstates.idle

執行緒2:呼叫signalevent,執行上面那兩行**

執行緒1:執行case下面的_event.reset()和_event.waitone(),開始等待

現在,connectionmanager的工作基本完成了,下面是receiver的修改。

同樣,在receiver裡面,我也加入了狀態機,依然是上面提到過的那3個狀態。主要的修改在startreceive裡面,下面列出**中主要進行了修改的部分:

private void startreceive()

catch (system.exception ex)

}if (core.getinstance.connectionmanager.receiverqueue.count == 0)

break;

case receiverstates.shut_down:

_start = false;

break;}}

}整個的狀態轉換,也與connectionmanager很相似,因此我就不多說了。

不過還是有乙個地方要做點修改,只不過是在connectionmananger的addsocket裡面。當呼叫signalevent把connectionmanager自己啟用的時候,順便再呼叫一下receiver的signalevent,將receiver也啟用。

接下來,是listener的修改。它的狀態也是3個,跟前面的一樣,狀態轉換的方式也一樣。主要改變都在listenonaccept裡面,下面是**:

private void listenonaccept()

catch(...)

break;

case listenerstates.shut_down:

listener.stop();

_start = false;

break;}}

}因為listener比較簡單,我沒有為它新增event,因此它的signalevent裡面只有狀態轉換,看起來有點名不副實,呵呵。

經過了這麼大的修改,上層的core肯定也會有不小的改變。主要就是start和stop,如下:

public void start()

public void stop()

其實也很好看懂。

先是宣告乙個執行緒池:

private sonic.net.threadpool threadpool;

再宣告乙個task

private mytask task;

然後是例項化:

threadpool = new sonic.net.threadpool((short)configprovider.getinstance.maxthreads, (short)configprovider.getinstance.concurrentthreads);

task = (mytask)activator.createinstance(type.gettype(configprovider.getinstance.task));

然後是使用,當有資料可以接收的時候,就打包:

//接收資料

byte buffer = new byte[32];

int bytes_read = asocket.getstream().read(buffer, 0, 10);

//打包成任務

mytask atask = task.newtask();

atask.buffer = buffer;

//放到執行緒池裡

threadpool.dispatch(atask);

退出的時候,在shut_down狀態下面新增關閉就可以了,如下:

threadpool.close();

接著再說一下mytask這個東西。就是乙個介面:

using sonic.net;

namespace server4win.util}}

再解釋一下下面這行**:

task = (mytask)activator.createinstance(type.gettype(configprovider.getinstance.task));

這是乙個反射呼叫。我寫了乙個類,叫做demotask,它實現了mytask介面。然後我把這個demotask配置到了配置檔案裡面,這樣就可以通過反射,動態的指定呼叫哪個介面的實現了。

配置檔案裡面的內容如下:

這次的內容有點多,寫得也有些亂,希望別看暈了。

下回我會重新回到linux下面,把這段時間的成果在linux上面實現一下。

實現迭代伺服器端和客戶端

前面的程式,不管伺服器端還是客戶端,都有乙個問題,就是處理完乙個請求立即退出了,沒有太大的實際意義。能不能像web伺服器那樣一直接受客戶端的請求呢?能,使用 while 迴圈即可。修改前面的回聲程式,使伺服器端可以不斷響應客戶端的請求。伺服器端 server.cpp include include ...

QPS 和併發 如何衡量伺服器端效能

qps 和併發 如何衡量伺服器端效能 和併發相關不得不提的乙個概念就是 qps query per second qps 其實是衡量吞吐量 throughput 的乙個常用指標,就是說伺服器在一秒的時間內處理了多少個請求 我們通常是指 http 請求,顯然數字越大代表伺服器的負荷越高 處理能力越強。...

WebSocket的C 伺服器端實現

由於需要在專案中增加websocket協議,與客戶端進行通訊,不想使用開源的庫,比如websocketpp,就自己根據websocket協議實現一套函式,完全使用c 實現。一 原理 websocket協議解析,已經在前面部落格裡面詳細講解過,可以參考部落格這裡就不詳細細說。伺服器端實現就是使用tcp...