最近一個項目,最開始使用IdTcpServer,在大壓力測試的時候,只連接了800個多一點的客戶端(每個客戶端連接上之后每秒鍾發送一個幾十字節的報文,服務器應答)。但是持續的時間不會超過10分鍾,服務器就會掛掉(經常是服務器突然關閉消失,任何提示都沒有)。后來優化了互斥量之后,可以連接到1000多個客戶端。但是服務器消失的問題依然存在。
今天再一台雙CPU,4G內存的服務器上試驗了下,居然最也只能連接到2000多個客戶端。然后換了Indy10.1.5服務器只做簡單的連接和應答,客戶端連接之后只發送一個報文,還是一樣,服務器最多只能連接2000多個客戶端。
然后下載了Indy10的官方幫助文件(http://www.projectindy.org/downloads/IndyDocs_10.1.5.0_HtmlHelp.zip)它的TCPServer幫助里面有一段話:
But there are performance and resource limitations imposed by the platform or Operating System on the number of threads that can be created. On Windows, for example, the number of threads is limited by the available virtual memory. By default, every thread gets one megabyte of stack space and implies a theoretical limit of 2028 threads. Reducing the default stack size will allow more threads to be created, but will adversely impact system performance.
意思理論上Windows的操作系統最多支持2028個線程。於是昏倒了。因為我們的客戶端最少也會有5000個呀。
后來有換了delphi的異步非阻塞TCPServer,測試連接了15000個客戶端都沒有啥問題。
難道Indy真的只能支持到2000多個客戶端連接?
各位大俠有用過Indy的TCP服務器的,你們的服務器可以連接多少客戶端呀?
沒做過這中壓力測試得呢
真有這么大量的連接,建議做IOCP+WinSock API,別用控件的
Windows的操作系統不一只支持2028個線程的!
用CreateThread測試,數萬個都OK的,只是越來越慢而已
大量的連接非常注重內存泄漏等問題的,並需要修改源碼的
Indy10支持IOCP和纖程,不過要安裝SuperCore包(默認沒有),並設置Server的IOHandle,否則Indy10的效能在Windows和Indy9一樣
減少線程的stack空間可以增加線程數量,
或者在xp+(只是xp+,不包括win2k)的boot.ini中加上/3GB選項也可以增加進程可用的地址空間。
但是不管怎么說,
在32-bit的Windows上,最多的同步線程在2000~3000這個水平。
支持更多的用戶可以使用分布式處理,即你的主服務器把試圖連接的客戶端分配到其他服務器去。象QQ、hotmail之類的都是這么處理的,
每次你連接的都是www.hotmail.com,但是具體完成服務的可能是xxxxx.xxxx.hotmail.msn.com之類的。
一台服務器能夠處理2000個連接已經不錯了,多搞幾台服務器吧。
To ly_liuyang,THX,看了IOCP,還沒有開始做就昏了。明天繼續學習。
To getit911,THX,我怎么安裝不了SuperCore的包呀,編譯通不過,你是怎么安裝的?我也看到Indy的幫助說纖程,當時也奇怪,怎么他的IOHandler里面沒有這個東西,都是線程的。
再頂一下,明天來結分。
indy的tcpserver使用的io模型是多線程阻塞socket,對超過1000的並發連接,並不是好的選擇,indy10並不支持iocp,里面的纖程是針對unix傳統的多進程模型在windows上的移植.這樣的情況,建議使用iocp,看看msdn,自己寫吧,也不是很復雜
用一個線程綁定一個客戶連接,效率比較差了,
對於密集型的應用,不能用indy這樣的控件,他太復雜了,反而沒有效率了。
對於這樣的應用,只能采用非阻塞的方式了,雖然比較麻煩,但是只有這個辦法,讓一個線程為多個客戶服務。
同時,要使用線程池,不要客戶斷開就釋放線程,線程可以掛起留着再用。
分布式處理是最佳途徑。
多增加幾台服務器的硬件價格是微不足道的,
即使你使用更復雜的模型,完成端口+線程池,
所能增加的用戶數量也是有限的,也就5%吧。
而且這種模式也是難以移植的。
我又想到了一個提高負載的辦法。
就是創建多個進程,單一進程的線程數量是受到限制的,但是,使用多個進程就能避免這個結果。
可以根據負載情況,靈活的增加進程數量,但是,一個進程只能打開一個同一個端口的句柄,好像有一個復制句柄的方式,讓別的進程共享同一個Socket句柄。
如果實現這個,就不受線程數量的限制了,不過,我不是很清楚windows在實現阻塞Socket的方式,阻塞的好像是循環的,不能釋放線程的,效率低下,應該使用異步Socket,這樣才能讓等待線程掛起。
樓上的,不可能。
一個系統中總的線程數量是有限的,這是因為
每一個線程都要分配一個TCB,4KB,這個是不能交換到虛存的,另外還要分配局部stack。
內存空間的限制決定了線程的數量不可能很多。
而且活躍線程增加到一定程序,系統的響應速度就嚴重降低了。
多個進程遠遠比多線程更耗資源..
如果是多用戶長連接,可以考慮使用IOCP,用少量線程服務大量客戶.
至於線程池,倒是一般都用到的..主要能在大量短連接通訊時候起作用.
Woo~看來討論挺激烈呀。再加些分。多討論下。
用純Winsock API做了一個簡單的TcpServer,沒有IOCP
采用線程方式,一個服務器最多也只能創建2000個左右的連接線程,之后出現10053(軟件造成連接取消)錯誤。證明不是Indy控件的問題。
接下來再試試異步方式。
Winsock API的程序代碼如下:
program WinSockSvr;
{$APPTYPE CONSOLE}
(*-------------------------------------------------------------------
說明:TCP/IP 服務器演示程序 Ver0.1
采用標准WinSock2.2 API函數
采用線程堵塞模式,每個客戶端連接創建一個收發線程。
作者:Zuni
時間:2006-3-20
使用到的函數說明:
WSAStartup //初始化WinSock
WSACleanup //釋放WinSock引用
socket //創建Socket,對服務器來說,就是創建偵聽Socket
bind //綁定偵聽套接字到本機
listen //開始偵聽
accept //接受客戶端連接
recv //從套接字接受數據
send //向套結字發送數據
closesocket //關閉套接字
getpeername //獲取套接字的客戶端的TSockAddr結構
inet_ntoa //把IPv4網絡地址轉成點分地址
ntohs //網絡字節序轉換成主機字節序
htons //把主機字節序轉成網絡字節序
-------------------------------------------------------------------*)
uses
WinSock, System, SysUtils, StrUtils, ScktComp, Windows;
const
DEF_BUF = 4095;
var
iPort : Integer = 2004;
bEcho : Boolean = True;
iCount : Integer = 0;
procedure Useage;
begin
WriteLn('usage: WinSockSvr [-p:x] [-o]');
WriteLn(' -p:x Port number to listen on');
WriteLn(' -n: Do not echo the receved world');
WriteLn('');
Halt;
end;
procedure DoArgs;
var
i : Integer;
s : string;
sv : string;
begin
// if ParamCount<1 then
// Useage;
for i:=1 to ParamCount do
begin
s := ParamStr(i);
sv := RightStr(s, Length(s)-3);
if (s[1]='-') or (s[1]='/') then
begin
try
case UpCase(s[2]) of
'P': iPort := StrToInt(sv);
'N': bEcho := False;
end;
except
Useage;
end;
end else
Useage;
end;
end;
function ClientThread(Param: Pointer): Integer; stdcall;
var
iRet : Integer;
s : TSocket;
saPeer : TSockAddr;
cBuf : array[0..DEF_BUF] of Char;
sBuf : string;
iLeft : Integer;
idx : Integer;
i : Integer;
begin
s := TSocket(Param);
i := SizeOf(saPeer);
getpeername(s, saPeer, i);
while True do
begin
iRet := recv(s, cBuf, DEF_BUF+1, 0);
if iRet=0 then
begin
WriteLn(Format('Client(%s:%d) is close gracefully', [inet_ntoa(saPeer.sin_addr), saPeer.sin_port]));
closesocket(s);
DEC(iCount);
Break;
end;
if iRet=SOCKET_ERROR then
begin
WriteLn('recv() function failed, Error code', WSAGetLastError);
closesocket(s);
DEC(iCount);
Break;
end else
begin
cBuf[iRet] := #0;
Write(Format('Get message from %s:%d ', [inet_ntoa(saPeer.sin_addr), saPeer.sin_port]));
SetLength(sBuf, iRet);
sBuf := cBuf;
WriteLn(sBuf);
end;
//發送反饋
if bEcho then
begin
sBuf := 'You sent message: '+ sBuf;
iLeft := Length(sBuf)+1; //加1是為了多加個換行符
for i:=0 to iLeft-2 do
begin
cBuf[i] := sBuf[i+1];
end;
cBuf[iLeft-1] := #10;
idx := 0;
while (iLeft>0) do
begin
iRet := send(s, cBuf[idx], iLeft, 0);
if iRet=0 then
begin
WriteLn(Format('Client(%s:%d) is close gracefully', [inet_ntoa(saPeer.sin_addr), saPeer.sin_port]));
closesocket(s);
DEC(iCount);
Break;
end;
if iRet=SOCKET_ERROR then
begin
WriteLn('send() function failed, Error code', WSAGetLastError);
closesocket(s);
DEC(iCount);
Break;
end;
iLeft := iLeft - iRet;
idx := idx + iRet;
end;
end;
end;
Result := 1;
end;
var
wsd : TWSAData;
saSrv : TSockAddr;
saClt : TSockAddr;
scktListen : TSocket;
scktClient : TSocket;
iLen : Integer;
hThread : THandle;
dwThreadId : DWORD;
begin
DoArgs;
//初始化WinSock,版本號2.2
if (WSAStartup($0202, wsd)<>0) then
begin
WriteLn('Fail to load WinSock2.2');
Exit;
end else
WriteLn('Step 1: Load WinSock Succ, Current Version: ', IntToHex(wsd.wHighVersion,4));
//創建偵聽Socket
scktListen := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if scktListen=INVALID_SOCKET then
begin
WriteLn('Fail to create a listen socket, Error code: ', WSAGetLastError);
WSACleanup;
Exit;
end else
WriteLn('Step 2: Creat listen socket Succ');
//綁定Listen Socket到本機
saSrv.sin_family := AF_INET;
saSrv.sin_port := htons(iPort);
saSrv.sin_addr.S_addr := htonl(INADDR_ANY);
if bind(scktListen, saSrv, SizeOf(saSrv))=SOCKET_ERROR then
begin
WriteLn('Fail to bind the listen socket, Error code: ', WSAGetLastError);
Closesocket(scktListen);
WSACleanup;
Exit;
end else
WriteLn('Step 3: Bind Succ, Listen port is ' , saSrv.sin_port );
//開始偵聽
if listen(scktListen, SOMAXCONN)=SOCKET_ERROR then
begin
WriteLn('Fail to listen, Error code: ', WSAGetLastError);
Closesocket(scktListen);
WSACleanup;
Exit;
end else
WriteLn('Step 4: Listening......');
WriteLn('');
while True do
begin
//接受客戶端連接
iLen := SizeOf(saClt);
scktClient := accept(scktListen, @saClt, @iLen);
if scktClient=INVALID_SOCKET then
begin
WriteLn('Fail to accept a client connect, Error code: ', WSAGetLastError);
Break;
end else
WriteLn('Accept client: ', inet_ntoa(saClt.sin_addr), ':', ntohs(saClt.sin_port));
//創建客戶端通訊線程
hThread := CreateThread(nil, 0, @ClientThread, Pointer(scktClient), 0, dwThreadId);
if hThread=0 then
begin
WriteLn('Fail to create client thread, Error code: ', GetLastError);
Break;
end;
INC(iCount);
WriteLn(Format('.....................................Count: %d',[iCount]));
CloseHandle(hThread);
end;
closesocket(scktListen);
WSACleanup;
end.
http://blog.csdn.net/tercel99/article/details/46689445