CnPing.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. {******************************************************************************}
  2. { CnPack For Delphi/C++Builder }
  3. { 中国人自己的开放源码第三方开发包 }
  4. { (C)Copyright 2001-2007 CnPack 开发组 }
  5. { ------------------------------------ }
  6. { }
  7. { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 }
  8. { 改和重新发布这一程序。 }
  9. { }
  10. { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 }
  11. { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 }
  12. { }
  13. { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 }
  14. { 还没有,可访问我们的网站: }
  15. { }
  16. { 网站地址:http://www.cnpack.org }
  17. { 电子邮件:master@cnpack.org }
  18. { }
  19. {******************************************************************************}
  20. unit CnPing;
  21. {* |<PRE>
  22. ================================================================================
  23. * 软件名称:网络通讯组件包
  24. * 单元名称:Ping 通讯单元
  25. * 单元作者:胡昌洪Sesame (sesamehch@163.com)
  26. * 备 注:定义了 TCnPing
  27. * 开发平台:PWin2000Pro + Delphi 5.01
  28. * 兼容测试:PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6
  29. * 本 地 化:该单元中的字符串均符合本地化处理方式
  30. * 单元标识:$Id$
  31. * 修改记录:2008.04.04 V1.0
  32. * 创建单元
  33. ================================================================================
  34. |</PRE>}
  35. interface
  36. {$I CnPack.inc}
  37. uses
  38. Windows, SysUtils, Classes, Controls, Winsock, StdCtrls, //Sockets,
  39. CnClasses, CnConsts, CnNetConsts;
  40. type
  41. PCnIPOptionInformation = ^TCnIPOptionInformation;
  42. TCnIPOptionInformation = packed record
  43. TTL: Byte; // Time To Live (used for traceroute)
  44. TOS: Byte; // Type Of Service (usually 0)
  45. Flags: Byte; // IP header flags (usually 0)
  46. OptionsSize: Byte; // Size of options data (usually 0, max 40)
  47. OptionsData: PAnsiChar; // Options data buffer
  48. end;
  49. PCnIcmpEchoReply = ^TCnIcmpEchoReply;
  50. TCnIcmpEchoReply = packed record
  51. Address: DWORD; // replying address
  52. Status: DWORD; // IP status value (see below)
  53. RTT: DWORD; // Round Trip Time in milliseconds
  54. DataSize: Word; // reply data size
  55. Reserved: Word;
  56. Data: Pointer; // pointer to reply data buffer
  57. Options: TCnIPOptionInformation; // reply options
  58. end;
  59. TIpInfo = record
  60. Address: Int64;
  61. IP: string;
  62. Host: string;
  63. end;
  64. TOnReceive = procedure(Sender: TComponent; IPAddr, HostName: string;
  65. TTL, TOS: Byte) of object;
  66. TOnError = procedure(Sender: TComponent; IPAddr, HostName: string;
  67. TTL, TOS: Byte; ErrorMsg: string) of object;
  68. //==============================================================================
  69. // Ping 通讯类
  70. //==============================================================================
  71. { TCnPing }
  72. TCnPing = class(TCnComponent)
  73. {* 通过调用ICMP.DLL库中的函数来实现Ping功能。}
  74. private
  75. hICMP: THANDLE;
  76. FRemoteHost: string;
  77. FRemoteIP: string;
  78. FIPAddress: Int64;
  79. FTTL: Byte;
  80. FTimeOut: DWord;
  81. FPingCount: Integer;
  82. FDelay: Integer;
  83. FOnError: TOnError;
  84. FOnReceived: TOnReceive;
  85. FDataString: string;
  86. FWSAData: TWSAData;
  87. FIP: TIpInfo;
  88. procedure SetPingCount(const Value: Integer);
  89. procedure SetRemoteHost(const Value: string);
  90. procedure SetTimeOut(const Value: DWord);
  91. procedure SetTTL(const Value: Byte);
  92. procedure SetDataString(const Value: string);
  93. procedure SetRemoteIP(const Value: string);
  94. function PingIP_Host(const aIP: TIpInfo; const Data; Count: Cardinal;
  95. var aReply: string): Integer;
  96. {* 以设定的数据Data(无类型缓冲区)Ping一次并返回结果。Count表示数据长度 }
  97. function GetReplyString(aResult: Integer; aIP: TIpInfo;
  98. pIPE: PCnIcmpEchoReply): string;
  99. {* 返回结果字符串。}
  100. function GetDataString: string;
  101. function GetIPByName(const aName: string; var aIP: string): Boolean;
  102. {* 通过机器名称获取IP地址}
  103. function SetIP(aIPAddr, aHost: string; var aIP: TIpInfo): Boolean;
  104. {* 通过机器名称或IP地址填充完整IP信息}
  105. protected
  106. procedure GetComponentInfo(var AName, Author, Email, Comment: string);
  107. override;
  108. public
  109. constructor Create(AOwner: TComponent); override;
  110. destructor Destroy; override;
  111. function Ping(var aReply: string): Boolean;
  112. {* 进行循环Ping,循环次数在PingCount属性中指定。}
  113. function PingOnce(var aReply: string): Boolean; overload;
  114. {* 以设定的数据Ping一次并返回结果。}
  115. function PingOnce(const aIP: string; var aReply: string): Boolean; overload;
  116. {* 向指定IP进行一次Ping并返回结果。}
  117. function PingFromBuffer(var Buffer; Count: Longint; var aReply: string):
  118. Boolean;
  119. {* 以参数Buffer的数据Ping一次并读取返回结果。}
  120. published
  121. property RemoteIP: string read FRemoteIP write SetRemoteIP;
  122. {* 要Ping的目标主机地址,只支持ip}
  123. property RemoteHost: string read FRemoteHost write SetRemoteHost;
  124. {* 要ping的目标主机名,有主机名存在时会覆盖 RemoteIP 的内容}
  125. property PingCount: Integer read FPingCount write SetPingCount default 4;
  126. {* 调用Ping方法时进行多少次数据发送,默认是4次。}
  127. property Delay: Integer read FDelay write FDelay default 0;
  128. {* 相邻两次 Ping 间的时间间隔,单位毫秒,默认 0 也就是不延时}
  129. property TTL: Byte read FTTL write SetTTL;
  130. {* 设置的TTL值,Time to Live}
  131. property TimeOut: DWord read FTimeOut write SetTimeOut;
  132. {* 设置的超时值}
  133. property DataString: string read GetDataString write SetDataString;
  134. {* 欲发送的数据,以字符串形式表示,默认为"CnPack Ping"。}
  135. property OnReceived: TOnReceive read FOnReceived write FOnReceived;
  136. {* Ping一次成功时返回数据所触发的事件}
  137. property OnError: TOnError read FOnError write FOnError;
  138. {* Ping出错时返回的内容和信息。包括目的未知、不可达、超时等。}
  139. end;
  140. implementation
  141. {$R-}
  142. const
  143. SCnPingData = 'CnPack Ping.';
  144. ICMPDLL = 'icmp.dll';
  145. type
  146. //==============================================================================
  147. // 辅助过程 从icmp.dll导入的函数
  148. //==============================================================================
  149. TIcmpCreateFile = function (): THandle; stdcall;
  150. TIcmpCloseHandle = function (IcmpHandle: THandle): Boolean; stdcall;
  151. TIcmpSendEcho = function (IcmpHandle: THandle;
  152. DestAddress: DWORD;
  153. RequestData: Pointer;
  154. RequestSize: Word;
  155. RequestOptions: PCnIPOptionInformation;
  156. ReplyBuffer: Pointer;
  157. ReplySize: DWord;
  158. TimeOut: DWord): DWord; stdcall;
  159. var
  160. IcmpCreateFile: TIcmpCreateFile = nil;
  161. IcmpCloseHandle: TIcmpCloseHandle = nil;
  162. IcmpSendEcho: TIcmpSendEcho = nil;
  163. IcmpDllHandle: THandle = 0;
  164. procedure InitIcmpFunctions;
  165. begin
  166. IcmpDllHandle := LoadLibrary(ICMPDLL);
  167. if IcmpDllHandle <> 0 then
  168. begin
  169. @IcmpCreateFile := GetProcAddress(IcmpDllHandle, 'IcmpCreateFile');
  170. @IcmpCloseHandle := GetProcAddress(IcmpDllHandle, 'IcmpCloseHandle');
  171. @IcmpSendEcho := GetProcAddress(IcmpDllHandle, 'IcmpSendEcho');
  172. end;
  173. end;
  174. procedure FreeIcmpFunctions;
  175. begin
  176. if IcmpDllHandle <> 0 then
  177. FreeLibrary(IcmpDllHandle);
  178. end;
  179. //==============================================================================
  180. // Ping 通讯类
  181. //==============================================================================
  182. { TCnPing }
  183. constructor TCnPing.Create(AOwner: TComponent);
  184. begin
  185. inherited Create(AOwner);
  186. FRemoteIP := '127.0.0.1';
  187. FTTL := 64;
  188. FPingCount := 4;
  189. FDelay := 0;
  190. FTimeOut := 10;
  191. FDataString := SCnPingData;
  192. hICMP := IcmpCreateFile(); // 取得DLL句柄
  193. if hICMP = INVALID_HANDLE_VALUE then
  194. raise Exception.Create(SICMPRunError);
  195. end;
  196. destructor TCnPing.Destroy;
  197. begin
  198. if hICMP <> INVALID_HANDLE_VALUE then
  199. IcmpCloseHandle(hICMP);
  200. inherited Destroy;
  201. end;
  202. procedure TCnPing.GetComponentInfo(var AName, Author, Email,
  203. Comment: string);
  204. begin
  205. AName := SCnPingName;
  206. Author := SCnPack_Sesame;
  207. Email := SCnPack_SesameEmail;
  208. Comment := SCnPingComment;
  209. end;
  210. procedure TCnPing.SetPingCount(const Value: Integer);
  211. begin
  212. if Value > 0 then
  213. FPingCount := Value;
  214. end;
  215. procedure TCnPing.SetRemoteIP(const Value: string);
  216. begin
  217. if FRemoteIP <> Value then
  218. begin
  219. FRemoteIP := Value;
  220. if SetIP(FRemoteIP, '', FIP) then
  221. begin
  222. FRemoteHost := FIP.Host;
  223. FIPAddress := FIP.Address;
  224. end;
  225. end;
  226. end;
  227. procedure TCnPing.SetRemoteHost(const Value: string);
  228. begin
  229. if FRemoteHost <> Value then
  230. begin
  231. // RemoteHost 更改的话,RemoteIP 自动清空
  232. FRemoteHost := Value;
  233. if SetIP('', FRemoteHost, FIP) then
  234. begin
  235. FRemoteIP := FIP.IP;
  236. FIPAddress := FIP.Address;
  237. end;
  238. end;
  239. end;
  240. procedure TCnPing.SetTimeOut(const Value: DWord);
  241. begin
  242. FTimeOut := Value;
  243. end;
  244. procedure TCnPing.SetTTL(const Value: Byte);
  245. begin
  246. FTTL := Value;
  247. end;
  248. procedure TCnPing.SetDataString(const Value: string);
  249. begin
  250. FDataString := Value;
  251. end;
  252. function TCnPing.GetDataString: string;
  253. begin
  254. if FDataString = '' then
  255. FDataString := SCnPingData;
  256. Result := FDataString;
  257. end;
  258. function TCnPing.Ping(var aReply: string): Boolean;
  259. var
  260. iCount, iResult: Integer;
  261. sReply: string;
  262. begin
  263. aReply := '';
  264. iResult := 0;
  265. try
  266. SetIP(RemoteIP, RemoteHost, FIP);
  267. for iCount := 1 to PingCount do
  268. begin
  269. iResult := PingIP_Host(FIP, Pointer(FDataString)^, Length(DataString) * SizeOf(Char),
  270. sReply);
  271. aReply := aReply + #13#10 + sReply;
  272. if iResult < 0 then
  273. Break;
  274. if FDelay > 0 then
  275. Sleep(FDelay);
  276. end;
  277. finally
  278. Result := iResult >= 0;
  279. end;
  280. end;
  281. function TCnPing.PingOnce(var aReply: string): Boolean;
  282. begin
  283. SetIP(RemoteIP, RemoteHost, FIP);
  284. Result := PingIP_Host(FIP, pointer(FDataString)^, Length(DataString),
  285. aReply) >= 0;
  286. end;
  287. function TCnPing.PingOnce(const aIP: string; var aReply: string): Boolean;
  288. begin
  289. SetIP(aIP, aIP, FIP);
  290. Result := PingIP_Host(FIP, pointer(FDataString)^, Length(DataString),
  291. aReply) >= 0;
  292. end;
  293. function TCnPing.PingFromBuffer(var Buffer; Count: Integer;
  294. var aReply: string): Boolean;
  295. begin
  296. SetIP(RemoteIP, RemoteHost, FIP);
  297. Result := PingIP_Host(FIP, Buffer, Count, aReply) >= 0;
  298. end;
  299. function TCnPing.PingIP_Host(const aIP: TIpInfo; const Data;
  300. Count: Cardinal; var aReply: string): Integer;
  301. var
  302. IPOpt: TCnIPOptionInformation; // 发送数据结构
  303. pReqData, pRevData: PAnsiChar;
  304. pCIER: PCnIcmpEchoReply;
  305. begin
  306. Result := -100;
  307. pReqData := nil;
  308. if Count <= 0 then
  309. begin
  310. aReply := GetReplyString(Result, aIP, nil);
  311. Exit;
  312. end;
  313. if aIP.Address = INADDR_NONE then
  314. begin
  315. Result := -1;
  316. aReply := GetReplyString(Result, aIP, nil);
  317. Exit;
  318. end;
  319. GetMem(pCIER, SizeOf(TCnICMPEchoReply) + Count);
  320. GetMem(pRevData, Count);
  321. try
  322. FillChar(pCIER^, SizeOf(TCnICMPEchoReply) + Count, 0); // 初始化接收数据结构
  323. pCIER^.Data := pRevData;
  324. GetMem(pReqData, Count);
  325. Move(Data, pReqData^, Count); // 准备发送的数据
  326. FillChar(IPOpt, Sizeof(IPOpt), 0); // 初始化发送数据结构
  327. IPOpt.TTL := FTTL;
  328. try //Ping开始
  329. if WSAStartup(MAKEWORD(2, 0), FWSAData) <> 0 then
  330. raise Exception.Create(SInitFailed);
  331. if IcmpSendEcho(hICMP, //dll handle
  332. aIP.Address, //target
  333. pReqData, //data
  334. Count, //data length
  335. @IPOpt, //addree of ping option
  336. pCIER,
  337. SizeOf(TCnICMPEchoReply) + Count, //pack size
  338. FTimeOut //timeout value
  339. ) <> 0 then
  340. begin
  341. Result := 0; // Ping正常返回
  342. if Assigned(FOnReceived) then
  343. FOnReceived(Self, aIP.IP, aIP.Host, IPOpt.TTL, IPOpt.TOS);
  344. end
  345. else
  346. begin
  347. Result := -2; // 没有响应
  348. if Assigned(FOnError) then
  349. FOnError(Self, aIP.IP, aIP.Host, IPOpt.TTL, IPOpt.TOS, SNoResponse);
  350. end;
  351. except
  352. on E: Exception do
  353. begin
  354. Result := -3; // 发生错误
  355. if Assigned(FOnError) then
  356. FOnError(Self, aIP.IP, aIP.Host, IPOpt.TTL, IPOpt.TOS, E.Message);
  357. end;
  358. end;
  359. finally
  360. WSACleanUP;
  361. aReply := GetReplyString(Result, aIP, pCIER);
  362. if pRevData <> nil then
  363. begin
  364. FreeMem(pRevData); // 释放内存
  365. pCIER.Data := nil;
  366. end;
  367. if pReqData <> nil then
  368. FreeMem(pReqData); //释放内存
  369. FreeMem(pCIER); //释放内存
  370. end;
  371. end;
  372. function TCnPing.GetReplyString(aResult: Integer; aIP: TIpInfo;
  373. pIPE: PCnIcmpEchoReply): string;
  374. var
  375. sHost: string;
  376. begin
  377. Result := SInvalidAddr;
  378. case aResult of
  379. -100: Result := SICMPRunError;
  380. -1: Result := SInvalidAddr;
  381. -2: Result := Format(SNoResponse, [RemoteHost]);
  382. else
  383. if pIPE <> nil then
  384. begin
  385. sHost := aIP.IP;
  386. if aIP.Host <> '' then
  387. sHost := aIP.Host + ': ' + sHost;
  388. Result := (Format(SPingResultString, [sHost, pIPE^.DataSize, pIPE^.RTT,
  389. pIPE^.Options.TTL]));
  390. end;
  391. end;
  392. end;
  393. function TCnPing.GetIPByName(const aName: string;
  394. var aIP: string): Boolean;
  395. var
  396. pHost: PHostEnt;
  397. FWSAData: TWSAData;
  398. sName: array[0..255] of AnsiChar;
  399. begin
  400. Result := False;
  401. StrPCopy(sName, {$IFDEF UNICODE}AnsiString{$ENDIF}(aName));
  402. aIP := '';
  403. if aName = '' then
  404. Exit;
  405. WSAStartup($101, FWSAData);
  406. try
  407. pHost := GetHostByName(@sName);
  408. Result := pHost <> nil;
  409. if Result then
  410. aIP := {$IFDEF UNICODE}String{$ENDIF}(inet_ntoa(PInAddr(pHost^.h_addr_list^)^));
  411. finally
  412. WSACleanup;
  413. end;
  414. end;
  415. function TCnPing.SetIP(aIPAddr, aHost: string; var aIP: TIpInfo): Boolean;
  416. var
  417. pIPAddr: PAnsiChar;
  418. begin
  419. Result := False;
  420. aIP.Address := INADDR_NONE;
  421. aIP.IP := aIPAddr;
  422. aIP.Host := aHost;
  423. if aIP.IP = '' then
  424. begin
  425. if (aIP.Host = '') or (not GetIPByName(aIP.Host, aIP.IP)) then
  426. Exit;
  427. end;
  428. GetMem(pIPAddr, Length(aIP.IP) + 1);
  429. try
  430. ZeroMemory(pIPAddr, Length(aIP.IP) + 1);
  431. StrPCopy(pIPAddr, {$IFDEF UNICODE}AnsiString{$ENDIF}(aIP.IP));
  432. aIP.Address := inet_addr(PAnsiChar(pIPAddr)); // IP转换成无点整型
  433. finally
  434. FreeMem(pIPAddr); // 释放申请的动态内存
  435. end;
  436. Result := aIP.Address <> INADDR_NONE;
  437. end;
  438. initialization
  439. InitIcmpFunctions;
  440. finalization
  441. FreeIcmpFunctions;
  442. end.