Solved

For AvonWyss

Posted on 2001-07-02
35
253 Views
Last Modified: 2011-08-18
Here you go...

Post the code for UDP Streaming here. Thanks again!
0
Comment
Question by:DelFreak
  • 19
  • 16
35 Comments
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6248156
I will.
0
 

Author Comment

by:DelFreak
ID: 6257140
Hi! Any development?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6257410
Yes, but I don't think I'll be able to finish, test and post if before sometime next week. There will be some more code than in the TCP example since a simple version of packet resending and such will also be implemented to give you somewhat reliance to your UDP data streaming.
0
 

Author Comment

by:DelFreak
ID: 6279443
Still waiting...
0
 
LVL 14

Accepted Solution

by:
AvonWyss earned 300 total points
ID: 6281075
EE has been trashing my first posting on the subject. I really hope they will finally get this site fixed, I had to retype the complete comment.

Anyways, here's the UDP server and client. For your convenience, the server does not use TList or TStringList. The server stores up to 256 blocks in a ring buffer and thus allows resends to the clients requesting a packet to be resent. Since UDP is connectioj-less, two client-server packed messages have also been introduced to subscribe or unsubscribe clients. The server will display any packet received from a client.


program UDPPacketServer;

{$APPTYPE CONSOLE}

uses
  Windows,
  WinSock,
  SysUtils,
  Classes;

// basic classes, threads

type
      PInteger=^Integer;
      TPacket=packed record
            Serial,Length: Longint;
            Data: packed record end;
      end;
      PPacket=^TPacket;
      TClientInfo=packed record
            LastReply: Cardinal;
            Address: TSockAddrIn;
      end;
      TPacketSender=class(TThread)
      private
            FQueue: array of PPacket;
            FClients: array of TClientInfo;
            FLock: TRTLCriticalSection;
            FListeningPort: Word;
            FSocket: TSocket;
            FSerial,FSendSerial,FPacketsSent: Integer;
            function GetPacket(ASerial: Integer): PPacket;
            procedure PutPacket(APacket: PPacket);
      protected
            procedure Execute; override;
      public
            constructor Create(ListeningPort: Word);
            procedure Send(var Data; Length: Cardinal);
            destructor Destroy; override;
      end;

function TicksElapsed(Since: Cardinal): Cardinal;
var
      Temp: Int64;
begin
      Temp:=Int64(GetTickCount)-Since;
      if Temp<0 then
            Inc(Temp,$100000000);
      Result:=Temp;
end;

function SockAddrIn(Addr: Integer; Port: Word): TSockAddrIn;
begin
      Result.sin_family:=AF_INET;
      Result.sin_port:=HToNS(Port);
      Result.sin_addr.S_addr:=Addr;
      FillChar(Result.sin_zero,SizeOf(Result.sin_zero),0);
end;

{ TPacketSender }

constructor TPacketSender.Create(ListeningPort: Word);
var
      WSAData: TWSAData;
begin
      SetLength(FQueue,256); //number of blocks to keep in queue
      InitializeCriticalSection(FLock);
      FListeningPort:=ListeningPort;
      WSAStartup($101,WSAData);
      inherited Create(False);
end;

destructor TPacketSender.Destroy;
begin
      Terminate;
      WaitFor;
      inherited;
      DeleteCriticalSection(FLock);
      WSACleanup;
end;

procedure TPacketSender.Execute;
const
      BufferSize=16378;
      TimeOut=60*1000; //60s timeout until nonreplying clients are dropped
var
      I,J: Integer;
      Addr: TSockAddrIn;
      Packet,Buffer: PPacket;
      Data: PChar;
      LastSendTick: Cardinal;
