Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Sending Byte List
#1
I need to send an array of bytes to client connected to TIdTCPServer, I try with:

Code:
procedure TFServer.btnSendClick(Sender: TObject);
var
   tmpList      : TList;
   contexClient : TidContext;
   i            : integer;
begin
   try
       tmpList := IdTCPServer.Contexts.LockList;
   finally
       IdTCPServer.Contexts.UnlockList;
   end;
   i := 0;
   while ( i < tmpList.Count ) do begin
       contexClient := tmpList[i];
       contexClient.Connection.IOHandler.WriteBufferOpen;
       try
         contexClient.Connection.IOHandler.WriteLn(#$ea#$89#$ef);
       except
         contexClient.Connection.IOHandler.WriteBufferCancel;
       end;
       contexClient.Connection.IOHandler.WriteBufferClose;
       i := i + 1;
   end;
end;

and I get memory error I try few scenarios with write but same error is here :-(

Is there some solution to send bytes, because another site is hardware with firmware who need array of bytes in TCP connection.

Thanks for the help and assistance in the advance...
Reply
#2
(08-22-2018, 07:19 PM)krshin Wrote: I get memory error I try few scenarios with write but same error is here :-(

You need to keep the TIdTCPServer.Contexts list locked while you iterate through it, otherwise clients are free to (dis)connect from/to the server, modifying the contents of the list, while you are accessing it.

(08-22-2018, 07:19 PM)krshin Wrote: Is there some solution to send bytes, because another site is hardware with firmware who need array of bytes in TCP connection.

To send a byte array, use the TIdIOHandler.Write(TIdBytes) method instead of the TIdIOHandler.WriteLn(string) method.  Also, TIdIOHandler.WriteLn() is subject to charset encoding, so you are going to corrupt your bytes sending them as a string anyway.

Try this:

Code:
procedure TFServer.btnSendClick(Sender: TObject);
var
 bytes: TIdBytes;
 tmpList: TList;
 contexClient: TIdContext;
 i: Integer;
begin
 SetLength(bytes, 3);
 bytes[0] := $ea;
 bytes[1] := $89;
 bytes[2] := $ef;

 tmpList := IdTCPServer.Contexts.LockList;
 try
   for i := 0 to tmpList.Count-1 do
   begin
     contexClient := TIdContext(tmpList[i]);
     if contexClient <> nil then
     try
       contexClient.Connection.IOHandler.Write(bytes);
     except
     end;
   end;
 finally
   IdTCPServer.Contexts.UnlockList;
 end;
end;

That being said, if your TIdTCPServer.OnExecute event handler also sends any data to clients, then this approach becomes very dangerous, as you risk inter-mixing your bytes with other data that OnExecute may send, corrupting your communications.  In which case, the safer approach is to delegate the sending of the bytes to the OnExecute event handler, and let it decide when it is safe to send the bytes.  For example:

Code:
procedure TFServer.bIdTCPServerConnect(AContext: TIdContext);
begin
 AContext.Data := TIdThreadSafeList.Create;
end;

procedure TFServer.IdTCPServerDisconnect(AContext: TIdContext);
var
 Data: TIdThreadSafeList;
 tmpList: TList;
 i: Integer;
 P: Pointer;
begin
 Data := TIdThreadSafeList(AContext.Data);
 AContext.Data := nil;
 try
   tmpList := Data.LockList;
   try
     for i := 0 to tmpList.Count-1 do
     begin
       P := tmpList[i];
       TIdBytes(P) := nil;
     end;
   finally
     Data.UnlockList;
   end;
 finally
   Data.Free;
 end;
end;

procedure TFServer.bIdTCPServerExecute(AContext: TIdContext);
var
 Data: TIdThreadSafeList;
 tmpList: TList;
 bytes: TIdBytes;
begin
 ...
 Data := TIdThreadSafeList(AContext.Data);
 tmpList := Data.LockList;
 try
   while tmpList.Count > 0 do
   begin
     bytes := TIdBytes(tmpList[0]);
     tmpList.Delete(0);
     AContext.Connection.IOHandler.Write(bytes);
     bytes := nil;
   end;
 finally
   Data.UnlockList;
 end;
 ...
end;

procedure TFServer.btnSendClick(Sender: TObject);
var
 tmpList: TList;
 contexClient: TidContext;
 i: integer;
 bytes: TIdBytes;
begin
 tmpList := IdTCPServer.Contexts.LockList;
 try
   for i := 0 to tmpList.Count-1 do
   begin
     contexClient := TIdContext(tmpList[i]);
     if (contexClient <> nil) and (contexClient.Data <> nil) then
     begin
       SetLength(bytes, 3);
       bytes[0] := $ea;
       bytes[1] := $89;
       bytes[2] := $ef;
       TIdThreadSafeList(contexClient.Data).Add(Pointer(bytes));
       Pointer(bytes) := nil;
     end;
   end;
 finally
   IdTCPServer.Contexts.UnlockList;
 end;
end;

Reply
#3
Hello rlebeau,

Thank you for very detail sample and assistance :-)

I have only one problem when I send this array of bytes everything goes fine at the client hardware side, and I need to get some response from that clients over an open channel of communication, so how to get and read this response in this case?

Thanks for the help and assistance in the advance...
Reply
#4
(08-23-2018, 05:52 AM)krshin Wrote: I have only one problem when I send this array of bytes everything goes fine at the client hardware side, and I need to get some response from that clients over an open channel of communication, so how to get and read this response in this case?

If you know a response will arrive quickly (and you are not delegating the send to the OnExecute event, like I described), then you could simply read the response immediately in your btnSendClick() loop using the same IOHandler that you use to send the command, eg:

Code:
procedure TFServer.btnSendClick(Sender: TObject);
var
  bytes: TIdBytes;
  tmpList: TList;
  contexClient: TIdContext;
  i: Integer;
begin
  SetLength(bytes, 3);
  bytes[0] := $ea;
  bytes[1] := $89;
  bytes[2] := $ef;

  tmpList := IdTCPServer.Contexts.LockList;
  try
    for i := 0 to tmpList.Count-1 do
    begin
      contexClient := TIdContext(tmpList[i]);
      if contexClient <> nil then
      try
        contexClient.Connection.IOHandler.Write(bytes);
        // read response here...
      except
        contexClient.Connection.Disconnect;
      end;
    end;
  finally
    IdTCPServer.Contexts.UnlockList;
  end;
end;

However, it is generally best NOT to wait for responses in loops like that, especially loops running in the UI. You should instead read the response in the OnExecute event (especially if you are delegating sends to OnExecute).

Now, HOW you read a response from a client's IOHandler depends on the particular format of the response. Since you have not provided any details about the format of the communication protocol you are using, I can't help you with that particular coding. But TIdIOHandler has MANY reading methods available, for things like strings, integers, streams, byte arrays, etc.

Reply
#5
OK, this is great. I've been struggling and had arrive at a similar method to send a byte array... thinking I had failed I vasilate between trying Indy & the old reliable Piette's sockets but I want to use Indy- then suddenly my example streamed a response so there is hope. unfortunately I am a struggling newbie to Indy and an intermediate Delphi guy.

I created a dynamic array;
 TxBuffer : TIdBytes;
Sized it;
    SetLength(TxBuffer,6);

I loaded it with pointers, I see now I didn't have to do that anyway...

I sent the bytes like this but I don't think it's right?:

IdTCPClient.IOHandler.Write(@TxBuffer[0],7);

I need to receive a byte array. The protocol is a lightweight datalink protocol, framed by simple headers (Start of Frame byte, inverted payload length, payload length, (Payload is a packed record structure of bytes and words, then an array of words) followed by two bytes of the 'Modbus' CRC, which I am calculating fine.

Could someone please give me a starting point for the IOHandler to receive a buffer of bytes? It's not big, about 46 bytes right now though it will be bigger. Ultimately it will stream at a fair speed for monitoring the data.
Reply
#6
(08-29-2018, 11:43 AM)CaptRick Wrote: I sent the bytes like this but I don't think it's right?:

IdTCPClient.IOHandler.Write(@TxBuffer[0],7);

No, it is not. Use this instead, which sends the entire array as-is:

Code:
IdTCPClient.IOHandler.Write(TxBuffer);

Or, you can use this, which lets you specify how many bytes to send (ie, if you had a larger array that you wanted to send a smaller portion of):

Code:
IdTCPClient.IOHandler.Write(TxBuffer, 7);

(08-29-2018, 11:43 AM)CaptRick Wrote: I need to receive a byte array. The protocol is a lightweight datalink protocol, framed by simple headers (Start of Frame byte, inverted payload length, payload length, (Payload is a packed record structure of bytes and words, then an array of words) followed by two bytes of the 'Modbus' CRC, which I am calculating fine.

Good. Then you can simply read the header first, then read however many bytes the header says are in the payload. The IOHandler has many methods that would be suitable for this task - ReadByte(), ReadBytes(), ReadInt16/32/64(), etc (and conversely, equivalents for sending data).

Reply
#7
Perfect, Thanks rlebeau!

IdTCPClient.IOHandler.Write(TxBuffer, 7); worked perfectly, once I fixed the resize to 7 too... and readbytes() worked on the thread component run procedure. I am now able to read all 46 at once, then parse as appropriate. Off and running...
Reply
#8
Geez, every time I think I've got it...something doesn't behave like I thought. Indy Sockets so far have already made my life so much cleaner but the closer I get, the further away I seem.

I'm starting with the MicroClient example in Delphi 10.2 Tokyo.
// File : UClient.pas
// Project : MicroServer.dpr
// Easy example of TCP Client with indy component : TidTCPSever
//
// see indy doc: http://www.indyproject.org/sockets/docs/index.en.aspx

I've relegated my receive code to the thread onRun event TFClient.IdThreadComponentRun, otherwise I could use it to receive one series at a time, but they prefer firing them at me so I need to receive full buffers of bytes containing strain gauge data in quick intervals. There will be ~175 gauges, currently I'm testing with 11 copies of the same one. If I read 49 bytes at once for my test case it works fine. When I stopped to read the header first last night I found it triggers the onRun event if there is any more data in the buffer, so then I did an experiment reading one byte at a time and displaying it in the TFClient.IdThreadComponentRun onRun event when it fires and sure enough, this will read the entire buffer perfectly.

That's cool and could maybe be the way to go, but if I do that I don't know when it's finished and I can process the data.

Any way to really know how many bytes have been received in the IOHandler?

When I use SizeOf(RxBuffer) it's always 4 so it does a series of 4 till it's exhausted the incoming buffer. So then I thought OK, I just need to read it all. I'm getting garbage in my Uint16's- this could be an endian issue, I'm not certain yet because I don't program full time. Can I use the HostToLittleEndian function from Indy? I have endian changing routines but if it's 'already in there' why do that. When I read it by bytes I get $0B in the first byte (lines 10&11 below) which is 11 sensors if I decode just that byte.

Here is the series of bytes I receive currently;

1 - A5 Frame Start
2 - F3 255- Payload length = 243
3 - 0C 12, payload size
4 - A3 WEPACKETID_SENSORRESULTSFIRST_RES
5 - 00 zero
6 - CD 205
7 - 00 Status
8 - 00 Num Te mp sensors
9 - 00 Num Temp sensors
10 - 0B 11, corect Num Strain Sensors
11 - 00 Num Strain Sensors
12 - 00 Temp Sensor index
13 - 00 Temp Sensor index
14 - 00 Temp Sensor Result
15 - 00 Temp Sensor Result
16 - 76 CRC
17 - 7A CRC
18 - A5 Frame start
19 - E4 228
20 - 1B 27 checks out
21 - A4 Sensor results others
22 - 00 zero
23 - CD 205
24 - 00 u16StrainSensorIndex
25 - 00 u16StrainSensorIndex
26 - 73 u16Strain result
27 - 17
28 - 74 u16Strain result
29 - 17
30 - 74 u16Strain result
31 - 17
32 - 74 u16Strain result
33 - 17
34 - 74 u16Strain result
35 - 17
36 - 75 u16Strain result
37 - 17
38 - 75 u16Strain result
39 - 17
40 - 77 u16Strain result
41 - 17
42 - 77 u16Strain result
43 - 17
44 - 77 u16Strain result
45 - 17
46 - 76 u16Strain result
47 - 17
48 - DE CRC
49 - B2 CRC

Pretty sure now the garbage part is an endian issue. More later since I have a full time day job too...
Reply
#9
(08-31-2018, 10:38 AM)CaptRick Wrote: I've relegated my receive code to the thread onRun event TFClient.IdThreadComponentRun, otherwise I could use it to receive one series at a time, but they prefer firing them at me so I need to receive full buffers of bytes containing strain gauge data in quick intervals. There will be ~175 gauges, currently I'm testing with 11 copies of the same one. If I read 49 bytes at once for my test case it works fine. When I stopped to read the header first last night I found it triggers the onRun event if there is any more data in the buffer, so then I did an experiment reading one byte at a time and displaying it in the TFClient.IdThreadComponentRun onRun event when it fires and sure enough, this will read the entire buffer perfectly.

OnRun is a looped event. As soon as it exits, it is triggered again, over and over, for the lifetime of the thread. It is not dependent on socket I/O.

(08-31-2018, 10:38 AM)CaptRick Wrote: That's cool and could maybe be the way to go, but if I do that I don't know when it's finished and I can process the data.

That is why you should be reading a full message on each triggering of the OnRun event. Read a full header, read its full payload, process, exit. Repeat.

(08-31-2018, 10:38 AM)CaptRick Wrote: Any way to really know how many bytes have been received in the IOHandler?

Technically yes (see the IOHandler.InputBuffer.Size property), but that is not the way you should be approaching this situation. Your messages have structure to them. Code for that structure.

(08-31-2018, 10:38 AM)CaptRick Wrote: When I use SizeOf(RxBuffer) it's always 4

That means your RxBuffer is likely a dynamic array, so it is effectively just a pointer to data stored elsewhere in memory. A pointer has a fixed size (4 bytes when compiling for 32bit).

(08-31-2018, 10:38 AM)CaptRick Wrote: I'm getting garbage in my Uint16's- this could be an endian issue, I'm not certain yet

Most likely, yes. Most platforms that Delphi supports are little endian systems, but Indy assumes network byte order (big endian) by default, as most network protocols use that endian for cross-platform compatibility. It is a common standard. TIdIOHandler's reading and sending methods for integers have an optional AConvert parameter that is set to True by default, so they perform conversions between network endian and host endian for you.

Since you are sending data between TIdTCPServer and TIdTCPClient, things should "just work" out of the box. Unless you are not using TIdIOHandler's integer sending methods, but are instead receiving data from an external source and just forwarding it as-is. In which case, if the UInt16s are using the same endian as the receiver (check the source's documentation), you can set the AConvert parameter to False when reading the UInt16s.

(08-31-2018, 10:38 AM)CaptRick Wrote: Can I use the HostToLittleEndian function from Indy?

That function is for sending, not for reading. You would have to use LittleEndianToHost() instead. But that is moot if you use the AConvert parameter of TIdIOHandler.ReadUInt16() instead.

(08-31-2018, 10:38 AM)CaptRick Wrote: I have endian changing routines but if it's 'already in there' why do that.

Exactly, which is why the AConvert parameter exists.

(08-31-2018, 10:38 AM)CaptRick Wrote: When I read it by bytes I get $0B in the first byte (lines 10&11 below) which is 11 sensors if I decode just that byte.

Bytes 0B 00 is decimal 11 as a (U)Int16 in little endian.

On lines 2 and 3, you have "Payload length" and "payload size". Assuming those 2 bytes are actually a single (U)Int16, then bytes F3 0C would be decimal 3315 in little endian. Does that look like a reasonable payload size for your messsaging?

Reply
#10
Thanks so much this is so great! 'Looped event' explains a lot... Forgive me for newbie questions, I've excitedly adopted Indy Sockets for a whole 3 days now... What you have said means I am on the right track, and very nearly there already. I coded a read method to read the number of sensors get all the data, no matter how big, including the last CRC. The thing is the inverted payloads and payload sizes only refer to the two records/structures in packetized headers, one is only two bytes the other only 5 with most of the variables just one byte, using the structure to decode it seems irrelevant to me.

No, 3315 is not reasonable, the payload bytes are separate bytes with the inverted one being 255 - payloadsize, the other a byte with the size.

I will see about the IOHandler.InputBuffer.Size property, though I have the code written it would seem easier to read the buffer in it's entirety then deal with the byte array.

the AConvert parameter, well that's brilliant! I've read about so many things that would make life really easy for me. Normally we use Records and pointers to records as a 'magic decoder ring' for the buffer, but on dedicated LAN's we use UDP, not TCP and the records are much more complex usually. I had to deal with endian conversion every time because we were dealing with Motorola processors on the other end, but it was years ago.

I'm not actually using the server of this example but receiving from a device.

I have really hit the timing hard, but it's still possible I'm reading the wrong two bytes for the number of sensors, which is the payload that counts. they occur as an array immediately following the second structure.

I was playing with AConvert suspecting that might be an endian swapping deal by setting it to true, maybe I made it Big endian and gave myself the headache... I thought I undid it later, unless it's set to True by default since I never set it explicitly to False. If that is true I can fix it in 10 minutes or less!

I also tried to buy the E-book on Indy, but could not figure out how to pay for and download it.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)