Indy的TCPServer到底能支持多少个连接

zkfly 2006-03-18 06:11:48
最近一个项目,最开始使用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服务器的,你们的服务器可以连接多少客户端呀?
...全文
1137 点赞 收藏 13
写回复
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
zkfly 2006-03-20
用纯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.
回复
zkfly 2006-03-19
Woo~看来讨论挺激烈呀。再加些分。多讨论下。
回复
halfdream 2006-03-19
多个进程远远比多线程更耗资源..
如果是多用户长连接,可以考虑使用IOCP,用少量线程服务大量客户.
至于线程池,倒是一般都用到的..主要能在大量短连接通讯时候起作用.

回复
楼上的,不可能。
一个系统中总的线程数量是有限的,这是因为
每一个线程都要分配一个TCB,4KB,这个是不能交换到虚存的,另外还要分配局部stack。
内存空间的限制决定了线程的数量不可能很多。
而且活跃线程增加到一定程序,系统的响应速度就严重降低了。
回复
BlueTrees 2006-03-19
我又想到了一个提高负载的办法。

就是创建多个进程,单一进程的线程数量是受到限制的,但是,使用多个进程就能避免这个结果。

可以根据负载情况,灵活的增加进程数量,但是,一个进程只能打开一个同一个端口的句柄,好像有一个复制句柄的方式,让别的进程共享同一个Socket句柄。

如果实现这个,就不受线程数量的限制了,不过,我不是很清楚windows在实现阻塞Socket的方式,阻塞的好像是循环的,不能释放线程的,效率低下,应该使用异步Socket,这样才能让等待线程挂起。

回复
分布式处理是最佳途径。
多增加几台服务器的硬件价格是微不足道的,
即使你使用更复杂的模型,完成端口+线程池,
所能增加的用户数量也是有限的,也就5%吧。
而且这种模式也是难以移植的。

回复
BlueTrees 2006-03-19
用一个线程绑定一个客户连接,效率比较差了,

对于密集型的应用,不能用indy这样的控件,他太复杂了,反而没有效率了。

对于这样的应用,只能采用非阻塞的方式了,虽然比较麻烦,但是只有这个办法,让一个线程为多个客户服务。

同时,要使用线程池,不要客户断开就释放线程,线程可以挂起留着再用。
回复
tsbob 2006-03-19
indy的tcpserver使用的io模型是多线程阻塞socket,对超过1000的并发连接,并不是好的选择,indy10并不支持iocp,里面的纤程是针对unix传统的多进程模型在windows上的移植.这样的情况,建议使用iocp,看看msdn,自己写吧,也不是很复杂
回复
zkfly 2006-03-18
To ly_liuyang,THX,看了IOCP,还没有开始做就昏了。明天继续学习。
To getit911,THX,我怎么安装不了SuperCore的包呀,编译通不过,你是怎么安装的?我也看到Indy的帮助说纤程,当时也奇怪,怎么他的IOHandler里面没有这个东西,都是线程的。

再顶一下,明天来结分。
回复
clasj 2006-03-18
一台服务器能够处理2000个连接已经不错了,多搞几台服务器吧。
回复
减少线程的stack空间可以增加线程数量,
或者在xp+(只是xp+,不包括win2k)的boot.ini中加上/3GB选项也可以增加进程可用的地址空间。
但是不管怎么说,
在32-bit的Windows上,最多的同步线程在2000~3000这个水平。
支持更多的用户可以使用分布式处理,
即你的主服务器把试图连接的客户端分配到其他服务器去。
象QQ、hotmail之类的都是这么处理的,
每次你连接的都是www.hotmail.com,
但是具体完成服务的可能是xxxxx.xxxx.hotmail.msn.com之类的。
回复
getit911 2006-03-18
Indy10支持IOCP和纤程,不过要安装SuperCore包(默认没有),并设置Server的IOHandle,否则Indy10的效能在Windows和Indy9一样
回复
ly_liuyang 2006-03-18
没做过这中压力测试得呢

真有这么大量的连接,建议做IOCP+WinSock API,别用控件的

Windows的操作系统不一只支持2028个线程的!
用CreateThread测试,数万个都OK的,只是越来越慢而已

大量的连接非常注重内存泄漏等问题的,并需要修改源码的
回复
发动态
发帖子
网络通信/分布式开发
创建于2007-08-02

1566

社区成员

Delphi 网络通信/分布式开发
申请成为版主
社区公告
暂无公告