https://blog.csdn.net/lijia626482312/article/details/40858061
一個人從接到項目到昨天終於完成,用了差不多4個月,其中各種心酸和眼淚。我的項目是通過網絡從客戶端上采集數據,通訊原則是客戶端有數據要上傳,如果網絡允許就連接服務器,首先客戶端發送一個消息判斷服務器是不是處於忙碌和資源空閑狀態,然后發送文件等等。可以說是一個基於C/S模式的多線程socket程序。
我剛開始那到這個項目,我們經理把項目給我一看,你一個人可以做的出來嗎,我一看,額。。。。。很簡單嗎!must,領導要求,必須做出來啊,從此在這個項目陷進去了。。。。。。
由於以前沒有做過網絡方面的程序,我的開始是從傳統模型開始的,就是accept一個連接就開啟一個線程,這也是我那到這個項目,覺得簡單的地方,很快我就實現了這個問題,我通過連接幾台客戶端,簡單的測試一下,OK,可以正常的采集,基本的功能都可以實現。那好,我把項目一個技術經理,經理一看,小李,你效率挺快的嗎,OK,你做個壓力測試一下。然后我立馬寫了一個測試程序,寫完后,我自信滿滿的拿給測試部門測試,我碰到了這個項目的第一個方向問題。測試部門的主管跟我說,客戶端連接服務器,30、40台客戶端,可以正常運行,客戶端數量再往上的話,就比較容易出現亂碼(也就是亂序),這個程序拿給用戶不行,這是砸我們公司的招牌,我當時就懵了,怎么可能,代碼沒有問題啊!我回去一查,可能是粘包,我的處理方案是精簡代碼結構!寫好后,我自己模擬用100台,測試了一晚上,沒有問題,應該沒有問題了吧,然后我又自信滿滿的拿到測試部門,我又懵了,(⊙o⊙)…,又不行,上面的問題,還是出現了,我想這是人品問題啊,怎么可能,然后在這個問題上,我糾結了差不多快兩個月,代碼再怎么優化都有問題,我都准備徹底放棄了!!!
一次偶然的機會在網上看到windows操作系統有六種模型:select模型,WSAAsyncSelect異步模型,WSAEventSelect事件模型,重疊I/O事件模型,重疊I/O完成例程,IOCP完成端口。我一個一個模型差不多都用了一遍,好像都不好用,前面4種,有windows平台數量限制(64台客戶端),我一看這不行,那就用IOCP吧!
iocp步驟是:
1、創建工作在線程;
SYSTEM_INFO sysinfo;
GetSysteminfo(&sysinfo);
int threadcount = sysinfo.sysinfo.dwNumberOfProcessors*2;
for (DWORD i = 0;i<sysinfo.dwNumberOfProcessors*2;i++)
{
HANDLE m_pWorkThread;
m_pWorkThread = CreateThread(NULL,0,ServerWorkerThread,m_ComletionPort,0,NULL);
CloseHandle(m_pWorkThread);
}
2、創建socket,不同傳統模型,m_listenSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
3、綁定socket,CreateIoCompletionPort((HANDLE)m_listenSock,m_ComletionPort,0,0);
4、傳統模式的bind,listen;
5、accept(acceptex更高效),將iocp綁定客戶端socket
SOCKADDR_IN saRemote;
int RemoteLen=sizeof(SOCKADDR_IN);
SOCKET Accept = INVALID_SOCKET;
Accept = WSAAccept(pDlg->m_listenSock,(SOCKADDR*)&saRemote, &RemoteLen,NULL,0));
PerHandleData[index].Socket = Accept;
memcpy(&PerHandleData[index].ClientAddr,&saRemote,RemoteLen);
HANDLE m_retcompletion = CreateIoCompletionPort((HANDLE)PerHandleData[index].Socket,m_ComletionPort,(DWORD)&PerHandleData[index],0);
6、綁定iocp成功后,投遞接收請求WSARecv(PerHandleData[index].Socket, &(PerIoData[index].DataBuf), 1, &RecvBytes, &Flags,&(PerIoData[index].Overlapped), NULL);
以上就是iocp的大概步驟,我使用上面的模型創建好iocp模型后,發現確實好用不少,3Mb的短連接數據傳輸,接收速度比傳統模型幾乎快了十幾秒。
然后我又進行了模擬100台客戶端壓力測試,發現運行了10幾分鍾系統出現崩潰,錯誤提示是:Debug Assertion Failed!File:Dbgheap.c Line:1011,然后我這個問題上又堵了好幾天,這是什么情況呢,程序的邏輯是收完一段數據,給變一個標志位,然后通過檢索這個標志位,在另外的線程處理,邏輯沒有問題啊,原來windows多線程開發是線程不安全的,多線程中共享數據的訪問,都要加鎖。不然的話,出現內存溢出,數組越界等等奇怪的問題。
還有iocp如何區分每一個socket,我是采用檢索全局socket,GetQueuedCompletionStatus被激活后,將接收到的數據放到各自的全局變量區域。全局變量怎么申請呢,如果連接數量夠多,申請固定大小的全局內存空間,在程序啟動的時候會出現內存不足,解決辦法就是在堆中申請內存(也就是malloc,注意malloc之后別忘記free,如果頻繁的申請釋放內存空間,容易出現磁盤碎片,不鼓勵使用這種辦法)。
投遞多個wsarecv有必要嗎,投遞多個能充分的使用cpu,這個不可否認,但是投遞之后我們需要花更多的內存處理時間去組包和處理數據,如何數據還沒有收完的話,還需要進一步的投遞多個包,然后再組包和處理數據。。。。。。這樣的效率更慢,我覺得一來一回(接收數據先判斷自己的數據是否收滿,沒有收滿再投遞一個wsarecv)的方式已經可以滿足自己的需要了,但是一來一回的處理方式,需要注意,GetQueuedCompletionStatus所在的工作着線程最好是不要做過多的數據處理工作,以免影響客戶端發送的太快,服務器接收的太慢(如果出現這個問題,容易引起遠程主機主動關閉socket錯誤)。
iocp中是如何進行超時判斷的?服務器資源一定,客戶端如果一直連接而不發送數據或者出現各種意外客戶端斷開連接服務器而沒有及時發現,服務器會出現資源不夠用的情況,這時就要超時判斷。工作者線程中記錄wsarecv和WSASend操作的時間,然后再另外開一個線程判斷上一次的處理wsarecv和wsasend操作跟當前時間是否超時(可以根據自己需求設定,mfc中可以使用CTimespan::GetTotalSeconds()取得時間差),如果超時就關閉socket,釋放相應的內存等等。
與IO打交道,估計是所有iocp的難題,據相關資料記載(我也不知道是在哪里看到的,IO外設的任何操作速度相對cpu處理速度來說,cpu開了1000多倍),而iocp工作者線程池設計目的為了充分發揮cpu的性能,所以工作者線程中最好不要處理IO(這就出現一個問題,空間換時間,內存不夠使用)。
工作者不處理IO,那就帶來了內存的使用問題,IO處理慢,未處理的IO比較多,未處理內存積壓越來越多,內存不夠用。32位操作系統,內存使用機制,內核默認2G內存,程序員自己可用2G。可以通過設置c盤的boot.ini隱藏文件設置3GB程序員可用內存(具體方法可自己上網搜索)。
昨天終於寫好iocp服務器程序,在內存大小限制情況,短連接,每個連接都只上傳6MB以內的數據,能跑500台端機,不過IO處理是關鍵,如果服務器忙碌的時候,在服務器上做其他操作如刷屏、打印、鍵盤輸入數據等,會出現寫文件很慢,內存很容易出現不夠用的情況,不過,這已經能滿足我的性能需求了。
終於能長長的松一口氣。。。。。。好開心啊!