讨论:如果服务器需要承受1000个以上的并发,你同意使用线程阻塞式还是非阻塞式?(300分)

  • 主题发起人 主题发起人 barton
  • 开始时间 开始时间
to 张无忌:
有关连接安全不是本系统需要解决的问题。因为无论哪种方式都有这个问题而非仅仅我的控
件。有必要加一个IP黑名单列表。
 
barton,你写的那个控件能不能来一份?:)
copy_paste@163.com
 
to copy_paste:
一点导读:
1.我专门写了一个流类TWsStream,方便读出某个结构,其实用TMemoryStream也是可以的。
2.在TIOCPServer中采用了这几项技术:
a.座位管理:对所有的连接采用座位式管理,虽然借用TList类,但活动时决不做Delete操
作,只有暂停时才做Clear。每个连接在TList中的Index就是它的座位号,便于快速寻址。
连接结束后,只将那个座位变空,然后下一个连接可以继续用,如果没有空座位了,再用
Add方法建立一个。这样这个TList的Count就是连接的Peak数。每个连接的座位号还有个特
殊用处:就是建立完成端口时的CompletionKey值(单句柄访问)。
b.VCL异步。因为读出数据后不适合在线程中做数据处理,所以,工作者线程在读出数据
后直接交给TIOCPServer后返回。TIOCPServer在收到线程通知后,将数据包放入包队列,
然后建立一个消息,发送给窗口过程,窗口过程将数据包传递给VCL再处理它就线程安全
了。
c.每个连接就建立一个对象是避免不了的。而且现在所有的Send方法不是直接通过调用
TIOCPServer来实现,而是通过这个对象的Send方法将数据发出去。我的客户端控件只是
个例子,虽然有错,但可以用来做测试。
3.现在已经发现好下问题:
a.当客户主动断开时,服务器并不切断;
b.当客户端多发几个数据包时,会搞死服务器。

4.结论:该控件还不可用。
 
多线程在同时连接的数目达到200以上确实有些问题:
1.每个连接每次向服务端申请200K左右的数据。
2.当运行一段时间后,随即出现服务端死机。
3.开始开始认为是资源没有释放,但后来确定非此问题。把并行连接的数目减少到100
左右即可。服务端操作系统是98。
4.我现在也想知道当并发请求的数量有点大且每次的流量还不小的情况时,到底怎样
解决才是好办法?
5.希望大家给个好点子,谢谢!!!
 
绝对支持异步Socket,编写程序非常简单,效率极高!!同步早就过时了!!!
 
barton
差点忘了问你了,你现在做的这个多线程+阻塞的结构中,在客户端中断开使用
WSAEnumNetworkEvents来进行确定是否断开还是其它方法,不知如果是异常断开是使用什么方式?
还是像SConnect中那样,Receive一下,接收不了就算是断开了,但有时会阻塞在那里。。。
有段跟一个网友一直在搞这,后来也是用WSAEnumNetworkEvents也确定,但发现它也只是有时
工作正常,后面那人用Ping来做这事,没法子。

我前段看了些VC的IOCP,现在IOCP的方法好像是不止一种,即除了: CreateIoCompletionPort之外
还有其它的方式。而且感觉用起来的方法,像极了串行方式似的。
而看CreateIoCompletionPort方式的程序,它是用WSABUF中里面字节定义了C/S的协议,而不是在
PPerHandleData中定义的,PPerHandleData只是定义了一些Server中处理的细节问题。嗯,可能理解
有误。。。
呵呵,对IOCP也不熟,它的逻辑结构也不熟,所以你的CPSocks的逻辑确也没看懂,像我理解的
东西上面的程序就是了,只是这样了。再过段时间,我也定下心也搞它。:)
 
studing!!!!!
 
gz
继续讨论

 
大家继续啦!
 
