Delphi中的TUdpSocket不能当作服务端的问题

  在不同的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