D
drizzledu
Unregistered / Unconfirmed
GUEST, unregistred user!
我在网上找到一个相关帖子,但是我没水平按照帖子中的方法修改代码,帖子内容如下,谁来帮我改改idhttp 超时解决方法(转)2007-08-17 10:36最近在做网络通讯方面的东东,自然而然就用到了Indy系列组件,Indy系统组件是基于阻塞模式下的,建立在Socket2API基础上的一个组件开发包,它封装了百余种网络协议,具有极其强大的功能,并为开发人员提供了方便易用的操作接口来实现形形色色的功能。 一开始,真的很庆幸,我写了几十行左右的代码就完成了一段功能还算强大的示例程序,但是,随着程序功能一步步的实现,功能越加越多后,我反而越来越迷惑了,我的多线程程序经常会无故死锁,抑或是运行过于缓慢,难道是我的线程同步做得不好?在检查了自己的相关代码后,发现并没有问题啊!于是我更加困惑了!! 难道是Indy的问题?我先粗略的看了下,发现Indy的TIdTCPConnection的ReadTimeout属性设置的超时,在一些方法中根本就是不起效果的,要知道TIdTCPConnection可是TIdTCPClient的基类啊!后来,经过分析,我倒发现一个非常明显的问题。因为我有用到Socks代理,所以我看看相关的代码吧。Indy将所有Socks代理方面的操作抽象成TIdSocksInfo组件,定义在IdSocks单元。验证Socks代理的过程是MakeSocksConnection,它内部通过判断Socks代理的版本来调用MakeSocks4Connection或MakeSocks5Connection。 我们以MakeSocks5Connection为例,来分析以下代码吧。我们截取144-151行的代码如下, 为了方便,我们给下面几行进行了重新编号。 (1) FIOHandler.Send(tempBuffer, len);
(2) try (3) FIOHandler.Recv(tempBuffer, 2);
(4) except (5) On E: Exceptiondo
begin
(6) raise EIdSocksServerRespondError.Create(RSSocksServerRespondError);
(7) end;
(8) end;
先简单地介绍下,FIOHanlder主要是负责Socket IO操作的具体实现,它主要包含Open,Close,Send,Recv等方法,上面看到的Send调用的是Socket API中的send, Recv调用的是Socket API中的recv。上面八行作为验证Socks代理的核心代码,不止一次在MakeSocks5Connection中出现。它的功能是先发送相关验证信息,然后再接收验证结果,如果出错的话,触发一个异常。 貌似很简单,可是错误就在这里面。我们看看Socket API recv的介绍吧,下文摘自msdn(只摘选了对本文有用的几句话): If no incoming data is available at the socket, the recv call waits for data to arrive unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set to WSAEWOULDBLOCK. The select, WSAAsyncSelect, or WSAEventSelect calls can be used to determine when more data arrives. 就是说,如果没有数据待读,那么除非是非阻塞模式,不然的话套接口将一直等待数据的到来。Indy采用的是阻塞模式, 那就是说,如果FIOHandler.Recv()一直收不到数据,它将一直等下去,不仅仅是这个组件,连同调用它的线程,都会这样无休止地等下去。如果调用的是主线程,后果就更惨了。 这种情况发生的可能性极大,一个编码存在Bug的代理服务器软件或者是由于连接过多且负载能力较差的代理服务器以及网络问题都会造成这种情况的发生,有的时候也许并没有死,一两分钟过后甚至几分钟过后,也许真的还能收到数据,但是在这里,TIdTCPConnection.ReadTimeout属性已经不能进行控制了。 另外,4-8行的代码,其实根本就不会执行,不会执行完全取决于Windows Socket异常的设计,如果某个Socket IO操作发生异常, 那么,那个操作只会返回SOCKET_ERROR,然后程序员再调用WSAGetLastError取得错误编号,由自己决定是否触发或产生异常。 弄了半天,原来是该死的recv。那我们应该怎么解决呢?看着上面的recv的介绍吧,可以先用select, WSAAsyncSelect, or WSAEventSelect,并在它们的参数设置一个超时,如果检测到有数据,再调用recv,否则就是read超时了。具体的写法应该不难吧,呵呵。最后,我们重新编译一下indy就可以用了,{$Delphi}/Source/Indy/下,有两个.dpkw文件,把它们改成.dpk文件,然后编译吧。 我下了Indy10,这个错误依然存在,就是说,在Delphi或.Net中使用Indy的朋友要留意了噢,嘿嘿。 当然,Indy这套组件这么宠大,肯定还会有其它一些问题的存在,到时候大家再一起发现哈。如果有哪位大虾觉得我有错误的地方,也敬请指出,欢迎讨论。
(2) try (3) FIOHandler.Recv(tempBuffer, 2);
(4) except (5) On E: Exceptiondo
begin
(6) raise EIdSocksServerRespondError.Create(RSSocksServerRespondError);
(7) end;
(8) end;
先简单地介绍下,FIOHanlder主要是负责Socket IO操作的具体实现,它主要包含Open,Close,Send,Recv等方法,上面看到的Send调用的是Socket API中的send, Recv调用的是Socket API中的recv。上面八行作为验证Socks代理的核心代码,不止一次在MakeSocks5Connection中出现。它的功能是先发送相关验证信息,然后再接收验证结果,如果出错的话,触发一个异常。 貌似很简单,可是错误就在这里面。我们看看Socket API recv的介绍吧,下文摘自msdn(只摘选了对本文有用的几句话): If no incoming data is available at the socket, the recv call waits for data to arrive unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set to WSAEWOULDBLOCK. The select, WSAAsyncSelect, or WSAEventSelect calls can be used to determine when more data arrives. 就是说,如果没有数据待读,那么除非是非阻塞模式,不然的话套接口将一直等待数据的到来。Indy采用的是阻塞模式, 那就是说,如果FIOHandler.Recv()一直收不到数据,它将一直等下去,不仅仅是这个组件,连同调用它的线程,都会这样无休止地等下去。如果调用的是主线程,后果就更惨了。 这种情况发生的可能性极大,一个编码存在Bug的代理服务器软件或者是由于连接过多且负载能力较差的代理服务器以及网络问题都会造成这种情况的发生,有的时候也许并没有死,一两分钟过后甚至几分钟过后,也许真的还能收到数据,但是在这里,TIdTCPConnection.ReadTimeout属性已经不能进行控制了。 另外,4-8行的代码,其实根本就不会执行,不会执行完全取决于Windows Socket异常的设计,如果某个Socket IO操作发生异常, 那么,那个操作只会返回SOCKET_ERROR,然后程序员再调用WSAGetLastError取得错误编号,由自己决定是否触发或产生异常。 弄了半天,原来是该死的recv。那我们应该怎么解决呢?看着上面的recv的介绍吧,可以先用select, WSAAsyncSelect, or WSAEventSelect,并在它们的参数设置一个超时,如果检测到有数据,再调用recv,否则就是read超时了。具体的写法应该不难吧,呵呵。最后,我们重新编译一下indy就可以用了,{$Delphi}/Source/Indy/下,有两个.dpkw文件,把它们改成.dpk文件,然后编译吧。 我下了Indy10,这个错误依然存在,就是说,在Delphi或.Net中使用Indy的朋友要留意了噢,嘿嘿。 当然,Indy这套组件这么宠大,肯定还会有其它一些问题的存在,到时候大家再一起发现哈。如果有哪位大虾觉得我有错误的地方,也敬请指出,欢迎讨论。