上一次,我新增了排程佇列。這次,我將加入狀態機以及執行緒池。
我先來說一說狀態機,然後再說執行緒池。
寫**也是在幹工作。既然幹工作,就是為了解決問題。那加入狀態機,有什麼作用呢?或者說解決了什麼問題呢?
首先,沒有狀態機可以不?當然可以,而且沒有任何問題,就像我前面的**一樣。加入狀態機,就是為了讓程式的邏輯更加清楚,執行更加靈活,同時也可以帶來更高的效率。不過事情總是兩面的,加入狀態機後,程式的執行狀態變得更多了,**的行為更加難以掌控,出現錯誤後,排查起來也更困難了。總之一句話,風險還是很大的。但是我還是要加的,因為收益也是很大很大的。
目前,我的**裡面有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...