begin
      FSocket:=Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); // create new TCP socket, unbound and unconnected
      GetMem(Buffer,BufferSize); //allocate datagram buffer
      Data:=Pointer(@Buffer.Data);
      LastSendTick:=0;
      try
            I:=1;
            IOCtlSocket(FSocket,FIONBIO,I); // set socket to nonblocking mode. this allows to check for Terminated
            Addr:=SockAddrIn(INADDR_ANY,FListeningPort);
            Bind(FSocket,Addr,SizeOf(Addr)); // bind the socket to the specified address and port
            repeat
                  while FSendSerial<FSerial do begin //check for items queued to be sent
                        Packet:=GetPacket(FSendSerial); //retrieve next packet to be sent from queue
                        if not Assigned(Packet) then
                              Abort; //should never happen; would indicate a queue overflow (too many PutPacket calls in a row)
                        Inc(FSendSerial);
                        for I:=0 to Length(FClients)-1 do begin
                              Winsock.SendTo(FSocket,Packet^,Packet.Length,0,FClients[I].Address,SizeOf(TSockAddrIn)); //no error check, not really necessary here
                              Inc(FPacketsSent);
                        end;
                        LastSendTick:=GetTickCount;
                  end;
                  repeat
                        J:=SizeOf(Addr);
                        I:=RecvFrom(FSocket,Buffer^,BufferSize,0,Addr,J);
                        if I>0 then begin
                              (PChar(Buffer)+Buffer.Length)^:=#0;
                              Write(inet_ntoa(Addr.sin_addr),':',HToNS(Addr.sin_port),' ',Data); //show received packet
                              if Buffer.Serial>=0 then
                                    Write(' ',Buffer.Serial);
                              WriteLn;
                              I:=0;
                              while I<Length(FClients) do
                                    with FClients[I].Address do
                                          if (Addr.sin_port=sin_port) and (Addr.sin_addr.S_addr=sin_addr.S_addr) then //known client address and port?
                                                Break
                                          else
                                                Inc(I);
                              if I=Length(FClients) then begin //did an unknown sender contact us?
                                    if Data='SUBSCRIBE' then begin //does he want a subscription?
                                          SetLength(FClients,Length(FClients)+1); //add new client
                                          with FClients[High(FClients)] do begin
                                                Address:=Addr;
                                                LastReply:=GetTickCount;
                                          end;
                                    end;
                              end else begin //we know who sent the packet
                                    FClients[I].LastReply:=GetTickCount;
                                    if Data='UNSUBSCRIBE' then begin //remove from distribution?
                                          while I<High(FClients) do begin
                                                FClients[I]:=FClients[I+1];
                                                Inc(I);
                                          end;
                                          SetLength(FClients,High(FClients));
                                    end else if Data='RESEND' then begin //resend old packet?
                                          Packet:=GetPacket(Buffer.Serial);
                                          if Assigned(Packet) then
                                                Winsock.SendTo(FSocket,Packet^,Packet.Length,0,FClients[I].Address,SizeOf(TSockAddrIn));
                                    end;
                              end;
                        end else
                              Break;
                  until False;
                  if TicksElapsed(LastSendTick)<TimeOut then begin //check for timed out clients if we have sent a packet sometimes
                        J:=0;
                        for I:=0 to Length(FClients)-1 do begin
                              if J>0 then
                                    FClients[I-J]:=FClients[I]; //if any are found, remove them
                              Inc(J,Ord(TicksElapsed(FClients[I].LastReply)>TimeOut));
                        end;
                        if J>0 then begin
                              WriteLn(J,' clients timed out'); //output debug info
                              SetLength(FClients,Length(FClients)-J); //resize array after any deletion
                        end;
                  end;
                  Sleep(20); //all work done for now, wait some
            until Terminated;
      finally
            EnterCriticalSection(FLock);
            FreeMem(Buffer);
            CloseSocket(FSocket); //close listening socket
            for I:=0 to Length(FQueue)-1 do //delete all pending packets
                  FreeMem(FQueue[I]);
            LeaveCriticalSection(FLock);
      end;
end;

function TPacketSender.GetPacket(ASerial: Integer): PPacket;
begin
      Result:=FQueue[ASerial mod Length(FQueue)];
      if Assigned(Result) and (Result.Serial<>ASerial) then
            Result:=nil; //reset packet if serial is wrong
end;

procedure TPacketSender.PutPacket(APacket: PPacket);
var
      OldPacket: Pointer;
      Index: Integer;
begin
      EnterCriticalSection(FLock); //enter critical section
      try
            APacket.Serial:=FSerial; //set serial number of packets, so that a client can detect dropped packets
            Index:=APacket.Serial mod Length(FQueue); //index inside the queue
            OldPacket:=FQueue[Index]; //read pointer of packet which will be replaced in the queue
            FQueue[Index]:=APacket; //add packet to send queue
            Inc(FSerial); //increase packet serial number
      finally
            LeaveCriticalSection(FLock); //leave critical section
      end;
      if Assigned(OldPacket) then
            FreeMem(OldPacket);
