在不同的DELHI版本中,INDY的版本是不同的,而且9和10不兼容,为了让代码简单,轻便,跨版本移值,决定udp使用时使用TUdpSocket控件来实现收发。结果却令人郁闷的发现,无法接收,因为TUdpSocket是为作Client设置的,没有进行Bind,于是,显式调用了Bind,依然不行,返回一个错误码。查看netstat ,显示的是一个随机的端口。
由于对WINSOCK底层不是很了解,看源码时没有发现什么问题。结果在CSDN上有人指出了问题所在,TUdpSocket在Open时会调用Connect,而Connect时会bind到一个随机的端口,所以再次bind时会失败。
原来如此,原来一个Socket不管是Client还是Server,都会Bind到一个端口的,我还以为只有服务端会Bind,惭愧啊。
既然找到了原因,也就找到了解决的办法,,办法有两个:
(1))给VCL打补丁,在Open的时候先进行bind,然后再调用connect,但是这样就改变了VCL原来控件的行为,也许以后某个人会进行怎么的调用,想当然的认为Tudpsoket发送的时候不会bind在设置的本地端口上,但用了这个patch,却会一直绑在固定的端口上,让他一头雾水。
unit u_UDPSocketPatch;
interface uses Windows, Sockets, WinSock;
implementation type TUdpSocketPatcher = Class(TIpSocket) Protected Procedure Open; Override; end; TAccessCrack = Class(TCustomIpClient) private FConnected: Boolean; FOnConnect: TSocketNotifyEvent; FOnDisconnect: TSocketNotifyEvent; end; Procedure PatchVCLCode(ProcOld, ProcNew: Pointer); var newCode : packed record JmpRel32 : Byte; Offset32 : Integer; end; begin newCode.JmpRel32 := $E9; newCode.Offset32 := Integer(procNew) - Integer(procOld) - 5; WriteProcessMemory( GetCurrentProcess, procOld, @newCode, SizeOf(newCode), DWORD(nil^) );
end; { TIPSocketPatcher }
procedure TUdpSocketPatcher.Open; var addr: TSockAddr; begin inherited Open; Bind; if Active and not TAccessCrack(Self).FConnected then begin addr := GetSocketAddr(RemoteHost, RemotePort); {$IFDEF MSWINDOWS} TAccessCrack(Self).FConnected := ErrorCheck(WinSock.connect(Handle, addr, sizeof(addr))) = 0; {$ENDIF} {$IFDEF LINUX} FConnected := ErrorCheck(Libc.connect(Handle, addr, sizeof(addr))) = 0; if not FConnected then // Workaround on bug in Red Hat 6.2 Close; {$ENDIF} if TAccessCrack(Self).Connected then TAccessCrack(Self).DoConnect; end; end;
initialization PatchVCLCode(@TUdpSocket.Open, @TUdpSocketPatcher.Open ); end.
(2)另一个重写一个组件作为服务器,这个办法就是需要用户再安装此组件到DELPHI上,感觉也不是很好啊。
unit u_UDPSocketServer;
interface uses Classes, Sockets, Winsock; type TudpSocketMode = (smClient, smServer); TUdpSocketServer = class(TIpSocket) private FMode: TudpSocketMode; procedure SetMode(const Value: TudpSocketMode); Public Constructor Create(AOwner: TComponent); Override; procedure Open; Override; published property Active; property BlockMode; property LocalHost; property LocalPort; property RemoteHost; property RemotePort; property OnCreateHandle; property OnDestroyHandle; property OnReceive; property OnSend; property OnError; Property Mode: TudpSocketMode read FMode write SetMode; end;
implementation
{ TUdpSocketServer }
constructor TUdpSocketServer.Create(AOwner: TComponent); begin inherited; SockType := stDgram; Protocol := IPPROTO_UDP; end;
procedure TUdpSocketServer.Open; begin inherited; if (FMode = smServer) and Active then Bind; end;
procedure TUdpSocketServer.SetMode(const Value: TudpSocketMode); begin FMode := Value; end;
end.
比较起来,还是新生成一个控件比较简洁,而且不会影响到原来的VCL
|
请发表评论