to copy_paste:
我的TIOCPServer这样工作:
1.建立Socket
2.绑定这个Socket
3.开始监听
4.建立一个总完成端口
5.按指定数量建立工作者线程
在这个工作者线程中,查询端口的完成状态。如果有返回,总是返回一个CompletionKey和
一个PPerHandleData结构,根据这个CompletionKey值能够定位是哪个连接对象;根据这个
PPerHandleData结构能够定位是哪个Package。然后通知该包已经收到或发送的字节数。
6.建立一个Accept线程
在这个Accept线程中监听连接,如果有连接,先预订座位,用连接的Socket、总完成端口
句柄、预订有座位号+1(CompletionKey)建立一个新的关联完成端口。建立失败,则取消预
订的座位号,否则用这个Socket和座位号建立一个连接对象,并占据这个座位号。然后通知
连接对象开始接收数据。最后继续下一轮监听。
7.连接对象的工作:连接对象建立时准备好的一个ReceivePackage,没有提交,当收到Post
通知后,投递这个ReceivePackage的WSARecv请求。当调用Send方法时,新建立一个Send
Package,然后加入到连接对象的Package列表中。当ReceivePackage收到工作者线程的返回
通知,则封装这个包,然后提交服务器,并重新建立一个数据块。当SendPackage收到工作
者线程的返回通知,则看看是否已经发送完(即:返回的字节数=数据块的长度),如果是,则
释放这个SendPackage,如果没有发送完,则调整数据包,清除已经发送完的数据,重新投递
这个SendPackage。
 
已知的问题:
1.用accept接受连接是阻塞方式的,能不能也交给工作者线程来处理?
2.每个包都建立和锁定一个内存块,当连接足够多时,会超过系统能够锁定数据块的数量
极限(MSDN上有文档专门说明这一点:
http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/Winsock.asp),
所以我想,能不能在ReceivePackage结构中加入固定长度的缓冲区(8192字节最好),而所有
连接的所有SendPackage共用一个内存块(足够大,例如2M大小)。每次增加一个发送数据块
则在这个块中分配一小块空间,复制进去,并记录其位置和长度,同样采用座位管理制管理
这个大的发送缓冲区,既节省了锁定的内存块数又避免了内存块移动操作。
3.如何以异步方式通知连接对象Socket已断开或者已超时。服务端显然不能用阻塞方式。
 
呵呵,我现在先不管初始化,和其它处理方式,我不太理解的地方是在
GetQueuedCompletionStatus处理方式,我现在来说说Server中的Client在WorkThread中的工作思路

在Server.Accept一个ClientSocket时,建立一个TServerClientSocket实例,并将这个实例
关联到CompletionPort中,那WorkerThread::GetQueuedCompletionStatus取出来的就是
TServerClientSocket这个实例,那么这个实例在WorkerThread线程进行工作
ClientSocket.WorkBlock(Block, Transfered);
而不是PPerHandleData,因为CompletionKey即是DWORD,传一个PPerHandleData指针,还不如
俺去传一个对象,俺就可以懒一点了。

WorkBlock最大的处理就是维护一次Client交互中数据的处理,它的数据应该在WSABUF中,而其它
PPerHandleData应该是Client在Server中的附加信息,应该是在Client中进行相应变化。

Transfered的数量是一次I/O完成的数量,如果为0的话,那么Client是出错了,也就是说,
客户机可能超时,可能断开,那么就应该Free这个Client了。
一个包如果大于WSABUF中的MaxCount,那应该就是维护这个Client.WorkBlock中的处理,
所以我觉得应该对每个Client中应该有OnDataIn, OnDataOut这个函数,进行回调出在主
界面(主线程)中需要发送/读出的数据,那么再进行投递。完成一个交互。

function WorkerThread(AServer: TServerSocket): DWORD; stdcall;
var
Block: PBlock;
Transfered: DWORD;
ClientSocket: TServerClientSocket;
begin
Result := 0;
while True do
begin
ClientSocket := nil;
if not GetQueuedCompletionStatus(AServer.CompletionPort, Transfered,
DWORD(ClientSocket), POverlapped(Block), INFINITE) then
begin //调用失败
if Assigned(ClientSocket) then
FreeAndNil(ClientSocket);
Continue;
end;
if Cardinal(Block) = SHUTDOWN_FLAG then //Server断开Client
break;
if Transfered = 0 then //I/O读写返回0,可能超时,可能断开连接。
begin
FreeAndNil(ClientSocket);
Continue;
end;

case ClientSocket.WorkBlock(Block, Transfered) of
RESPONSE_UNKNOWN:
FreeAndNil(ClientSocket);
RESPONSE_FAIL:
break;
end;
end;
end;
 
1:
Accept接受,一般来说都是写个线程作的,我看了所有的C操作,还有delphi的里面的,
我觉得应该就是一个线程来做此事了。
2:
我不清楚,呵呵,没有考虑到这个问题,如果有这种限制,那么就用一个内存流来做此事如何,
封装一个内存流,然后作为TServerSocket的一个对象来为All Client分配空间。那么就应该
没有这个锁定的限制了吧。我说那些VC的程序好像都是封装了对这些Buffer的操作。
3:
这个问题嘛,好像,可能,大概,,,,俺就,,,,
不如找个Handle,或是ServerSocket建立一个Handle,然后将All ClientSocket.Handle
WSAAsyncSelect到ServerSocket.Handle去,然后Handle得到消息后,将它PostQueuedCompletionStatus
到WorkerThread去。不知以为如何?呵.
 
