TClientSocket不断尝试与TServerSocket连接,运行一段时间后的问题! (200分)
分类:Internet/TCPIP 叮叮当当 (2002-05-14 19:14:00)
运行环境:WindowsXP + Delphi 6.0
要求:使用一个TClientSocket尝试与尚未运行的TServerSocket服务端应用程序连接,力求在TServerSocket运行后尽快与之建立连接并通信,因此使用一个TTimer定时为1秒不断尝试连接。
注意:TServerSocket平时是关闭的,很少开启,而TClientSocket要在其开启后尽最大努力尽快与之连接。
另外:TClientSocket和TServerSocket均采用ctNonBlocking(非阻塞方式)。
在TClientSocket的OnError事件中屏蔽连接失败错误:
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode := 0;
end;
定时器中代码如下:
procedure TFrom1.Timer1Timer(Sender: TObject);
begin
if not ClientSocket1.Active then ClientSocket1.Active := True;
end;
结果:TServerSocket服务端应用程序启动时,TClientSocket能正常与TServerSocket连接,但如果TClientSocket运行一段时间后仍未启动TServerSocket服务器(大约10分钟),TClientSocket应用程序就会出错!
错误为:Project BNTester.exe raised exception class ESocketError with message 'Windows socket error: 当该操作在进行中,由于保持活动的操作检测到一个故障,该连接中断。(10055), on API 'connect". Process stopped. Use Step or Run to continue.
继续运行还会引发另一个错误:Project BNTester.exe raised exception class ESocketError with message 'Windows socket error: 提供的文件句柄无效。(10022), on API 'WSACancelASyncRequest"". Process stopped. Use Step or Run to continue.
我初步分析是由于Socket句柄资源用尽导致的,有什么方法可以解决?
张无忌 (2002-05-14 19:47:00)
我用winsock进行多线程扫描的时候也碰到过这个问题,由于每次open的时候都
调用socket建立新的套结字,导致资源耗尽。
我想如果你用api来解决就释放对winsock.dll的引用,下次建立socket之前,在
增加winsock.dll的引用记数。试试看!
叮叮当当 (2002-05-14 19:56:00)
API我用不来啊,再说TClientSocket、TServerSocket控件归根到底不也是对Winsock API的调用嘛,你会用API的话,说说它们哪里有错了?
张无忌 (2002-05-14 20:20:00)
我想TClientSocket和TServerSocket都是利用WSAAsyncSelect()来分派Wsocket I/O消息
如果应用程序调用了WSAAsyncSelect()这个函数, 那么除非调用CloseSocket()关闭套
解字,否则消息通知永远有效,如果你没有关闭连接的套解字那么很有可能是这个原因
导致出错,还有winsock.dll每个引用的引用程序的资源是有限的,如果调用winsock.dll
的应用程序建立的Tsocket数量太多,资源就可能耗光。好象这个数目是可以从WSADATA的
返回值里得到详细的数目。
WSADATA中的iMaxSockets就是最大可打开socket 的数目。
dmg (2002-05-14 20:43:00)
我也碰到类似的问题也没有找到答案
叮叮当当 (2002-05-14 20:55:00)
To: 张无忌
可我自始至终只用了一个TClientSocket呀,它应该也只用一个Socket吧?
张无忌 (2002-05-14 20:57:00)
错了,你如果看了他的员代码,每次open前他都建立了一个套解字!!!!
张无忌 (2002-05-14 20:58:00)
这就是我找到的代码
procedure TCustomWinSocket.Open(const Name, Address, Service: string; Port: Word; Block: Boolean);
begin
if FConnected then raise ESocketError.CreateRes(@sSocketAlreadyOpen);
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP); //这就是关键部分!
if FSocket = INVALID_SOCKET then raise ESocketError.CreateRes(@sCannotCreateSocket);
try
Event(Self, seLookUp);
if Block then
begin
FAddr := InitSocket(Name, Address, Service, Port, True);
DoOpen;
end else
AsyncInitSocket(Name, Address, Service, Port, 0, True);
except
Disconnect(FSocket);
raise;
end;
end;
叮叮当当 (2002-05-14 21:13:00)
我确实在ScktComp.pas中找到了你说的这段代码,但是我在第一行“if FConnected then raise ESocketError.CreateRes(@sSocketAlreadyOpen);”设置了断点,并运行我的程序,发现根本不会运行到这段代码嘛。
猛禽 (2002-05-14 21:18:00)
最简单的解决办法是将ClientSocket改用Blocking方式试连,
直至连上后再断开,改回NonBlocking方式
叮叮当当 (2002-05-14 21:24:00)
To: 猛禽
阻塞方式连接和非阻塞方式连接的唯一区别是不是阻塞方式不会触发OnRead事件,要自己在代码里判断是否有接收数据到达?
选择阻塞方式或非阻塞方式时,服务器端和客户端是否必须一致?
猛禽 (2002-05-14 21:49:00)
非阻塞方式下全部传输操作都是异步的,不仅仅是事件那么简单,在非阻塞方式下:
Client Server
Connect---------Accept
OnConnect OnClientConnect
OnRead/OnWrite--OnClientWrite/OnClientRead
(注意,要在相应该事件中用SendBuf/ReceiveBuf等读写数据)
在阻塞方式下:
Client----------Server
Connect---------Accept
TWinSocketStream.Read/Write
(只能通过TWinSocketStream进行,不能用SendBuf/ReceiveBuf等)
两端最好一致,否则数据同步会有问题,所以:
直至连上后再断开,改回NonBlocking方式
叮叮当当 (2002-05-14 22:24:00)
To: 猛禽
>直至连上后再断开,改回NonBlocking方式
这么做有点牵强,如果一连接上,Server就要主动向Client发送数据呢?
你的意思是说如果两端都用Blocking方式就没有问题了?
张无忌 (2002-05-15 8:01:00)
如果你用Blocking那么消息将阻塞,如果连不上去,你的程序就像死了一样。
而且你连上去以后把它变成非阻塞能不能用都是问题,要涉及到ioctlsocket
等一些winsock函数的调用问题,我昨天仔细分析了一下,ClientSocket中的
哪个socket就是TCustomWinSocket,
张无忌 (2002-05-15 8:12:00)
最好的建议是你用一个线程来连接服务器,用winsock API来写,如果不会写,我可以提供
帮助!
张无忌 (2002-05-15 8:22:00)
最好不要用TClientsock的非阻塞式,他的实现是用readfile和writefile配合重叠I/O
效率不高,相当耗资源,就其原因是他用的是winsock1.1版本,在winsock2版本中,
可以用WSARecv()中的lpOverlapped这个参数来进行重叠I/O操作,效率更高!
张无忌 (2002-05-15 8:26:00)
我的qq :775033联系
Sachow (2002-05-15 9:59:00)
我在Win2K下运行了20多分钟都不出问题,但是SocketHandle的值却一直在增加,可能
是Win2K支持的最大句柄数远大于Win98或者其它原因,但这样的话最终还是要出问题的。
建议在ClientSocket的OnError事件中加入这行代码试试看:
closesocket(Socket.SocketHandle);
张无忌 (2002-05-15 12:37:00)
每次连接TClientSocket都会建一个新的SOCKET,
叮叮当当 (2002-05-15 18:44:00)
To: 张无忌
那你可知TClientSocket何时释放它创建的Socket?我采用动态创建TClientSocket的方法,每次重试连接服务器都Free后再创建也没用!
张无忌 (2002-05-15 18:50:00)
我分析了好半天,还是哪个TClientSocket里的FSocket就是TCustomWinSocket
我看了他的代码,很多地方没有看明白。
不过我对你的这个问题有一个很好的办法,
我在上一个帖子也是这么回的
那就是用一个单独的线程用阻塞式去连接这个服务器,如果连接上就postmessage
一个消息给主窗口。各位有什么意见?
叮叮当当 (2002-05-15 19:07:00)
用阻塞式重连就不会有SocketHandle耗尽的问题么?
张无忌 (2002-05-15 19:09:00)
我在做程序中,代码写了很多,一会就可以调试!
张无忌 (2002-05-15 19:34:00)
代码写好了,
mywyn (2002-05-15 20:18:00)
to 叮叮当当:
1.你没有运行到断点,是因为你没有把vcl源码加入搜索路径。
2.阻塞式重连是不会有SocketHandle耗尽的问题,看一下源码
if Block then
begin
FAddr := InitSocket(Name, Address, Service, Port, True);
DoOpen;
end else
AsyncInitSocket(Name, Address, Service, Port, 0, True);
except
Disconnect(FSocket);
如果是阻塞方式出错,会被捕获并执行Disconnect(FSocket)。当然,如果你用
非阻塞方式,在OnError中执行Socket.Close,效果也是一样。
张无忌 (2002-05-15 20:31:00)
mywyn你的QQ是?
mywyn (2002-05-15 22:27:00)
to 张无忌:
71553950,不过我老是忘记开QQ,已被朋友骂过N回了。^_^
Sachow (2002-05-16 9:28:00)
很多人对socket API编程讳莫如深,其实没有有想像中的那么难,只用控件,对底层工作
难以深入理解,遇到控件难以解决的地方就无法。
一个socket客户端的工作步骤为:建立socket -> 连接服务器 -> 读写,先后需要调用的
函数为socket(), connect(), read()/write()或recv(), send()等。
每次运行socket()函数时,如果成功,就会打开一个句柄(在UNIX下叫“文件描述符”File
Descriptor),通常的编程是如果connect()失败,就应该关闭该句柄,而TCustomWinSocket
却没有这样做,所以打开的句柄数不断增加,操作系统充许一个进程打开的句柄数是有限的,
当达到这个值时,程序就要报错。
昨天我以每秒一次的频率用计时器定时连接服务器端,在OnError时关闭句柄。初始的
SocketHandle值为124,在3个值之间变动,运行一个小时后,SocketHandle增加到900,
其间出现了几次10038错误(不是Socket连接),每次出此错误的时候SocketHandle就要
增加,不知道它搞了些什么——而用API的话,一切都在自己的掌握中,很清楚。
现在我正在UNIX运行着程序试着,等两三个小时后再看结果。
叮叮当当 (2002-05-16 21:17:00)
谢谢大家!我开始尝试用TTcpClient控件代替TClientSocket连接TServerSocket,但是发现有时能连接成功,有时不行,发送数据也一样,也许是阻塞非阻塞的问题。另一个我想到用来替代TClientSocket的控件(ICS里的TWSocket)则也有古怪问题,它是通过State属性的改变来通知用户当前的通信状态的(State改变时会触发OnChangeState事件),State的值为以下枚举:wsInvalidState、wsOpened、wsBound、wsConnecting、wsConnected、wsAccepting、wsListening、wsClosed。可我发现这个控件不管是否连接成功都会出现wsConnected状态,只不过如果连接成功,State就不再变化,而如果是连接失败,则再出现wsClosed状态而已。倒~真是搞不懂ICS的作者是怎么想的!
张无忌 (2002-05-16 21:19:00)
:)
张无忌 (2002-05-16 21:22:00)
我想这几天好好研究研究ICS控件
叮叮当当 (2002-05-16 21:22:00)
还想到过一个控件——Delphi自带的TPowerSock,可它没有接收、发送事件!*_*
张无忌 (2002-05-16 21:54:00)
TPowerSock看帮助说不能直接用,
叮叮当当 (2002-05-16 22:56:00)
To: 张无忌
>TPowerSock看帮助说不能直接用,
可我已经在程序中用它来测试连接速度了(因为只有它有TimeOut属性),使用很成功。
张无忌 (2002-05-16 23:28:00)
哦,我没有使用过,:)FastNet组件毛病多,很少用。
mywyn (2002-05-17 18:21:00)
to 叮叮当当:
我仔细的看了ics源码,没有问题,你的情况可能是连上又断开了。假如你
只调用了Connect的话。另外还有一种情况要注意:你的Server端已在侦听,当有客户端
请求连接时由于某种原因没有调用Accept,此时客户端仍然显示连接成功。这跟控件
无关,用API函数也是一样。
叮叮当当 (2002-05-17 19:58:00)
To: mywyn
谢谢!但是我用ICS的TWSocket连接时,并没有开服务器程序呀,根本不可能连上才对。
你QQ不常开,那么你有MSN么?我的MSN:pschen@21cn.com,很想和你交个朋友!
mywyn (2002-05-17 20:48:00)
to 叮叮当当:
不好意思,我没有MSN。你的情况我从来没遇到过。最好把你连接的代码贴一下。
另外,我的ics中的Wsocket.pas的版本为4.34。
叮叮当当 (2002-05-17 22:42:00)
To: mywyn
我的ICS中WSocket.pas的版本为4.40(这个是ICS最新的Beta版,我本以为是Beta版存在的问题,后来我又下载了最新的一个正式版,也是一样)。
证明这个问题的步骤如下:
1)新建一个Project,在窗体上放置一个TWSocket、一个TListBox、一个TButton;
2)为Button1添加OnClick事件处理代码如下:
procedure TForm1.Button1Click(Sender: TObject);
begin
SOCK.Addr := 'localhost'; // 每次调用 Connect 方法之前
SOCK.Port := '6112'; // 都必须重新设置 Addr、Port,麻烦。
SOCK.Connect;
end;
3)为WSocket1添加OnChangeState事件处理代码如下:
procedure TForm1.WSocket1ChangeState(Sender: TObject; OldState,
NewState: TSocketState);
const
_StateStringArray: array[TSocketState] of string = ('wsInvalidState',
'wsOpened', 'wsBound', 'wsConnecting', 'wsConnected', 'wsAccepting',
'wsListening', 'wsClosed');
begin
ListBox1.Items.Add(_StateStringArray[OldState]+' -> '+_StateStringArray[NewState]);
end;
4)按F9运行,点击按钮,你将会看到明明没有连接上却有wsConnected状态出现。
mywyn (2002-05-18 12:51:00)
问题找到了:
Check := msg.lParamLo and FD_CONNECT;
if Check <> 0 then begin
if FState <> wsConnected then begin
ChangeState(wsConnected);
TriggerSessionConnected(msg.lParamHi);
if (msg.lParamHi <> 0) and (FState <> wsClosed) then
Close;
end;
end;
它把错误放到了最后判断,也就是这一句:
if (msg.lParamHi <> 0) and (FState <> wsClosed) then
Close;
有两种方法解决:1.是修改代码。
2.是调用Connect后,等待SessionConnected事件触发,然后
判断Error。
我因为一开始就是用的第二种方法,所以没有碰到你的这种情况。
叮叮当当 (2002-05-20 2:00:00)
谢谢各位参与讨论!我正在总结讨论的帖子和相关资料,看有哪些解决方法可行。
帖子的积分稍候就会配发。
张无忌 (2002-05-20 12:41:00)
我还有个办法,你在连接之前用一个标志,比如
fstatue:=dtConnect,
在ERROR处理事件里如果连接失败,先判断这个标志是不是为dtConnect,如果是的,
就是关闭这个TClientSocket。
叮叮当当 (2002-05-22 23:49:00)
原来很简单,在OnError事件里将Socket关闭就没事了(释放了SocketHandle资源)。
procedure TForm1.ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
Socket.Close;
ErrorCode := 0;
end;
叮叮当当 (2002-05-23 0:02:00)
其实前面各位都有提到在OnError里关闭Socket,都怪我没有好好看前面各位的发言,不好意思。[
]
mywyn-60,Sachow-50,猛禽-30,张无忌-60,的回答最终被接受。