為何要寫這篇文章 ,之前看過阿二的夢想船的<Poco::TCPServer框架解析> http://www.cppblog.com/richbirdandy/archive/2010/09/10/123994.html
無奈代碼太多,看起繁瑣.所以 准備 以流程圖簡化,便於理解.也方便自己以後使用.
本文內容 是基於window api分析的.
本文的poco是1.4.6p4 (2014-04-18)版本的. 雖然現在poco版本是1.6 但調用改動不大.
poco下載地址:http://pocoproject.org/releases/
本文分析以 TimeServer.cpp 作為入口分析:
1,Inline 內聯函數:可以參考:
http://blog.sina.com.cn/s/blog_90e888f50100zgo2.html
主要是提升執行效率.
2,類成員函數的 重載,重寫,隱藏,
參考:
dazhong159 的<類成員函數的重載、重寫、和覆蓋區別>
http://blog.csdn.net/dazhong159/article/details/7844369
代碼中大量使用,重寫,隱藏.
3,select模型的原理:
引用
很幽默的講解六種Socket I/O模型
http://blog.csdn.net/normalnotebook/article/details/999840
的內容:
for i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是說select只能同時管理最多64個連接
是同步操作.
老陳非常想看到女兒的信。以至於他每隔10分鐘就下樓檢查信箱,看是否有女兒的信~~~~~
在這種情況下,"下樓檢查信箱"然後回到樓上耽誤了老陳太多的時間,以至於老陳無法做其他工作。
select模型和老陳的這種情況非常相似:周而復始地去檢查......如果有數據......接收/發送.......
..... MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); addr.sin_family := AF_INET; addr.sin_port := htons(5678); addr.sin_addr.S_addr := htonl(INADDR_ANY); bind( MainSock, @addr, sizeof(addr) ); listen( MainSock, 5 ); while (not Terminated) do begin FD_ZERO( fd_read ); FD_SET( MainSock, fd_read ); timeout.tv_sec := 0; timeout.tv_usec := 500; if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1個等待Accept的connection begin if FD_ISSET( MainSock, fd_read ) then begin for i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是說select只能同時管理最多64個連接 begin len := sizeof(addr); ASock := accept( MainSock, addr, len ); if ASock <> INVALID_SOCKET then ....//為ASock創建一個新的線程,在新的線程中再不停地select end; end; end; end; //while (not self.Terminated) shutdown( MainSock, SD_BOTH ); closesocket( MainSock ); end;
所以,select模型,只能用於一般的小型連接....高並發是不行的.
4,
C++構造函數按下列順序被調用:
(1)任何虛擬基類的構造函數按照它們被繼承的順序構造;
(2)任何非虛擬基類的構造函數按照它們被繼承的順序構造;
(3)任何成員對象的構造函數按照它們聲明的順序調用;
(4)類自己的構造函數。
5,關於 FastMutex 互斥變量
bool NotificationQueue::empty() const
{
FastMutex::ScopedLock lock(_mutex);
return _nfQueue.empty();
}
在 empty() 執行完成後 調用 ~FastMutex::ScopedLock析構函數來釋放.
window下是使用的臨界區:
class Foundation_API MutexImpl
{
protected:
MutexImpl();
~MutexImpl();
void lockImpl();
bool tryLockImpl();
bool tryLockImpl(long milliseconds);
void unlockImpl();
private:
CRITICAL_SECTION _cs;//臨界區
};
可以看見,windows 下環境使用的臨界區.
6,關於線程:
window下使用
_thread = (HANDLE) _beginthreadex(NULL, _stackSize, ent, this, 0, &threadId);
執行線程的操作.
7,等待事件,連接請求的同步是使用的
WaitForSingleObject(這也是我的最愛)
通過SetEvent () ,ResetEvent() 來激活重置.
8,static_cast<> reinterpret_cast<> dynamic_cast<> 的使用.
可參考:
http://www.cnblogs.com/bastard/archive/2011/12/14/2288117.html
http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html
像代碼中:
void* pThread;
reinterpret_cast<ThreadImpl*>(pThread)->_pRunnableTarget->run();
//reinterpret_cas 這個轉換是最“不安全”的,兩個沒有任何關系的類指針之間轉換都可以用這個轉換實現,舉個例子
_threadId = static_cast<DWORD>(threadId);
//static_cast 用於基本的數據類型轉換(char,int),及指針之間的轉換
9,關於智能(靈巧)指針auto_ptr.
auto_ptr 簡單點說,就是 保證創建的資源 在退出時能被free(無論有沒有異常)
std::auto_ptr<TCPServerConnection> pConnection(_pConnectionFactory->createConnection(pCNf->socket()));
AutoPtr<Notification> pNf = _queue.waitDequeueNotification(idleTime);
可以直接 在<memory>中找到
template<class _Ty>
class auto_ptr
{ // wrap an object pointer to ensure destruction
可以參考:
More Effective C++中文版.pdf 7.4 Item M28:靈巧(smart)指針 章節(baidu 查到下載)
http://blog.chinaunix.net/uid-9525959-id-2001803.html
中的片段:
如何避免使用auto_ptr的缺陷
auto_ptr並不是完美無缺的,它的確很方便,但也有缺陷,在使用時要注意避免。首先,不要將auto_ptr對象作為STL容器的元素。C++標准明確禁止這樣做,否則可能會碰到不可預見的結果。
auto_ptr的另一個缺陷是將數組作為auto_ptr的參數:
auto_ptr<char> pstr (new char[12] ); //數組;為定義
記住不管什麼時候使用數組的new操作時,必須要用delete[]來摧毀數組。因為auto_ptr的析構函數只對非數組類型起作用。所以數組是不能被正確摧毀的話,程序的行為是不明確的。總之,auto_ptr控制一個由new分配的單對象指針,僅此而已。
不過C++ 11標准中解決了這問題:
unique_ptr
smart pointer with unique object ownership semantics
只能有一個主人的指針,可以用於STL容器
shared_ptr
smart pointer with shared object ownership semantics
可共享的指針
weak_ptr
weak reference to an object managed by std::shared_ptr
弱引用指針
auto_ptr
smart pointer with strict object ownership semantics
只能有一個主人的指針,不能用於STL容器
走遠了,想深入(不要想多了-_- ),請baidu...
看完上面之些,發現是不是覺得 各種知識又鞏固了.
所以還是要看開源代碼,之前公司整死不用開源的...哎...
圖片過寬,不能顯示(請 在新標簽中打開圖片.謝謝.)
1,TCPServer 主服務,負責 調用select 模型,來處理 連接消息的變化.
2,PooledThread 是線程池.當被激活時,調用 TCPServerDispatcher::run() 來處理收到包後的具體請求.而 TCPServerDispatcher::run() 中調用
TimeServerConnection.run(). TimeServerConnection.run()通過子類隱藏 來實現 程序員 自定義 功能. 不得不說寫POCO的大牛利害.
3,TCPServerDispatcher,派遣管理者(先這麼叫吧). 接收消息變化,放入隊列.管理 連接數.
當放入隊列時,會激活 PooledThread 中的事件 .
PooledThread 又反過來 激活 TCPServerDispatcher::run() [姑且叫 有條件時相互激活吧 ]
4,TCPServerConnection.實現具體行為,通過繼承由子類的 run() 來自定義實現 功能.
5,TCPServerConnectionFactory 負責創建和管理 TCPServerConnection.
6,TCPServerParams 這個參數管理 ,就不說了.你懂的.
7,NotificationQueue 把 連接 放入隊列.進行管理.
看完主要幾個類的介紹,其它流程都應該懂大概了.
由於圖太長的關系多,
圖片過寬,不能顯示(請 在新標簽中打開圖片.謝謝.)
windows 下的select的確性能不太行,而linux 版本是用的epoll.
epoll相對select 要高效點.可參考:http://blog.csdn.net/legion8169/article/details/1637154
但poco tcpserver 中是有線程池操作的,所以說來效率不會太低.
先到這兒,還沒有寫完.
我們可以改變什麼:
ThreadPool(int minCapacity = 2, int maxCapacity = 16, int idleTime = 60, int stackSize = POCO_THREAD_STACK_SIZE); /// Creates a thread pool with minCapacity threads. /// If required, up to maxCapacity threads are created /// a NoThreadAvailableException exception is thrown. /// If a thread is running idle for more than idleTime seconds, /// and more than minCapacity threads are running, the thread /// is killed. Threads are created with given stack size.
增加線程池中線程數(費話!),來加快select 中處理.
在中等程序中,增加 TCPSelect Manage進程, 來負責與多個 TcpServer 的進程通信.
即再增加一個管理者(中間鍵,或activemq之類),來加強並發能力,
或者直接使用linux 環境 ,即用了epoll 來實現高效性.
個人愚見,可能有些沒寫明白.還望高手指點.
謝謝.