{******************************************************************************} { CnPack For Delphi/C++Builder } { 中国人自己的开放源码第三方开发包 } { (C)Copyright 2001-2018 CnPack 开发组 } { ------------------------------------ } { } { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 } { 改和重新发布这一程序。 } { } { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 } { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 } { } { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 } { 还没有,可访问我们的网站: } { } { 网站地址:http://www.cnpack.org } { 电子邮件:master@cnpack.org } { } {******************************************************************************} unit CnUDP; {* |
================================================================================
* 软件名称:网络通讯组件包
* 单元名称:UDP 通讯单元
* 单元作者:周劲羽 (zjy@cnpack.org)
* 备    注:定义了 TCnUDP,使用非阻塞方式进行 UDP 通讯,支持广播
* 开发平台:PWin2000Pro + Delphi 5.01
* 兼容测试:PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6
* 本 地 化:该单元中的字符串均符合本地化处理方式
* 单元标识:$Id$
* 修改记录:2008.11.28 V1.1
*                加入一控制接收缓冲区大小的属性
*           2003.11.21 V1.0
*                创建单元
================================================================================
|
} interface {$I CnPack.inc} uses Windows, Messages, Classes, SysUtils, WinSock, Forms, contnrs; const csDefRecvBuffSize = 4096; csDefUDPSendBuffSize = 256 * 1024; csDefUDPRecvBuffSize = 256 * 1024; type //============================================================================== // UDP 通讯类 //============================================================================== { TCnUDP } TOnReceive = procedure(Sender: TComponent; Buffer: Pointer; Len: Integer; FromIP: string; Port: Integer) of object; {* 接收到数据事件 |
     Sender     - TCnUDP 对象
     Buffer     - 数据缓冲区
     Len        - 数据缓冲区长度
     FromIP     - 数据来源 IP
     Port       - 数据来源端口号
   |
} TCnUDP = class(TComponent) {* 使用非阻塞方式进行 UDP 通讯的类。支持广播、数据队列等。} private FRemoteHost: string; FRemotePort: Integer; FLocalPort: Integer; FSocketWindow: HWND; FOnDataReceived: TOnReceive; FListening: Boolean; Wait_Flag: Boolean; RemoteAddress: TSockAddr; RemoteHostS: PHostEnt; Succeed: Boolean; Procing: Boolean; EventHandle: THandle; ThisSocket: TSocket; Queue: TQueue; FLastError: Integer; FRecvBufSize: Cardinal; FRecvBuf: Pointer; FBindAddr: string; FSockCount: Integer; FUDPSendBufSize: Cardinal; FUDPRecvBufSize: Cardinal; procedure WndProc(var Message: TMessage); function ResolveRemoteHost(ARemoteHost: string): Boolean; procedure SetLocalPort(NewLocalPort: Integer); procedure ProcessIncomingdata; procedure ProcessQueue; procedure FreeQueueItem(P: Pointer); function GetQueueCount: Integer; procedure SetupLastError; function GetLocalHost: string; procedure UpdateBinding; procedure SetRecvBufSize(const Value: Cardinal); procedure SetBindAddr(const Value: string); function SockStartup: Boolean; procedure SockCleanup; procedure SetUDPRecvBufSize(const Value: Cardinal); procedure SetUDPSendBufSize(const Value: Cardinal); protected procedure Wait; procedure Loaded; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function SendStream(DataStream: TStream; BroadCast: Boolean = False): Boolean; {* 发送一个数据流。如果 BroadCase 为真,执行 UDP 广播,否则发送数据到 RomoteHost 的机器上的 RemotePort 端口} function SendBuffer(Buff: Pointer; Length: Integer; BroadCast: Boolean = False): Boolean; {* 发送一个数据块。如果 BroadCase 为真,执行 UDP 广播,否则发送数据到 RomoteHost 的机器上的 RemotePort 端口} procedure ClearQueue; {* 清空数据队列。如果用户来不及处理接收到的数据,组件会把新数据包放到数据 队列中,调用该方法可清空数据队列} procedure ProcessRecv; {* 处理该 UDP 接口的接收内容。由于 CnUDP 组件的 OnDataReceived 是在主线程 消息处理中调用的,如果主线程代码需要等待 UDP 接收而不希望处理所有消息, 可以调用该函数。} property LastError: Integer read FLastError; {* 最后一次错误的错误号,只读属性} property Listening: Boolean read FListening; {* 表示当前是否正在监听本地端口,只读属性} property QueueCount: Integer read GetQueueCount; {* 当前数据队列的长度,只读属性} property BindAddr: string read FBindAddr write SetBindAddr; {* 绑定本地地址} published property RemoteHost: string read FRemoteHost write FRemoteHost; {* 要发送 UDP 数据的目标主机地址} property RemotePort: Integer read FRemotePort write FRemotePort; {* 要发送 UDP 数据的目标主机端口号} property LocalHost: string read GetLocalHost; {* 返回本机 IP 地址,只读属性} property LocalPort: Integer read FLocalPort write SetLocalPort; {* 本地监听的端口号} property RecvBufSize: Cardinal read FRecvBufSize write SetRecvBufSize default csDefRecvBuffSize; {* 接收的数据缓冲区大小} property UDPSendBufSize: Cardinal read FUDPSendBufSize write SetUDPSendBufSize default csDefUDPSendBuffSize; {* UDP 发送的数据缓冲区大小} property UDPRecvBufSize: Cardinal read FUDPRecvBufSize write SetUDPRecvBufSize default csDefUDPRecvBuffSize; {* UDP 接收的数据缓冲区大小} property OnDataReceived: TOnReceive read FOnDataReceived write FOnDataReceived; {* 接收到 UDP 数据包事件} end; // 取广播地址 procedure GetBroadCastAddress(sInt: TStrings); // 取本机IP地址 procedure GetLocalIPAddress(sInt: TStrings); implementation {$R-} //============================================================================== // 辅助过程 //============================================================================== // 从Winsock 2.0导入函数WSAIOCtl function WSAIoctl(s: TSocket; cmd: DWORD; lpInBuffer: PCHAR; dwInBufferLen: DWORD; lpOutBuffer: PCHAR; dwOutBufferLen: DWORD; lpdwOutBytesReturned: LPDWORD; lpOverLapped: POINTER; lpOverLappedRoutine: POINTER): Integer; stdcall; external 'WS2_32.DLL'; const SIO_GET_INTERFACE_LIST = $4004747F; IFF_UP = $00000001; IFF_BROADCAST = $00000002; IFF_LOOPBACK = $00000004; IFF_POINTTOPOINT = $00000008; IFF_MULTICAST = $00000010; type sockaddr_gen = packed record AddressIn: sockaddr_in; filler: packed array[0..7] of AnsiChar; end; INTERFACE_INFO = packed record iiFlags: u_long; // Interface flags iiAddress: sockaddr_gen; // Interface address iiBroadcastAddress: sockaddr_gen; // Broadcast address iiNetmask: sockaddr_gen; // Network mask end; // 取广播地址 procedure DoGetIPAddress(sInt: TStrings; IsBroadCast: Boolean); var s: TSocket; wsaD: WSADATA; NumInterfaces: Integer; BytesReturned, SetFlags: u_long; pAddr, pMask, pCast: TInAddr; pAddrStr: string; PtrA: pointer; Buffer: array[0..20] of INTERFACE_INFO; i: Integer; begin WSAStartup($0101, wsaD); // Start WinSock s := Socket(AF_INET, SOCK_STREAM, 0); // Open a socket if (s = INVALID_SOCKET) then exit; try // Call WSAIoCtl PtrA := @bytesReturned; if (WSAIoCtl(s, SIO_GET_INTERFACE_LIST, nil, 0, @Buffer, 1024, PtrA, nil, nil) <> SOCKET_ERROR) then begin // If ok, find out how // many interfaces exist NumInterfaces := BytesReturned div SizeOf(INTERFACE_INFO); sInt.Clear; for i := 0 to NumInterfaces - 1 do // For every interface begin SetFlags := Buffer[i].iiFlags; if (SetFlags and IFF_BROADCAST = IFF_BROADCAST) and not (SetFlags and IFF_LOOPBACK = IFF_LOOPBACK) then begin pAddr := Buffer[i].iiAddress.AddressIn.sin_addr; pMask := Buffer[i].iiNetmask.AddressIn.sin_addr; if IsBroadCast then begin pCast.S_addr := pAddr.S_addr or not pMask.S_addr; pAddrStr := string(inet_ntoa(pCast)); end else begin pAddrStr := string(inet_ntoa(pAddr)); end; if sInt.IndexOf(pAddrStr) < 0 then sInt.Add(pAddrStr); end; end; end; except ; end; CloseSocket(s); WSACleanUp; end; // 取本机IP地址 procedure GetLocalIPAddress(sInt: TStrings); begin DoGetIPAddress(sInt, False); end; // 取广播地址 procedure GetBroadCastAddress(sInt: TStrings); begin DoGetIPAddress(sInt, True); end; //============================================================================== // UDP 通讯类 //============================================================================== { TCnUDP } const WM_ASYNCHRONOUSPROCESS = WM_USER + 101; Const_cmd_true = 'TRUE'; type PRecvDataRec = ^TRecvDataRec; TRecvDataRec = record FromIP: string[128]; FromPort: u_short; Buff: Pointer; BuffSize: Integer; end; constructor TCnUDP.Create(AOwner: TComponent); begin inherited Create(AOwner); Queue := TQueue.Create; FListening := False; Procing := False; FRecvBufSize := csDefRecvBuffSize; FUDPSendBufSize := csDefUDPSendBuffSize; FUDPRecvBufSize := csDefUDPRecvBuffSize; FBindAddr := '0.0.0.0'; GetMem(RemoteHostS, MAXGETHOSTSTRUCT); FSocketWindow := AllocateHWND(WndProc); EventHandle := CreateEvent(nil, True, False, ''); if SockStartup then begin ThisSocket := Socket(AF_INET, SOCK_DGRAM, 0); if ThisSocket = TSocket(INVALID_SOCKET) then begin SetupLastError; SockCleanup; Exit; end; setsockopt(ThisSocket, SOL_SOCKET, SO_DONTLINGER, Const_cmd_true, 4); setsockopt(ThisSocket, SOL_SOCKET, SO_BROADCAST, Const_cmd_true, 4); FListening := True; end; end; destructor TCnUDP.Destroy; begin if FRecvBuf <> nil then begin FreeMem(FRecvBuf); FRecvBuf := nil; end; ClearQueue; Queue.Free; FreeMem(RemoteHostS, MAXGETHOSTSTRUCT); DeallocateHWND(FSocketWindow); CloseHandle(EventHandle); if ThisSocket <> 0 then closesocket(ThisSocket); if FListening then SockCleanup; inherited Destroy; end; procedure TCnUDP.UpdateBinding; var Data: DWORD; Addr: TSockAddr; begin if not (csDesigning in ComponentState) then begin FListening := False; if ThisSocket <> 0 then begin closesocket(ThisSocket); SockCleanup; end; if SockStartup then begin ThisSocket := Socket(AF_INET, SOCK_DGRAM, 0); if ThisSocket = TSocket(INVALID_SOCKET) then begin SockCleanup; SetupLastError; Exit; end; end; FillChar(Addr, SizeOf(Addr), 0); Addr.sin_addr.S_addr := Inet_Addr(PAnsiChar(AnsiString(FBindAddr))); Addr.sin_family := AF_INET; Addr.sin_port := htons(FLocalPort); Wait_Flag := False; if WinSock.Bind(ThisSocket, Addr, SizeOf(Addr)) = SOCKET_ERROR then begin SetupLastError; SockCleanup; Exit; end; // Allow to send to 255.255.255.255 Data := 1; WinSock.setsockopt(ThisSocket, SOL_SOCKET, SO_BROADCAST, PAnsiChar(@Data), SizeOf(Data)); Data := FUDPSendBufSize; WinSock.setsockopt(ThisSocket, SOL_SOCKET, SO_SNDBUF, PAnsiChar(@Data), SizeOf(Data)); Data := FUDPRecvBufSize; WinSock.setsockopt(ThisSocket, SOL_SOCKET, SO_RCVBUF, PAnsiChar(@Data), SizeOf(Data)); WSAAsyncSelect(ThisSocket, FSocketWindow, WM_ASYNCHRONOUSPROCESS, FD_READ); FListening := True; end; end; procedure TCnUDP.Loaded; begin inherited; UpdateBinding; end; procedure TCnUDP.SetBindAddr(const Value: string); begin if Value <> FBindAddr then begin FBindAddr := Value; UpdateBinding; end; end; procedure TCnUDP.SetLocalPort(NewLocalPort: Integer); begin if NewLocalPort <> FLocalPort then begin FLocalPort := NewLocalPort; UpdateBinding; end; end; function TCnUDP.ResolveRemoteHost(ARemoteHost: string): Boolean; var Buf: array[0..127] of AnsiChar; begin Result := False; if not FListening then Exit; try RemoteAddress.sin_addr.S_addr := Inet_Addr(PAnsiChar(StrPCopy(Buf, {$IFDEF UNICODE}AnsiString{$ENDIF}(ARemoteHost)))); if RemoteAddress.sin_addr.S_addr = SOCKET_ERROR then begin Wait_Flag := False; WSAAsyncGetHostByName(FSocketWindow, WM_ASYNCHRONOUSPROCESS, Buf, PAnsiChar(RemoteHostS), MAXGETHOSTSTRUCT); repeat Wait; until Wait_Flag; if Succeed then begin with RemoteAddress.sin_addr.S_un_b do begin s_b1 := remotehostS.h_addr_list^[0]; s_b2 := remotehostS.h_addr_list^[1]; s_b3 := remotehostS.h_addr_list^[2]; s_b4 := remotehostS.h_addr_list^[3]; end; end; end; except ; end; if RemoteAddress.sin_addr.S_addr <> 0 then Result := True; if not Result then SetupLastError; end; function TCnUDP.SendStream(DataStream: TStream; BroadCast: Boolean): Boolean; var Buff: Pointer; begin GetMem(Buff, DataStream.Size); try DataStream.Position := 0; DataStream.Read(Buff^, DataStream.Size); Result := SendBuffer(Buff, DataStream.Size, BroadCast); finally FreeMem(Buff); end; end; function TCnUDP.SendBuffer(Buff: Pointer; Length: Integer; BroadCast: Boolean): Boolean; var Hosts: TStrings; i: Integer; function DoSendBuffer(Buff: Pointer; Length: Integer; Host: string): Boolean; var i: Integer; begin Result := False; try if not ResolveRemoteHost(Host) then Exit; RemoteAddress.sin_family := AF_INET; RemoteAddress.sin_port := htons(FRemotePort); i := SizeOf(RemoteAddress); if WinSock.sendto(ThisSocket, Buff^, Length, 0, RemoteAddress, i) <> SOCKET_ERROR then Result := True else SetupLastError; except SetupLastError; end; end; begin if BroadCast then begin Result := False; Hosts := TStringList.Create; try GetBroadCastAddress(Hosts); for i := 0 to Hosts.Count - 1 do if DoSendBuffer(Buff, Length, Hosts[i]) then Result := True; finally Hosts.Free; end; end else Result := DoSendBuffer(Buff, Length, FRemoteHost); end; function TCnUDP.GetQueueCount: Integer; begin Result := Queue.Count; end; procedure TCnUDP.FreeQueueItem(P: Pointer); var Rec: PRecvDataRec; begin Rec := PRecvDataRec(P); Rec.FromIP := ''; FreeMem(Rec.Buff); FreeMem(Rec); end; procedure TCnUDP.ClearQueue; var Rec: PRecvDataRec; begin while Queue.Count > 0 do begin Rec := Queue.Pop; FreeQueueItem(Rec); end; end; procedure TCnUDP.ProcessQueue; var Rec: PRecvDataRec; begin if Procing then Exit; Procing := True; try while Queue.Count > 0 do begin Rec := Queue.Pop; if Assigned(FOnDataReceived) then FOnDataReceived(Self, Rec.Buff, Rec.BuffSize, string(Rec.FromIP), Rec.FromPort); FreeQueueItem(Rec); end; finally Procing := False; end; end; procedure TCnUDP.ProcessRecv; var Unicode: Boolean; MsgExists: Boolean; Msg: TMsg; begin Unicode := IsWindowUnicode(FSocketWindow); if Unicode then MsgExists := PeekMessageW(Msg, FSocketWindow, 0, 0, PM_REMOVE) else MsgExists := PeekMessageA(Msg, FSocketWindow, 0, 0, PM_REMOVE); if MsgExists then begin if Msg.Message <> WM_QUIT then begin TranslateMessage(Msg); if Unicode then DispatchMessageW(Msg) else DispatchMessageA(Msg); end; end; end; procedure TCnUDP.WndProc(var Message: TMessage); begin if FListening then begin with Message do begin if Msg = WM_ASYNCHRONOUSPROCESS then begin if LParamLo = FD_READ then begin ProcessIncomingdata; if not Procing then ProcessQueue; end else begin Wait_Flag := True; if LParamHi > 0 then Succeed := False else Succeed := True; end; SetEvent(EventHandle); end else Result := DefWindowProc(FSocketWindow, Msg, WParam, LParam); end; end; end; procedure TCnUDP.ProcessIncomingdata; var from: TSockAddr; i: Integer; Rec: PRecvDataRec; IBuffSize: Integer; begin i := SizeOf(from); if FRecvBuf = nil then GetMem(FRecvBuf, FRecvBufSize); IBuffSize := WinSock.recvfrom(ThisSocket, FRecvBuf^, FRecvBufSize, 0, from, i); if (IBuffSize > 0) and Assigned(FOnDataReceived) then begin GetMem(Rec, SizeOf(TRecvDataRec)); ZeroMemory(Rec, SizeOf(TRecvDataRec)); Rec.FromIP := ShortString(Format('%d.%d.%d.%d', [Ord(from.sin_addr.S_un_b.S_b1), Ord(from.sin_addr.S_un_b.S_b2), Ord(from.sin_addr.S_un_b.S_b3), Ord(from.sin_addr.S_un_b.S_b4)])); Rec.FromPort := ntohs(from.sin_port); GetMem(Rec.Buff, IBuffSize); Rec.BuffSize := IBuffSize; CopyMemory(Rec.Buff, FRecvBuf, IBuffSize); Queue.Push(Rec); end; end; procedure WaitforSync(Handle: THandle); begin repeat if MsgWaitForMultipleObjects(1, Handle, False, INFINITE, QS_ALLINPUT) = WAIT_OBJECT_0 + 1 then Application.ProcessMessages else Break; until False; end; procedure TCnUDP.Wait; begin WaitforSync(EventHandle); ResetEvent(EventHandle); end; procedure TCnUDP.SetupLastError; begin FLastError := WSAGetLastError; end; procedure TCnUDP.SockCleanup; begin if FSockCount > 0 then begin Dec(FSockCount); if FSockCount = 0 then WSACleanup; end; end; function TCnUDP.SockStartup: Boolean; var wsaData: TWSAData; begin if FSockCount = 0 then begin Result := WSAStartup($0101, wsaData) = 0; if not Result then Exit; end; Inc(FSockCount); Result := True; end; function TCnUDP.GetLocalHost: string; var p: PHostEnt; s: array[0..256] of AnsiChar; begin SockStartup; try GetHostName(@s, 256); p := GetHostByName(@s); Result := string(inet_ntoa(PInAddr(p^.h_addr_list^)^)); finally SockCleanup; end; end; procedure TCnUDP.SetRecvBufSize(const Value: Cardinal); begin if FRecvBufSize <> Value then begin FRecvBufSize := Value; if FRecvBuf <> nil then begin // 释放,等待下次需要时重新分配 FreeMem(FRecvBuf); FRecvBuf := nil; end; end; end; procedure TCnUDP.SetUDPRecvBufSize(const Value: Cardinal); var Data: DWORD; begin FUDPRecvBufSize := Value; if FListening then begin Data := FUDPRecvBufSize; WinSock.setsockopt(ThisSocket, SOL_SOCKET, SO_RCVBUF, PAnsiChar(@Data), SizeOf(Data)); end; end; procedure TCnUDP.SetUDPSendBufSize(const Value: Cardinal); var Data: DWORD; begin FUDPSendBufSize := Value; if FListening then begin Data := FUDPSendBufSize; WinSock.setsockopt(ThisSocket, SOL_SOCKET, SO_SNDBUF, PAnsiChar(@Data), SizeOf(Data)); end; end; end.