Recent

Author Topic: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.  (Read 16084 times)

guest60499

  • Guest
Program input: "a\nb\n" from netcat. If the OnStatus output is removed all that is seen is a blank line each time a line is entered in netcat. Any suggestions? Code needs a complete project and Synapse but everything is the default except form name.

Code: Pascal  [Select][+][-]
  1. unit form;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  9.   blcksock;
  10.  
  11. type
  12.   TTCPClient = class(TThread)
  13.   private
  14.     FHost: String;
  15.     FPort: Integer;
  16.     Socket: TTCPBlockSocket;
  17.   protected
  18.     procedure Execute; override;
  19.     procedure SocketStatusHandler(Sender: TObject; Reason: THookSocketReason;
  20.                                   const Value: AnsiString);
  21.   public
  22.     constructor Create(CreateSuspended: Boolean;
  23.                        const StackSize: SizeUInt=DefaultStackSize);
  24.     property Host: String read FHost write FHost;
  25.     property Port: Integer read FPort write FPort;
  26.   end;
  27.  
  28.   { TMainForm }
  29.  
  30.   TMainForm = class(TForm)
  31.     procedure FormCreate(Sender: TObject);
  32.   private
  33.     TCPClient: TTCPClient;
  34.   public
  35.   end;
  36.  
  37. var
  38.   MainForm: TMainForm;
  39.  
  40. implementation
  41.  
  42. {$R *.lfm}
  43.  
  44. { TTCPClient }
  45.  
  46. procedure TTCPClient.Execute;
  47. begin
  48.   Socket.Connect(FHost, IntToStr(FPort));
  49.  
  50.   repeat
  51.     while Socket.CanRead(1000) do
  52.     begin
  53.       WriteLn(Socket.RecvByte(1)); // Socket.RecvString(1);
  54.     end;
  55.   until False;
  56. end;
  57.  
  58. procedure TTCPClient.SocketStatusHandler(Sender: TObject;
  59.                                          Reason: THookSocketReason;
  60.                                          const Value: AnsiString);
  61. var
  62.   Status: String;
  63. begin
  64.   case Reason of
  65.     HR_ResolvingBegin:
  66.       Status := 'ResolvingBegin';
  67.     HR_ResolvingEnd:
  68.       Status := 'ResolvingEnd';
  69.     HR_SocketCreate:
  70.       Status := 'SocketCreate';
  71.     HR_SocketClose:
  72.       Status := 'SocketClose';
  73.     HR_Connect:
  74.       Status := 'Connect';
  75.     HR_CanRead:
  76.       Status := 'CanRead';
  77.     HR_CanWrite:
  78.       Status := 'CanWrite';
  79.     HR_ReadCount:
  80.       Status := 'ReadCount';
  81.     HR_WriteCount:
  82.       Status := 'WriteCount';
  83.     HR_Wait:
  84.       Status := 'Wait';
  85.     HR_Error:
  86.       Status := 'Error';
  87.   end;
  88.   WriteLn(Status, ', ', Value);
  89. end;
  90.  
  91. constructor TTCPClient.Create(CreateSuspended: Boolean;
  92.                               const StackSize: SizeUInt=DefaultStackSize);
  93. begin
  94.   inherited Create(CreateSuspended, StackSize);
  95.   Socket := TTCPBlockSocket.Create;
  96.   Socket.OnStatus := @Self.SocketStatusHandler;
  97. end;
  98.  
  99. { TMainForm }
  100.  
  101. procedure TMainForm.FormCreate(Sender: TObject);
  102. begin
  103.   TCPClient := TTCPClient.Create(True);
  104.   TCPClient.Host := 'localhost';
  105.   TCPClient.Port := 2000;
  106.   TCPClient.Start;
  107. end;
  108.  
  109. end.
  110.  

With Socket.RecvByte:

Code: [Select]
ResolvingBegin, localhost:2000
ResolvingEnd, 127.0.0.1:2000
SocketCreate, IPv4
Connect, localhost:2000
CanRead,
ReadCount, 2
97
CanRead,
10
CanRead,
ReadCount, 2
98

With Socket.RecvString:

Code: [Select]
ResolvingBegin, localhost:2000
ResolvingEnd, 127.0.0.1:2000
SocketCreate, IPv4
Connect, localhost:2000
CanRead,
ReadCount, 2
Error,  110, Connection timed out
Error,  110, Connection timed out

ReadCount, 2
Error,  110, Connection timed out
Error,  110, Connection timed out