end;

procedure TPacketSender.Send(var Data; Length: Cardinal);
var
      Packet: PPacket;
begin
      GetMem(Packet,Length+SizeOf(Packet^)); //allocate memory for packet
      Packet.Length:=Length+SizeOf(Packet^); //set length...
      Move(Data,Packet.Data,Length); //...and copy data
      PutPacket(Packet);
end;

var
      Listening: TPacketSender;
      S: string;

begin
      Listening:=TPacketSender.Create(1234);
      WriteLn('Empty line aborts.');
      repeat
            ReadLn(S);
            if S<>'' then
                  Listening.Send(Pointer(S)^,Length(S));
      until S='';
      Listening.Free;
end.


The client is now also multithreaded and allows messages to be sent to the server (apart from the automatically sent acknowledgment message). Packets are currently limited to 2040 bytres of usable data; this should however be no restriction since UDP is not designed to transfer large data blocks anyways. Optimal sizes are around 500 bytes for mixed networks and about 1400 bytes for ethernet networks.


program UDPPacketClient;

{$APPTYPE CONSOLE}

uses
      Windows,
      WinSock,
      SysUtils,
      Classes;

// basic classes, threads

type
      TPacket=packed record
            Serial,Length: Longint;
            Data: packed array[0..2039] of Char;
      end;
      TReceiveThread=class(TThread)
      public
            Finished: Boolean;
            procedure Execute; override;
      end;

var
      WSAData: TWSAData;
      ClientSocket: TSocket;
      S: string;
      Addr: TSockAddrIn;
      Thread: TReceiveThread;

procedure SendPacket(Data: string; Serial: Integer=-1);
var
      Packet: TPacket;
begin
      Packet.Length:=Length(Data)+8;
      Packet.Serial:=Serial;
      Move(Pointer(Data)^,Packet.Data,Length(Data));
      Send(ClientSocket,Packet,Packet.Length,0);
end;

procedure TReceiveThread.Execute;
var
      S: string;
      Packet: TPacket;
begin
      while Recv(ClientSocket,Packet,SizeOf(Packet),0)=Packet.Length do begin
            SetString(S,PChar(@Packet.Data),Packet.Length-8);
            SendPacket('ACK',Packet.Serial);
            WriteLn(Packet.Serial:6,': ',S);
      end;
      Finished:=True;
end;

begin
      WSAStartup($101,WSAData);
      ClientSocket:=Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); // create new UDP socket, unbound and unconnected
      Addr.sin_family:=AF_INET; // fill up TSockAddrIn structure with address to bind to
      Addr.sin_port:=HToNS(1234); //port
      Addr.sin_addr.S_addr:=Inet_Addr('127.0.0.1'); //ip address (no host name!)
      if Connect(ClientSocket,Addr,SizeOf(Addr))=0 then begin
            Thread:=TReceiveThread.Create(False);
            SendPacket('SUBSCRIBE');
            repeat
                  ReadLn(S);
                  if S<>'' then
                        try
                              SendPacket('RESEND',StrToInt(S));
                        except
                              SendPacket(S);
                        end;
            until Thread.Finished or (S='');
            SendPacket('UNSUBSCRIBE');
      end else
            WriteLn('Connect Error');
      CloseSocket(ClientSocket);
      WSACleanup;
end.


Note that the client does detect if the server is offline or unreachable. The server, however, will detect clients which have not acknowledged a message for two minutes (time can be changed if needed) and automatically unsubscribe the client.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6281922
Sorry, it should read: "Note that the client does NOT detect if the server is offline or unreachable."
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6290058
DelFreak,have you had time to evaluate my sample applications?
0
 

Author Comment

by:DelFreak
ID: 6292604
I'll check it today!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6307667
DelFreak, did it work as expected?
0
 

Author Comment

by:DelFreak
ID: 6311177
Actually, I've been busy these past weeks and haven't been able to check the code. I promise to do so by tomorrow. Sorry! Tomorrow. Promise.
0
 

Author Comment

by:DelFreak
ID: 6316402
Okay! I tried the code and it seems to work fine. However, I haven't completely reviewed the code. I'll leave this thread open until the end of the week in which I should have been able to review the code by then. Sorry for the delay. :-)
0
 

Author Comment

