I have a TIdUDPClient created like this:
myUDPClient := TIdUDPClient.Create(nil);
myUDPClient.ReceiveTimeout := 500;
myUDPClient.Binding.Port := 300;
myUDPClient.Active := True;
The binding IP is not specified because I have 3 ethernet adapters with dynamic addressing (192.168.x.x, 10.10.x.x and 172.16.x.x), so the binding IP is generated by Indy and it is 0.0.0.0.
When I receive a packet I can determine the sender IP, but I cannot determine the local IP on which I received the packet (not 0.0.0.0, but one of the 3 IPs assigned to my computer).
Do you know any method to accomplish this?
Do not assign to Binding.Port directly, assign to myUDPClient.BoundPort instead and let TIdUDPClient assign to Binding.Port internally.
Since you are binding to 0.0.0.0 locally, there is no way to determine the destination IP when using TIdUDPClient to read the packet, since it uses the socket API recvfrom() function, hich does not report that info. The socket API getsockname() function will report 0.0.0.0 because that is what the socket is really bound to.
On Windows XP+, you can use GStack.ReceiveMsg() instead to receive packets. It has a TIdPacketInfo output parameter, which contains has a DestIP field (you have to use Binding.SetSockOpt() to enable ReceiveMsg() to collect that info), eg:
...
myUDPClient.Active := True;
myUDPClient.Binding.SetSockOpt(IPPROTO_IP, IP_PKTINFO, 1);
var
Buffer: TIdBytes;
BufLen: LongWord;
PktInfo: TIdPacketInfo;
begin
SetLength(Buffer, ...);
BufLen := GStack.ReceiveMsg(myUDPClient.Binding.Handle, Buffer, PktInfo);
// use Buffer up to BufLen bytes
// PktInfo.DestIP will be the IP that received the packet
end;
Alternatively, you can switch to TIdUDPServer instead, and create a separate entry in its Bindings collection for each local IP you want to receive packets on:
myUDPServer := TIdUDPServer.Create(nil);
myUDPServer.DefaultPort := 300;
myUDPServer.OnUDPRead := UDPRead;
LocalIPs := TStringList.Create;
try
GStack.AddLocalAddressesToList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.IP := LocalIPs[I];
finally
LocalIPs.Free;
end;
{
Or this, if you are using an up-to-date Indy 10 snapshot:
LocalIPs := TIdStackLocalAddressList.Create;
try
GStack.GetLocalAddressList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.SetBinding(LocalIPs[I].IPAddress, myUDPServer.DefaultPort, LocalIPs[I].IPVersion);
finally
LocalIPs.Free;
end;
}
myUDPServer.Active := True;
procedure TMyClass.DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
// ABinding.IP is the local IP address that received this packet...
end;
Related
I have a code which sends audio stream from server to client using TIdTCPClient and TIdTCPServer, it works fine, but it is not efficient when using a lot of clients so I decided to use TIdUDPClient and TIdUDPServer instead but have got some problems which I don't understand (I use the latest indy snapshot).
Server part of code:
.......
//AudioCallback - is a callback function for a 3rd party lib that prepares audio stream, it is called several times per second
...
var fStream:TFileStream;
...
procedure AudioCallback(buffer: LPSTR; len: UINT); cdecl;
var
IdUDPClient: TIdUDPClient;
IdBytes: TIdBytes;
begin
//len is always 1024
IdBytes := RawToBytes(buffer^, len);//works bad
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := cb mod 256;}//works fine
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := Random(255);}//works bad
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := cb mod 2;}//works fine
fStream.Write(IdBytes[0], length(IdBytes));//for testing purposes
IdUDPClient := TIdUDPClient.Create;
with IdUDPClient do
try
Host := '127.0.0.1';
Port := 32563;
try
Connect;
if Connected then
SendBuffer(IdBytes);
Disconnect;
except
end;
finally
Free;
end;
end;
Client part is simple:
....
var fStream:TFileStream;
....
procedure TClient.IdUDPReadEvent(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
fStream.Write(AData[0],length(AData));
end;
....
The issues that I don't understand:
I tried to use non-local IdUDPClient but the second call of
methods SendBuffer(IdBytes) or Connect always raises an idException
"Invalid argument", though the IdUDPClient properties stayed the
same
The packets sent from the server side and received on the
client differ. I tested it many times. The first received packet is
always the same. The second and the following others are the same
only if you manually fill the packet in some regular way like
IdBytes[cb] := cb mod 256. If you leave it with original data or fill it with random data - you receive not what you sent.
I am testing IndyFTP to upload a file to a server. The file is uploaded but has 0 bytes because there is a EIdAccessTimeout exception - 'Accept timed out". How can I prevent the exception? Is my code incorrect? The code is shown below:
procedure TForm1.FTPUpload1Click(Sender: TObject);
{ Indy FTP Upload. }
var
iHost: string;
iUsername: string;
iPassword: string;
iFolder: string;
iSourceFile: string;
iDestFile: string;
iAppend: boolean;
iStartPos: Int64;
begin
iHost := FTPHost1.Text;
iUsername := Username1.Text;
iPassword := Password1.Text;
iFolder := ServerFolder1.Text;
if FileExists(SourceFile1.Text) then
iSourceFile := SourceFile1.Text
else
Exit;
if FileExists(SourceFile1.Text) then
iDestFile := ExtractFileName(SourceFile1.Text)
else
Exit;
iAppend := False;
iStartPos := -1;
IdFTP1.Host := iHost;
IdFTP1.Username := iUsername;
IdFTP1.Password := iPassword;
IdFTP1.Connect;
IdFTP1.TransferType := ftBinary;
IdFTP1.Put(iSourceFile);
IdFTP1.Disconnect;
end;
There are some unused vars listed because I am just learning and have not used some of the parameters yet.
Most likely, your FTP client is set to ACTIVE mode, so this error means that after a successful login to the FTP server, the "reverse" connection couldn't be established (the file transfer).
In active mode FTP the client connects from a random unprivileged port
(N > 1023) to the FTP server's command port, port 21. Then, the client
starts listening to port N+1 and sends the FTP command PORT N+1 to the
FTP server. The server will then connect back to the client's
specified data port from its local data port, which is port 20.
Active FTP vs. Passive FTP, a Definitive Explanation
You can set to passive mode this way:
IdFTP1.Passive := True;
EDIT
In addition, use try-except-finally blocks, so you can do some error handling. Something like:
try
IdFTP1.Connect;
try
IdFTP1.Put(...);
finally
IdFTP1.Disconnect;
end;
except
// couldn't connect
end;
I'm trying to make a text chat through indy udp component and here is the codes for server and client
udp Client:
procedure TForm1.SendClick(Sender: TObject);
begin
sendtocl.Broadcast(usertype.Text, 12000);
usertype.Clear;
end;
onread Server :
procedure TForm1.UDPReceiverUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
var
AudioDataSize: Integer;
AudioData : Pointer;
begin
try
EnterCriticalSection(Section);
try
AudioDataSize := Length(AData);
if AudioDataSize > 10 then
begin
try
if not Player.Active then
begin
Player.Active := True;
Player.WaitForStart;
end;
except
end;
if BlockAlign > 1 then Dec(AudioDataSize, AudioDataSize mod BlockAlign);
AudioData := AudioBuffer.BeginUpdate(AudioDataSize);
try
BytesToRaw(AData, AudioData^, AudioDataSize);
finally
AudioBuffer.EndUpdate;
end;
end else
begin
Player.Active := False;
Player.WaitForStop;
end;
finally
LeaveCriticalSection(Section);
end;
except
end;
begin
chatboxmsg.Lines.Add(BytesToString(AData));
end;
end;
its working good but i had problem if i use the udp client with other purpose like send buffer "To send audio " the chatboxmsg.line shows flooded data of audio buffer any way to make the server read separated Adata ?
In UDP, every send (Broadcast(), Send(), SendBuffer(), etc) transmits a distinct datagram. The OnUDPRead event is triggered for every datagram that is received. AData contains the data of one distinct datagram at a time.
So, you have two choices:
format your datagrams in such a way (such as putting a header at the front of the data) so that they identify the type of data they carry. That way, your OnUDPRead handler can read the identifier/header and know whether to put the remaining data to ChatBoxMsg or pass it to the sound system.
if you do not want to (or cannot) change your datagram formats, then you will have to send text and audio datagrams to different ports. You can use a single TIdUDPServer object listening on multiple ports at the same time (that is what its Bindings collection is for), in which case the ABinding parameter of the OnUDPServer event will tell you which port AData was received on. Or, just use two separate TIdUDPServer objects, each one listening on a different port, and assign different OnUDPRead handlers to each one.
I would like to read data from an electronic device that sends data to my PC by UDP. I have programmed a UDP server in Delphi. An exception occurs in class EIdSocketError (Failure #10049). Here is my code:
procedure TForm1.Button1Click(Sender: TObject);
begin
IdUDPServer1.Bindings.add.IP := '192.168.1.1'; //Electronic device ip
IdUDPServer1.Bindings.add.Port:= 49152; //Electronic device port
IdUDPServer1.OnUDPRead:= UDPRead;
IdUDPServer1.Active:=True;
end;
procedure TForm1.UDPRead (Sender: TObject; AData: TStream; ABinding: TIdSocketHandle);
var
bytes_received: integer;
begin
bytes_received:=AData.size;
end;
What am I doing wrong?
Thanks in advance
As Keith Miller stated, you are misusing the Bindings property. You are adding 2 bindings to the server - one for 192.168.1.1 on port 0 and another for 0.0.0.0 on port 49152. You need to call Bindings.Add() only once for each IP/Port pair you want to bind to, eg:
var
Binding: TIdSocketHandle
Binding := IdUDPServer1.Bindings.Add;
Binding.IP := ...;
Binding.Port := ...;
Or:
with IdUDPServer1.Bindings.Add do
begin
IP := ...;
Port := ...;
end;
If you set the DefaultPort property ahead of time, then you can simplify the above to this:
IdUDPServer1.DefaultPort := ...;
IdUDPServer1.Bindings.Add.IP := ...;
With that said, socket error 10049 is WSAEADDRNOTAVAIL, which means you are using the wrong IP address in the first place. You need to specify an IP that belongs to the PC that TIdUDPServer is running on - the IP that the device will be sending UDP packets to. You can either bind to the single wildcard IP 0.0.0.0 (or just a blank string ''), which tells TIdUDPServer to bind to all available local IPs:
IdUDPServer1.DefaultPort := ...;
IdUDPServer1.Bindings.Add.IP := ''; // or: '0.0.0.0'
Or you can use Indy's GStack.LocalAddresses property to determine the locally available IPs and create separate Bindings for them individually as needed.
IdUDPServer1.Bindings.Clear;
IdUDPServer1.DefaultPort := ...;
with GStack.LocalAddresses do
begin
for I := 0 to Count-1 do
IdUDPServer1.Bindings.Add.IP := Strings[I];
end;
Update: if you bind the server to port 0 to let the OS pick a port, you can discover what port was selected by looking at the TIdSocketHandle.Port property after the server was activated:
var
Binding: TIdSocketHandle
ListeningPort: TIdPort;
IdUDPServer1.Bindings.Clear;
Binding := IdUDPServer1.Bindings.Add;
Binding.IP := ...;
Binding.Port := 0;
{
or:
IdUDPServer1.DefaultPort := 0;
Binding := IdUDPServer1.Bindings.Add;
Binding.IP := ...;
}
IdUDPServer1.Active := True;
ListeningPort := Binding.Port;
Is there a more elegant way of checking if a TCP port is available with Delphi other than catching a netstat call?
I guess you can use Indy's components to do that. For instance a TIdHTTPServer will raise an exception if a port is in use when it is being opened.
So basically you could create such component, bind it to localhost:<yourport> and if an exception is raised ( catch it and check it ) then the port is probably in use, else it is free.
I guess other indy components can tell if a port is open or not, but I can't look at it right now.
This was just to give you an approach.
#Mattl, if Available means open for you, you can use this code.
program CheckTCP_PortOpen;
{$APPTYPE CONSOLE}
uses
Winsock; //Windows Sockets API Unit
function PortTCPIsOpen(dwPort : Word; ipAddressStr:string) : boolean;
var
client : sockaddr_in;//sockaddr_in is used by Windows Sockets to specify a local or remote endpoint address
sock : Integer;
begin
client.sin_family := AF_INET;
client.sin_port := htons(dwPort);//htons converts a u_short from host to TCP/IP network byte order.
client.sin_addr.s_addr := inet_addr(PChar(ipAddressStr)); //the inet_addr function converts a string containing an IPv4 dotted-decimal address into a proper address for the IN_ADDR structure.
sock :=socket(AF_INET, SOCK_STREAM, 0);//The socket function creates a socket
Result:=connect(sock,client,SizeOf(client))=0;//establishes a connection to a specified socket.
end;
var
ret : Integer;
wsdata : WSAData;
begin
Writeln('Init WinSock');
ret := WSAStartup($0002, wsdata);//initiates use of the Winsock
if ret<>0 then exit;
try
Writeln('Description : '+wsData.szDescription);
Writeln('Status : '+wsData.szSystemStatus);
if PortTCPIsOpen(80,'127.0.0.1') then
Writeln('Open')
else
Writeln('Close');
finally
WSACleanup; //terminates use of the Winsock
end;
Readln;
end.
netstat information can be retrieved by calling the GetTcpTable and GetUdpTable functions in the IP Helper API, or IPHLPAPI.DLL. For more information on calling the IPHLPAPI.DLL from Delphi, check out this Network traffic monitor. There are some wrappers for it too, and it is part of JEDI API Library.
I wrote a Delphi version of NetStat long ago, but have since lost the source code. Those resources should get you started though.
The following code from Synapse works very well:
uses
blcksock;
function PortAvailable(Port:STring):boolean;
var
svr : TTCPBlockSocket;
begin
svr := TTCPBlockSocket.Create;
try
svr.Bind('0.0.0.0',Port);
svr.Listen;
result := svr.LastError = 0;
Svr.CloseSocket;
finally
svr.Free;
end;
end;
Using an Indy.Sockets v10 TIdTCPServer component:
function TExample.IsTCPPortAvailable(const APort: Word): Boolean;
var
LTCPServer: TIdTCPServer;
LBinding: TIdSocketHandle;
begin
Result := True;
LTCPServer := TIdTCPServer.Create;
try
try
with LTCPServer do
begin
DefaultPort := APort;
LBinding := Bindings.Add;
LBinding.IP := '127.0.0.1';
LBinding.Port := APort;
OnExecute := TCPServerExecute;
Active := True;
end;
finally
LTCPServer.Free;
end;
except on EIdCouldNotBindSocket do
Result := False;
end;
end;
procedure TExample.TCPServerExecute(AContext: TIdContext);
begin
end;
Based on Silver's example above, and since in many cases you want to find an available port rather than just verifying that a given port is in use:
uses
//Indy V10
IdContext,
IdSocketHandle,
IdTcpServer;
type
//our port-checking tool
TPortChk = class(TIdTCPServer)
procedure OnExec(AContext: TIdContext);
end;
procedure TPortChk.OnExec(AContext: TIdContext);
begin
//does nothing, but must exist and be hooked
end;
//check a TCP port to see if it's already in use.
//normally used before opening a listener.
function PortAvailable(APort: Word): Boolean;
var
svr: TPortChk;
bnd: TIdSocketHandle;
begin
//assume our port is available
Result := True;
//create our checking object
svr := TPortChk.Create;
try
//set the execute event
svr.OnExecute := svr.OnExec;
//loop looking for an available port
try
//set up the binding for our local system and the
//port in question
bnd := svr.Bindings.Add;
bnd.IP := '127.0.0.1';
bnd.Port := APort;
//try to bind. This will throw an EIdCouldNotBindSocket
//exception if the port is already in use.
svr.Active := True;
//if we get here, the port is *currently* available.
//close the server and bail
svr.Active := False;
Exit;
except
//whoops, port's in use (or some related failure)
Result := False;
end;
finally
svr.Free;
end;
end;
//search a range of ports for the first available
function FindAvailablePort(First, Count: Word): Word;
var
svr: TPortChk;
bnd: TIdSocketHandle;
begin
//assume our initial port is available
Result := First;
//create our checking object
svr := TPortChk.Create;
try
//set the execute event
svr.OnExecute := svr.OnExec;
//loop looking for an available port
while (Result - First) < Count do begin
try
//set up the binding for our local system and the
//port in question
bnd := svr.Bindings.Add;
bnd.IP := '127.0.0.1';
bnd.Port := Result;
//try to bind. This will throw an EIdCouldNotBindSocket
//exception if the port is already in use.
svr.Active := True;
//if we get here, we found our available port, so kill the
//server and bail
svr.Active := False;
Exit;
except
Inc(Result);
svr.Bindings.Clear;
end;
end;
//if we get here, all of our possible ports are in use,
//so return $FFFF to indicate that no port is available
Result := $FFFF;
finally
svr.Free;
end;
end;