rvk

  • Hero Member
  • *****
  • Posts: 7026
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #1 on: January 28, 2017, 12:13:53 am »
Shouldn't you at least build in some breathing room in that execute procedure.
Now it will suck the CPU dry because it's in a constant loop.
(maybe the onStatus gives it some room but without it the loop might be too CPU-costly)

Code: Pascal  [Select][+][-]
  1. procedure TTCPClient.Execute;
  2. begin
  3.   Socket.Connect(FHost, IntToStr(FPort));
  4.   while not terminated do
  5.     while Socket.CanRead(1000) do
  6.     begin
  7.       WriteLn(Socket.RecvString(1));
  8.       Sleep(100);
  9.     end;
  10.     Sleep(500);
  11.   end;
  12. end;

guest60499

  • Guest
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #2 on: January 28, 2017, 09:51:00 pm »
Shouldn't you at least build in some breathing room in that execute procedure.
Now it will suck the CPU dry because it's in a constant loop.
(maybe the onStatus gives it some room but without it the loop might be too CPU-costly)

Code: Pascal  [Select][+][-]
  1. procedure TTCPClient.Execute;
  2. begin
  3.   Socket.Connect(FHost, IntToStr(FPort));
  4.   while not terminated do
  5.     while Socket.CanRead(1000) do
  6.     begin
  7.       WriteLn(Socket.RecvString(1));
  8.       Sleep(100);
  9.     end;
  10.     Sleep(500);
  11.   end;
  12. end;

The numbers in the calls to CanRead and RecvString are timeouts, not lengths - if you set them to zero the program will utilize all of your CPU but when set to a nonzero value they block for the specified length before returning. This was kind of confusing when I encountered it because it makes it impossible to write a strictly blocking implementation of a network handling thread.

I did a slightly more extensive test of RecvByte and it seemed to work properly in all cases. When I get more time I will test them all, but if anyone knows a solution I would be extremely grateful. Also: OnStatus seems to only be run when a method of an instantiated TTCPBlockSocket is called which makes it hard to do asynchronous work with the class. Does anyone know the proper way to do this?

It's also possible I posted this in the wrong forum - if someone knows a better place for this feel free to report it so it can be moved. Thanks.

rvk

  • Hero Member
  • *****
  • Posts: 7026
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #3 on: January 28, 2017, 10:37:25 pm »
The numbers in the calls to CanRead and RecvString are timeouts, not lengths - if you set them to zero the program will utilize all of your CPU but when set to a nonzero value they block for the specified length before returning.
They don't block for that specific length of time. They only block for that time when there is no data received.

Either way... I do see the reason why your RecvString(1) doesn't work and times out. First... the 1 might be just too small. It doesn't matter if you use 100 or 10000 here because you know there is data and you are expecting a string. But do make it something bigger than 1ms... 1ms is really fast and the string might not even be transferred in that small time.

But the second problem is your string. RecvString() expects a CR+LF at the end of the string. If there is no CR+LF it will wait the given timeout until it does get that CR+LF. You are sending "a\nb\n" with netcat. That's only the LF (you could see that by the 10 you received). No CR is send. So you'll need to send "a\r\nb\r\n" for the RecvString() to receive the string.

Another option is to use
s := RecvTerminated(Timeout, LF);
This will receive a string with LF as terminator.

If this was not your original problem/question... I'm not sure what was.
« Last Edit: January 28, 2017, 10:38:58 pm by rvk »

guest60499

  • Guest
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #4 on: January 29, 2017, 09:42:13 pm »
The numbers in the calls to CanRead and RecvString are timeouts, not lengths - if you set them to zero the program will utilize all of your CPU but when set to a nonzero value they block for the specified length before returning.
They don't block for that specific length of time. They only block for that time when there is no data received.

Either way... I do see the reason why your RecvString(1) doesn't work and times out. First... the 1 might be just too small. It doesn't matter if you use 100 or 10000 here because you know there is data and you are expecting a string. But do make it something bigger than 1ms... 1ms is really fast and the string might not even be transferred in that small time.

The loop handles a timeout properly and nothing is printed. A blank line is printed only when a line is sent due to line buffering in netcat.

But the second problem is your string. RecvString() expects a CR+LF at the end of the string. If there is no CR+LF it will wait the given timeout until it does get that CR+LF. You are sending "a\nb\n" with netcat. That's only the LF (you could see that by the 10 you received). No CR is send. So you'll need to send "a\r\nb\r\n" for the RecvString() to receive the string.