by:DelFreak
ID: 6316419
Also, I would like to know if you know anything about TCP Tunneling. Would it be possible to do this in Delphi? What exactly does this do? Thanks!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6319143
Tunneling:

Have a look at the OSI model, which is commonly used to describe networking layers.

http://whatis.techtarget.com/definition/0,,sid9_gci523729,00.html

Well, normal applications all operate on the upper layers; WinSock offers a layer 4 interface. However, tunnels operate on the lower layers, usually layer 2, because they have to operate transparently for the upper layers (IPv4 for instance, which is layer 3). See the following link for some explanations:

http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci213230,00.html

Because of this, tunneling is more operating system dependent; a driver offering layer 2 capabilities has to be installed on the system. The virtual driver itself cannot be written in Delphi, however, the virtual driver may be a very thin interface calling functions of a Delphi appliucation, DLL or COM object, allowing to have the main operation be executed in compiled Delphi code.
0
 

Author Comment

by:DelFreak
ID: 6336159
Hi AvonWyss! Sorry for the late response. Anyway, I have a few observations and questions...

1. RESEND Function doesn't work. I tried using it but the whole application halted when I did. It said an error in some vxd had occured. This happened in every occasion I tried it. Why?

2. If possible could you make it so that I could send data using the 2 functions you posted in the TCP thread (ArrayToString and StringToArray). This is so that I could be able to choose which particular packet I want resent. The command would then be "RESEND 3" instead of just "RESEND".

