Internet构件Indy与blocking套接字调用模式
Indy采用blocking模式
Indy采用blocking套接字调用模式。blocking调用类似于读写文件。在进行读写
时,调用的方法在操作完成后才会返回。不同之处在于,网络通讯中的blocking
套接字方法调用可能会花更长的时间,因为远端数据并非时时准备好等待存取(
最快不会超过网络或modem处理数据的速度)。
例如,如果要连接远端,简单地调用connect方法,然后等待返回。如果不连接失
败,则引发一个异常。
Blocking无罪
人们一直不分青红皂白地攻击blocking套接字。与流行的说法相反,blocking套
接字并非一种罪恶。
当Winsock被(从Unix)“移植”到Windows平台,很快有问题发生。在Unix上,
问题很容易解决(类似多线程,不过在Unix上使用独立process代替线程)。Unix
客户端与服务器能够处理process,这些process使用blocking套接字。
Windows3.x不能处理、更不支持多线程。使用blocking接口将会“锁死”用户界
面,使程序不响应。于是,异步扩展特性被增加到WinSock,让Windows3.x可以使
用WinSock,而不会锁死。不过这需要不同的编程方法。因为Windows3.x存在的缺
点而导致blocking套接字不能很好地运作,所以Microsoft和其它一些厂商不遗余
力地攻击blocking套接字。
Win32支持多线程。但在此时,大家的想法已经改变了(比如,开发者坚信
blocking是不好的),而且这种情况无法逆转。所以,对blocking套接字的责难
一直继续。
实际上,blocking套接字是Unix实现套接字的唯一方法。blocking套接字也拥有
其它的优点,而且更有利于实现线程化、安全性更高。目前在Unix上也增加了一
些non-blocking套接字特性。但是,它们以与在Windows中截然不同的方式工作。
没有使用标准,且应用范围不广泛。在Unix上,blocking套接字仍旧在几乎每个
socket应用中存在,且将继续如此。
blocking的好处
1、易于编程——blocking非常易于编程。所有代码能以有序的形式存在于同一位
置。
2、易于移植——由于Unix采用blocking套接字,用blocking套接字的程序更易于
移植到Unix平台。Indy利用该特点,实现了多平台单代码的解决方案。
3、在线程中工作顺利——blocking套接字是串行的,很容易在线程中使用。
blocking的缺点
1、客户端用户界面“死锁”——blocking套接字调用在完成工作之后才返
回。如果在程序主线程中进行此类调用,应用程序将不能正常处理用户界面
消息。这就使用户界面“死锁”,因为在blocking套接字调用把控制权返回
给应用程序消息循环之前,update、repaint和其它消息不能被正常处理。
TIdAntiFreeze
Indy使用一个特殊的组件来解决用户界面死锁问题。把TIdAntiFreeze加入程
序,就可以随意在程序中进行标准的blocking Indy调用,而用户界面也不会死锁。
TIdAntiFreeze工作原理:在内部定时停止堆栈调用、然后调用Application.ProcessMessages。
在外部,对Indy的调用继续阻塞,和没使用TIdAntiFreeze一样正常。
线程化
在blocking套接字中几乎总是使用线程。Non-blocking套接字也可以被线程
化,不过需要额外的处理。而且,相对于blocking套接字而言毫无优势。在
编写blocking套接字服务器时,线程化非常重要。在编写高级的blocking客
户端时也是如此。
线程化的优点
1、优先级——可以调整独立线程优先权。可以为每个服务器任务或是连接配
给适当的CPU时间。
2、封闭性——可保持所有连接,连接之间不会互相干扰。
3、安全性——每个线程可拥有不同的安全属性。
4、多处理器——线程化能自动利用多处理器优势。
5、非串行化——线程化提供真正的并行可能性。假若不线程化,则所有请求
都需要一个单独线程来处理。于是需要把任务切割为多个小块以提高运行速
度。如果某个任务锁住或者执行较慢,其它线程必须等待它结束。任务块结束
后,后一个任务块才能被处理,如此类推。而在线程化的条件下,每个任务均
可作为整个任务来执行,操作系统自动分配CPU时间。
线程Pooling
线程的创建与破坏极耗资源。对于短时连接的服务更是如此。此类服务创建线
程、在很短时间内使用、然后摧毁它。这样,线程会频繁地创建和毁坏。拿
time server或web server来做例子,客户端发送简单的请求,服务器返回简单
回复。使用浏览器浏览一个网页时,有可能需要与服务器进行上百次的连接和
断开操作。线程pooling可以减轻痛苦。它并不在需要时创建和毁坏线程,而是
从已经创建但未使用的列表(pool)中“借出”线程。当线程不再需要,将被抛
回缓冲池,而不会摧毁。缓冲池中的线程被标记为未活动,所以并不消耗CPU循环。
缓冲池的大小可以调整,以适合系统需求。
Indy支持线程pooling。可以用TIdThreadMgrPool组件利用线程pooling。
数以百计的线程
对于一个忙碌的服务器,可能需要数以百计、千计的线程。人们普遍误解如此
多的线程将立刻使系统当掉。其实不然。
对于多数服务器而言,线程存在期的多数时间用于等待数据。在等待blocking
调用时,线程是不活动的。所以,在一个拥有500个线程的服务器中,可能只有
50个同时活动的线程。在我的系统上,330个线程只耗费1%的CPU时间。
线程与全局Section
当线程需要存取数据时,都必须获取对数据的控制权以保护数据完整性。对于
初学线程编程的人士,这恐怕有些头疼。不过多数服务器并不需要全局数据。
每个线程执行隔离的任务。
Indy之道
Indy与你所熟悉的其它Winsock组件不同。如果你曾经使用过其它组件,最好
是彻底忘记它们是如何工作的。几乎所有其它组件都是non-blocking,以异
步方式调用。你需要对事件进行回应,设置状态机,且常常要进行等待循环。
例如,使用其它组件,当进行连接时,必须等待connect事件发生,或者循环
至被告知已连接。而在Indy中,你简单调用connect,然后等待它返回。如果
连接失败,则引发一个异常。使用Indy非常相似于操作文件。代码可以放置于
一个地方,而无需切割到各种事件代码中。
Indy有何不同
简述
1、使用blocking调用
2、不依赖事件——Indy拥有事件(处于提供信息的考虑),但并非必须。
3、被设计为支持线程——无需进行额外的线程化工作。
细节
Indy不光是使用blocking调用,而且也表现为blocking方式。典型的Indy调用:
with IndyClient do begin
Connect; Try
// 你的代码
finally Disconnect; end;
end;
其它组件大概会这样调用:
procedure TFormMain.TestOnClick(Sender: TComponent);
begin
with SocketComponent do begin
Connect; try
while not Connected do begin
if IsError then begin
Abort;
end;
Application.ProcessMessages;
OutData := 'Data To send';
while length(OutData) > 0 do begin
Application.ProcessMessages;
end;
finally Disconnect; end;
end;
end;
procedure TFormMain.OnConnectError;
begin
IsError := True;
end;
procedure TFormMain.OnRead;
var
i: Integer;
begin
i := SocketComponent.Send(OutData);
OutData := Copy(OutData, i + 1, MaxInt);
end;
Indy天生支持线程。在Windows中用Indy编写服务器或客户端与在Unix平台
上编写服务器或客户端相似,甚至更加简单。
典型的Unix服务器有一个或多个“监听”进程,查找客户端请求。对于每个
客户端,都要创建一个新进程。
Indy以简单的方式工作。Windows能较好地处理线程。Indy服务器为每个客户
端锁定一个线程。
Indy服务器建立一个与主线程分离的监听线程。监听线程等待客户端请求。
对于每个连接的客户端,产生一个新线程为其服务。然后,在线程上下文中
引发适当的事件。