Another option is to use
s := RecvTerminated(Timeout, LF);
This will receive a string with LF as terminator.

If this was not your original problem/question... I'm not sure what was.

It seems to accept a string terminated with #10, but I will post the results when I am able. If the library uses sLineBreak or a similar conditionally defined constant then it will work, which it seems to be doing.

rvk

  • Hero Member
  • *****
  • Posts: 7026
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #5 on: January 29, 2017, 09:53:27 pm »
If the library uses sLineBreak or a similar conditionally defined constant then it will work, which it seems to be doing.
Synapse defenitly doesn't use sLineBreak. It uses CRLF (an internal constant of CR + LF). There is however a setting ConvertLineEnd which can convert the LF internally to CRLF so a line-ending can be detected.

See http://synapse.ararat.cz/doc/help/blcksock.TBlockSocket.html#RecvString
and http://synapse.ararat.cz/doc/help/blcksock.TBlockSocket.html#ConvertLineEnd

So when you set ConvertLineEnd to true (it's false by default) internally a received #10 will be translated to #13#10 so the CRLF is detected correctly.

guest60499

  • Guest
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #6 on: January 30, 2017, 03:57:40 pm »
If the library uses sLineBreak or a similar conditionally defined constant then it will work, which it seems to be doing.
Synapse defenitly doesn't use sLineBreak. It uses CRLF (an internal constant of CR + LF). There is however a setting ConvertLineEnd which can convert the LF internally to CRLF so a line-ending can be detected.

See http://synapse.ararat.cz/doc/help/blcksock.TBlockSocket.html#RecvString
and http://synapse.ararat.cz/doc/help/blcksock.TBlockSocket.html#ConvertLineEnd

So when you set ConvertLineEnd to true (it's false by default) internally a received #10 will be translated to #13#10 so the CRLF is detected correctly.

Thanks, this was exactly the issue. I will look into advising the Synapse author about it. Though I admittedly did not refer to the documentation enough, the behavior when receiving only a newline is rather confusing.

rvk

  • Hero Member
  • *****
  • Posts: 7026
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #7 on: January 30, 2017, 04:01:21 pm »
Unfortunately my version of the library (SVN Trunk) doesn't have the property you mentioned.
You mean that TTCPBlockSocket from SVN trunk doesn't have ConvertLineEnd ???
That's really strange because mine does have it.

Are you sure you're trying it for the correct object (i.e. TTCPBlockSocket) ?

(It's also in the source https://svn.code.sf.net/p/synalist/code/trunk/blcksock.pas)

guest60499

  • Guest
Re: [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #8 on: January 30, 2017, 05:45:05 pm »
Unfortunately my version of the library (SVN Trunk) doesn't have the property you mentioned.
You mean that TTCPBlockSocket from SVN trunk doesn't have ConvertLineEnd ???
That's really strange because mine does have it.

Are you sure you're trying it for the correct object (i.e. TTCPBlockSocket) ?

(It's also in the source https://svn.code.sf.net/p/synalist/code/trunk/blcksock.pas)

You're quick! I edited that line out. Unfortunately I was mistaken; I was looking in the namespace of the class I created containing the TTCPBlockSocket. That class does have it as it should and setting it has solved the problem (I can also use netcat's -C option). I also forgot to realize that reading an empty string is probably an error condition, though in the case of an improperly terminated line there is no error reported.

Notably setting ConvertLineEnd works regardless of whether the sender terminates lines with CR or CRLF. I'm wondering why it's even an option.

EDIT: Why this is confusing is that RecvString may return an empty string when timing out.

EDIT: To detect an error condition the following is necessary (unless using the callbacks, which are not appropriate for all situations). Using only the inner loop will cause the thread to exit the first time the call to RecvString times out.
Code: Pascal  [Select][+][-]
  1. procedure TTCPClient.Execute;
  2. label
  3.   ExitLoop;
  4. var
  5.   Line: String;
  6. begin
  7.   Socket.Connect(FHost, IntToStr(FPort));
  8.  
  9.   repeat
  10.     while Socket.CanRead(1000) do
  11.     begin
  12.       Line := Socket.RecvString(1);
  13.       if Line = '' then
  14.          goto ExitLoop;
  15.       WriteLn(Line);
  16.     end;
  17.   until False;
  18.  
  19. ExitLoop:
  20.   WriteLn('Done.');
  21. end;
  22.  

Cyrax

  • Hero Member
  • *****
  • Posts: 836
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #9 on: January 30, 2017, 06:18:33 pm »
Do not use labels and gotos! Use break to exit cleanly from loops.

guest60499

  • Guest
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #10 on: January 30, 2017, 08:55:22 pm »
Do not use labels and gotos! Use break to exit cleanly from loops.

Poorly used gotos are bad, but this is one of the cases where their use is recommended. Putting the loop bodies into functions or using flag variables makes the code less concise. The functionality goto provides in this instance is the same as a labeled loop, which is available in Java and was actually suggested before.

Cyrax

  • Hero Member
  • *****
  • Posts: 836
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #11 on: January 30, 2017, 10:33:20 pm »
Do not use labels and gotos! Use break to exit cleanly from loops.

Poorly used gotos are bad, but this is one of the cases where their use is recommended. Putting the loop bodies into functions or using flag variables makes the code less concise. The functionality goto provides in this instance is the same as a labeled loop, which is available in Java and was actually suggested before.

I don't see any suggestions of using gotos in that thread. And no, do not use gotos.

guest60499

  • Guest
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #12 on: January 30, 2017, 11:17:18 pm »
Do not use labels and gotos! Use break to exit cleanly from loops.

Poorly used gotos are bad, but this is one of the cases where their use is recommended. Putting the loop bodies into functions or using flag variables makes the code less concise. The functionality goto provides in this instance is the same as a labeled loop, which is available in Java and was actually suggested before.

I don't see any suggestions of using gotos in that thread. And no, do not use gotos.

Not in that thread, no, but labeled loops are a goto. If you search goto-related questions on e.g. Stack Exchange (2, 3, 4) you will often find comments that they are benign when used judiciously. If you search for questions related to the use of break and continue you will often find commenters saying that goto is also not a terrible language construct.

Thaddy

  • Hero Member
  • *****
  • Posts: 19181
  • Glad to be alive.
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #13 on: January 31, 2017, 11:10:43 am »
Not in that thread, no, but labeled loops are a goto. If you search goto-related questions on e.g. Stack Exchange (2, 3, 4) you will often find comments that they are benign when used judiciously. If you search for questions related to the use of break and continue you will often find commenters saying that goto is also not a terrible language construct.

The thing is you can get poor advice very quickly. And some even accept it as the holy grail....
"Goto's" are best generated by the compiler (which it does all over the place) and a programmer should stay away from it if the language has constructs that can help avoid its use.
The reason is simply structure: goto can have unwanted side effects (e.g. not releasing memory, wrong parameter counts, etc) that a structured language like modern pascal can help you preventing.
I use something similar like goto when I write assembler, but I never use it in Pascal code. Really, that's a BAD idea. Take computing classes when needed. EVERY teacher or professor will tell you the same.
There is some leeway, when you need to get out of trouble quick, but over the last ~ 20 years (I have 38+ of experience) I NEVER needed a goto,
Not in Pascal, Not in BASIC, not in C and not in C++. ~ 20 years or more ago, we had to.

I only see it when trying to help out, use it, debug it and then have a sleepless night and forget about it... ;)

People are not very good about having an overview of  state. Compilers have. That's why we wrote them...

Let the compiler do the jumping...
« Last Edit: January 31, 2017, 11:21:12 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Cyrax

  • Hero Member
  • *****
  • Posts: 836
Re: [SOLVED] [Synapse] TTCPBlockSocket.RecvString returns empty string.
« Reply #14 on: January 31, 2017, 03:39:38 pm »
Do not use labels and gotos! Use break to exit cleanly from loops.

Poorly used gotos are bad, but this is one of the cases where their use is recommended. Putting the loop bodies into functions or using flag variables makes the code less concise. The functionality goto provides in this instance is the same as a labeled loop, which is available in Java and was actually suggested before.

I don't see any suggestions of using gotos in that thread. And no, do not use gotos.

Not in that thread, no, but labeled loops are a goto. If you search goto-related questions on e.g. Stack Exchange (2, 3, 4) you will often find comments that they are benign when used judiciously. If you search for questions related to the use of break and continue you will often find commenters saying that goto is also not a terrible language construct.

Maybe in Java, but not in Pascal. If you use gotos in loops, there will be resource leaks etc. due to compiler generated clean up code which will be never executed if you jump out of loop by using a label and goto.

Never ever use goto in Pascal.

If you really want to exit from nested loops, enclose outer loop with try..finally and use Exit somewhere inside of the inner loops.
« Last Edit: January 31, 2017, 03:42:40 pm by Cyrax »

 

TinyPortal © 2005-2018