Thanks!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6339622
1. It worked fine on my testing environment: Windows 2000 SP2. If you could give me more information, such as which of the two application freezes (by running them on different PC's, the client would have to have another IP address set up to connect to), I'd maybe be able tzo look into it. Apart from thast, you may also want to check for updates/bugfixes for Windows at windowsupdate.microsoft.com since it does work with certain configurations.

2. You can use the two functions. However, in the case of the Resend, it does not make sense to use it; the packet which holds the RESEDn information does have a Serial number which specifies the packet to be resent.
0
 

Author Comment

by:DelFreak
ID: 6342955
1. I tried it on a WinMe box. The server froze and cause an error in *.vxd. I tried it at least 10 times and still the same error. My box was just updated so I don't think it has to do with the settings.

2. Yes but it would be better if I could specify which packet I want resent. Maybe you could modify the code with a function like this using the 2 functions or would this cause program instability?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6344183
1. Are you sure it's the server? Did the client run on a different machine?

2. You can specify the packet to be resent by changing the serial number. Using the other functions would not cause instability but they would make easy things complicated; the serial number would have to be converted to a string and back later, resulting in more complicated and slower code. So I don't see the point of doing this.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:DelFreak
ID: 6346983
1. Yes! It's the server. No, it was running on the same machine. I haven't tried it on a different machine yet since it froze on my machine.

2. Can the serial number be specified during run-time? I saw no implementation of this and therefore opted to use the 2 functions.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6347890
1. Sorry, but if the whole machine freezes, how can you be sure which application crashed? I just don't want to go and try to debug code (which by the way runs fine in my envirnoment) if I don't even know what app crashes. Since it does work on my PC's, I cannot do the test whether it's the server or not myself.

2. Of course. You can enter any number in the client (e.g. 3) and it will request this perticular packet to be resent by the server. If the packet does not exist, the request will be ignored. So, if you received, let's say, 5 packets from the server so far and enter "3" (without quotes) in the client, the server will resent packet 3.
0
 

Author Comment

by:DelFreak
ID: 6347925
1. ...since "it" froze on my machine. -> I'm sure it was the server. It froze, gave me a blue screen about *.vxd and then closed. Only the client was spared. I'll try and get the whole error message and post it later. What do you think caused it?

2. I'll try that later.

Also, I would really appreciate it if you could insert both ArrayToString and StringToArray functions into the code you posted so that I can use it and compare it with the original code you posted to see which works better for me. Thanks!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6348053
1. Try this:

instead of:

                    I:=RecvFrom(FSocket,Buffer^,BufferSize,0,Addr,J);
                    if I>0 then begin
                         (PChar(Buffer)+Buffer.Length)^:=#0;

use this code:

                    FillChar(Buffer^,BufferSize,0);
                    I:=RecvFrom(FSocket,Buffer^,BufferSize,0,Addr,J);
                    if I>0 then begin

2. If you man implementing the resend using the array functions instead of the serial method, no I will not, because it does not make sense as explained earlier.
0
 

Author Comment

by:DelFreak
ID: 6348187
1. I will try that later.

2. No, not for the Serial. For connection functions and parameters I intend to use. I would really appreciate it. Thanks!
0
 

Author Comment

by:DelFreak
ID: 6357936
AvonWyss, will you add the 2 functions to the code?
0
 

Author Comment

by:DelFreak
ID: 6366981
Please reply. Thank you!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6367264
1. Did you try the suggestion I gave you?

2. Why don't you try it yourself? You have all the code needed for this...
0
 

Author Comment

by:DelFreak
ID: 6367310
1. Yes. It seems to work fine now.

2. I would like to but you said I shouldn't make any changes to TPacket. I don't know where to place the functions if I can't touch the structure of TPacket. I would appreciate it very much if you would be so kind as to add the 2 functions. Thank you!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6367455
Ok. See this code from the server:

     repeat
          ReadLn(S);
          if S<>'' then
               Listening.Send(Pointer(S)^,Length(S));
     until S='';

It reads a line into a string and sends it to the clients. Instead of ReadLn, you can use ANY string (or data stored in a string). Because of this, you can use the function supplied to convert an array holding any set of strings to a single string which can be sent exactly as the string in the current code.


The client displays the text here:

     while Recv(ClientSocket,Packet,SizeOf(Packet),0)=Packet.Length do begin
          SetString(S,PChar(@Packet.Data),Packet.Length-8);
          SendPacket('ACK',Packet.Serial);
          WriteLn(Packet.Serial:6,': ',S);
     end;

Note that, just prior to the WriteLn line, you can convert the received string back to an array using the other supplied function, replacing the WriteLn line.
0
 

Author Comment

by:DelFreak
ID: 6371665
Thank you! I'm sorry for being a pest. You must be really irritated with me. Anyway, thanks again! I'll try that out now.
0
 

Author Comment

by:DelFreak
ID: 6382725
AvonWyss, A few more questions.

1. When I use the resend function... if I type in the packet number it works fine but when I type in "RESEND", the error occurs. Why?

2. Can I use this code for "Video Streaming"? If not, how can I make so that "Video Streaming" is possible?

Thanks!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6383027
1. Because, when you enter any text without specifying a serial, a serial of -1 will be used. This however will make an access violation here:

function TPacketSender.GetPacket(ASerial: Integer): PPacket;
begin
     Result:=FQueue[ASerial mod Length(FQueue)];
     if Assigned(Result) and (Result.Serial<>ASerial) then
          Result:=nil; //reset packet if serial is wrong
end;

since -1 mod somethig will still be negative. SO, in Order to avoid this, use this function:

function TPacketSender.GetPacket(ASerial: Integer): PPacket;
begin
     if ASerial>=0 then begin
          Result:=FQueue[ASerial mod Length(FQueue)];
          if Assigned(Result) and (Result.Serial<>ASerial) then
               Result:=nil; //reset packet if serial is wrong
     end else
          Result:=nil;
end;


2. This does not depent on the streaming code, but on the data stream used. MPeg and other frame-based video streams could be used just fine by just sending single frames or frame fragments over the connection.
0
 

Author Comment

by:DelFreak
ID: 6455030
AvonWyss, Will this code be able to do file transfers? Do you know how file transfers can be done so that they can be resumed if the connections is interrupted?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6457992
DelFreak, honest, don't you think that I did do enough for these 300 points? Why should I keep on writing lots of stuff for you if you practically never just accept an answer which has been given to a question of yours? It's been about EIGHT WEEKS since I posted the code for UDP. You can't be serious about this.
0
 

Author Comment

by:DelFreak
ID: 6459109
Hmmm... I'm sorry! I thought I accepted your answer when I posted that comment. I'm really sorry! I've been busy lately. m Anyway, I'm not asking for more code. I just want to know how file transfers can be done. I'll post another question regarding that. Again, I'm sorry! Thanks for the great code.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6486005
DelFreak, thank you for the points!

I haven't seen the other question, what is it called?
0
 

Author Comment

by:DelFreak
ID: 6486247
I haven't posted one yet. I don't have another 300 points to give. :(

When I get the points, I'll notify you.
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Suggested Solutions

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

23 Experts available now in Live!

Get 1:1 Help Now