CSDN有几篇关于完成端口的翻译的文章,还可以,我就是下载其中的例子看的。

TCP网络程序设计-完成端口之应用 (bluecrest原作)
http://www.csdn.net/develop/Read_Article.asp?Id=16630

用完成端口开发大响应规模的Winsock应用程序(5/完... (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15228

用完成端口开发大响应规模的Winsock应用程序(4) (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15226

用完成端口开发大响应规模的Winsock应用程序(3) (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15225

用完成端口开发大响应规模的Winsock应用程序(2) (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15224

摘译:用完成端口开发大响应规模的Winsock应用程... (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15212

摘译:用完成端口开发大响应规模的Winsock应用程... (chiway翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=15211

完成端口(上) (bnwxf翻译) Visual C++ 1443 2002-1-9
http://www.csdn.net/develop/Read_Article.asp?Id=11899

Windows Sockets 2.0:使用完成端口高性能,可扩展... (vcbear翻译)
http://www.csdn.net/develop/Read_Article.asp?Id=10177
 
To barton: 可能我真的是用词不当,不过如果你的服务器应用是在win2000平台下,而且规模非常大,并且要求性能能根据CPU数量的增加而线性增长,完成端口确实是唯一的方法。因为其他不管是异步事件还是重叠IO模型,或是多线程阻塞,都无法同时满足这几点。
完成端口的缺点正如你所说的,只有win2000平台才支持。不过一个项目基于何种平台,是一开始就要决定的,指望一个大型的服务器程序能在win9x下正常工作是不现实的。如果你指望程序写一次就能在windows,linux,unix下到处运行,那我看你还是用java好了
从你上面的发言来看,你的应用只要求200-300个套接字同时连接,所以的确有很多种选择,不一定非得用完成端口。至于多线程阻塞的模型,我前不久刚写过,采用线程池方式的,所有工作线程根据用户指定的数量一开始就创建好,这样就不存在什么频繁创建,释放对象产生内存碎片的问题了。工作起来也很稳定
见: http://www.delphibbs.com/delphibbs/dispq.asp?lid=883220
 
To barton: 问一下,你的应用中工作线程是否需要处理譬如连接远程数据库后台,运行远程数据库存储过程等类似的需要等待但不用占用CPU的工作??如果是的话,用阻塞多线程方式还是比较合适的,

还有,对象确实要比单纯的数组啊结构更消耗资源,虽然他们实质上都是只是一块内存区域的数据,但是对象的构造,析构,涉及到太多的东西了,特别是采了VCL里面的对象的话,你得看看这个对象是从哪里继承下来的
先查清楚他的底细才能用。一般在这次比较底层的场合,尽量避免使用。 你大可吧这些底层细节和你实际的应用分拆开来,底层尊照windows api的风格,用比较原始的方法来实现,应用处理那部分则采用面向对象的方法来实现,这样系统的整体效率和代码的可读性,可以取得一个比较好的平衡
 
大家说了很多,我也没有全看完,不过我想说说我的一些切身体会,是不是用线成阻塞要
具体分析,不能一概而论,关键要看你的业务类型,有的业务中包括时间比较长的操作,
比如对数据库的统计等,没有线程,其他的用户都要等他完事以后才能继续!当然我们通
常使用的大多数操作都可以在短时间内完成。

其次即便是用select或Accept的方式也未必就效率不高,首先你不必让所有的线程都去干
这个工作有几个在干就可以了,其他的可以让他们闲着!如果有的线程进入了实际处理业
务的阶段,再唤醒其他的线程替代它的工作,同时可以监视空闲的线程的数量,如果数量
不够了就建立新的线程,如果太多就杀掉一些。

如果你能人工的将一些费时的工作分离出来,那么我们可以为其建立专门的线程进行处理!
其余的照旧就可以了!另外你是及测试的时候最好能够在后台加入一些处理过程哪怕是
Sleep也可以这样你就能够对你的系统在真正的工作状态下所能够体现的性能有一个了解了!
 
同志们继续继续!
 
关于线程阻塞方式,你可以看看著名的FTP服务器软件--ServerU的工作方式,或许会有所启发
 
我来继续一把!
 
后退
顶部