Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to set the listen port for the TIdTCPServer?

292 views
Skip to first unread message

Bo Berglund

unread,
Mar 25, 2007, 3:50:23 PM3/25/07
to
I have tried tp create a handler object for communications with a
machine tool (discussed in another thread).
This object has two TIdTCPServer objects created in its constructor.
I want to set the listen ports for these objects to some value via a
property of my communications object, so I have a SetPort procedure
connected to the write of that property.
Here is the code of that part:

procedure TToolComm.SetStatusPort(const Value: word);
begin
FStatusPort := Value;
FsrvCommand.DefaultPort := Value;
FsrvStatus.DefaultPort := Value + 1;
end;

Then I later open up the listening with this command:

function TToolComm.OpenServer: boolean;
begin
if not FsrvCommand.Active then
FsrvCommand.Active := true;
Result := FsrvCommand.Active;
end;

If I call SetStatusPort with the value 6001 and then call Openserver
and then look in a command window with
netstat -a -p tcp
the result is that I can't see the port 6001 listed as a listening
port. Why?

What am I doing wrong here???

Bo Berglund

unread,
Mar 25, 2007, 5:59:29 PM3/25/07
to
On Sun, 25 Mar 2007 21:50:23 +0200, Bo Berglund
<bo.be...@telia.com> wrote:

>If I call SetStatusPort with the value 6001 and then call Openserver
>and then look in a command window with
>netstat -a -p tcp
>the result is that I can't see the port 6001 listed as a listening
>port. Why?

Don't know why it did not work the first time, but now it does...
I have another problem:

In the server OnExecute I have this code:

procedure TTool.srvCommandExecute(AThread: TIdPeerThread);
{Handler for client calls (calls are responses to commands)}
var
sRecData: string;
begin
sRecData := '';
while not AThread.Terminated and AThread.Connection.Connected do
begin
sRecData := AThread.Connection.ReadLn('</msg>', 2000);
if sRecData = '' then Continue;
AddCmdResponse(sRecData);
end;
end;

It seems like the server is stripping off the terminator (</msg> from
the returned data. Could this be changed so that I will receive the
complete string?

Remy Lebeau (TeamB)

unread,
Mar 25, 2007, 7:17:56 PM3/25/07
to

"Bo Berglund" <bo.be...@telia.com> wrote in message
news:08kd035hgedr2psi3...@4ax.com...

> If I call SetStatusPort with the value 6001 and then call Openserver
> and then look in a command window with
> netstat -a -p tcp
> the result is that I can't see the port 6001 listed as a listening
> port. Why?

Are there any entries already in the TIdTCPServer.Bindings collection
before you call SetStatusPort()? Setting the DefaultPort only effects
new entries that are added to the Bindings afterwards. It has no
effect on entries that already exist in the collection. So if you do
have existing entries, then you will need to loop through them setting
their Port property individually, ie:

procedure TToolComm.SetStatusPort(const Value: word);
var
I: Integer;
begin
FStatusPort := Value;

FsrvCommand.DefaultPort := Value;
for I := 0 to FsrvCommand.Bindings.Count-1 do
FsrvCommand.Bindings[I].Port := FsrvCommand.DefaultPort;

FsrvStatus.DefaultPort := Value + 1;

for I := 0 to FsrvStatus.Bindings.Count-1 do
FsrvStatus..Bindings[I].Port := FsrvStatus.DefaultPort;
end;


Gambit


Remy Lebeau (TeamB)

unread,
Mar 25, 2007, 7:38:20 PM3/25/07
to

"Bo Berglund" <bo.be...@telia.com> wrote in message
news:5jrd035l3e93che03...@4ax.com...

> while not AThread.Terminated and AThread.Connection.Connected do

Get rid of that line. You should not be looping at all. TIdTCPServer
triggers the OnExecute event handler inside an internal loop of its
own.

> It seems like the server is stripping off the terminator (</msg>
> from the returned data.

It is. That is how ReadLn() is designed to operate

> Could this be changed so that I will receive the complete string?

The simpliest way is to append the terminator to the end of the data
that is returned, ie:

procedure TTool.srvCommandExecute(AThread: TIdPeerThread);
var
sRecData: String;


begin
sRecData := AThread.Connection.ReadLn('</msg>', 2000);

if sRecData <> '' then
AddCmdResponse(sRecData + '</msg>');
end;

Alternatively, I normally would suggest using WaitFor() instead of
ReadLn(), ie:

procedure TTool.srvCommandExecute(AThread: TIdPeerThread);
var
sRecData: string;
begin
sRecData := AThread.Connection.WaitFor('</msg>');
AddCmdResponse(sRecData);
end;

However, WaitFor() is buggy in Indy 9. If there is pending data in
the socket after the desired message, the extra data gets included in
the result, which will mess up your communication. This was fixed in
Indy 10, ie:

procedure TTool.srvCommandExecute(AContext: TIdContext);
var
sRecData: string;
begin
sRecData := AThread.Connection.IOHandler.WaitFor('</msg>',
True, True);
AddCmdResponse(sRecData);
end;

But for Indy 9, you would have to call ReadFromStack() and then
manually scan the InputBuffer, ie:

procedure TTool.srvCommandExecute(AThread: TIdPeerThread);
var
sRecData: String;
iPos: Integer;
begin
repeat
AThread.Connection.ReadFromStack(True, 100, False);
iPos := MemoryPos('</msg>',
PChar(AThread.Connection.InputBuffer.Memory),
AThread.Connection.InputBuffer.Size);
until iPos <> 0;
sRecData := AThread.Connection.ReadString(iPos+5);
AddCmdResponse(sRecData);
end;


Gambit


Bo Berglund

unread,
Mar 26, 2007, 5:42:25 PM3/26/07
to
On Sun, 25 Mar 2007 15:17:56 -0800, "Remy Lebeau \(TeamB\)"
<no....@no.spam.com> wrote:

>
>"Bo Berglund" <bo.be...@telia.com> wrote in message
>news:08kd035hgedr2psi3...@4ax.com...
>
>> If I call SetStatusPort with the value 6001 and then call Openserver
>> and then look in a command window with
>> netstat -a -p tcp
>> the result is that I can't see the port 6001 listed as a listening
>> port. Why?
>
>Are there any entries already in the TIdTCPServer.Bindings collection
>before you call SetStatusPort()? Setting the DefaultPort only effects
>new entries that are added to the Bindings afterwards. It has no
>effect on entries that already exist in the collection. So if you do
>have existing entries, then you will need to loop through them setting
>their Port property individually, ie:

I have not used the bindings property at all, but I expect the server
to be switched on and off repeatedly, possibly on different ports as
well.
Does this push bindings objects into the list so I need to handle them
afterwards?
Anyway, I am now setting the DefaultPort when the server objects are
created (in the constructor) and it is also mirrored in the
FStatusPort variable.

> procedure TToolComm.SetStatusPort(const Value: word);
> var
> I: Integer;
> begin
> FStatusPort := Value;
>
> FsrvCommand.DefaultPort := Value;
> for I := 0 to FsrvCommand.Bindings.Count-1 do
> FsrvCommand.Bindings[I].Port := FsrvCommand.DefaultPort;
>
> FsrvStatus.DefaultPort := Value + 1;
> for I := 0 to FsrvStatus.Bindings.Count-1 do
> FsrvStatus..Bindings[I].Port := FsrvStatus.DefaultPort;
> end;
>

Thanks, I will test this tomorrow. :-)

/Bo

Bo Berglund

unread,
Mar 26, 2007, 5:53:56 PM3/26/07
to
On Sun, 25 Mar 2007 15:38:20 -0800, "Remy Lebeau \(TeamB\)"
<no....@no.spam.com> wrote:

>
>"Bo Berglund" <bo.be...@telia.com> wrote in message
>news:5jrd035l3e93che03...@4ax.com...
>
>> while not AThread.Terminated and AThread.Connection.Connected do
>
>Get rid of that line. You should not be looping at all. TIdTCPServer
>triggers the OnExecute event handler inside an internal loop of its
>own.

OK, I will, it was a fluke from misreading the examples. I am still
thinking of having an eternal loop in the OnConnect in order to grab
the handle to the client so I can send commands to it during the time
it stays connected.
I don't want to do that in the main line of the code so just setting a
local variable to the AThread object and later use that to send data
to the client seems to me to use the thread of the main app to execute
the Connection.Write method and thus suffer from the blocking
behaviour. Instead I plan to put outgoing data into a list that is
read in the internal loop in OnConnect and is written by the mainline
code.

Thanks for the explanation, I'll look into this tomorrow at work.

One final thing:
When looking at the server example for Indy9 I noticed that they have
dropped a TIdThreadMgrDefault component on the form and connected it
to the TIdTCPServer. What is the purpose of this?
If I also need to do the same (but not on a form, since I am creating
everything in code in my own object), do I need one such object per
server (I am using two servers)???

/Bo

Remy Lebeau (TeamB)

unread,
Mar 26, 2007, 6:50:48 PM3/26/07
to

"Bo Berglund" <bo.be...@telia.com> wrote in message
news:h8fg031jodda70l93...@4ax.com...

> I have not used the bindings property at all, but I expect
> the server to be switched on and off repeatedly, possibly
> on different ports as well.

When the server is activated, if the Bindings collection is empty then
an item is automatically added. The server always has to have at
least 1 Binding to listen on. When the server is deactivated and then
reactivated, that item remains in the collection, unless you manually
remove it in between.

> Anyway, I am now setting the DefaultPort when the server objects are
> created (in the constructor) and it is also mirrored in the
> FStatusPort variable.

You don't really need the FStatusPort variable at all, since the
FsrvCommand.DefaultPort will always be that same value.


Gambit


Bo Berglund

unread,
Mar 26, 2007, 5:57:45 PM3/26/07
to
On Mon, 26 Mar 2007 14:50:48 -0800, "Remy Lebeau \(TeamB\)"
<no....@no.spam.com> wrote:

>
>"Bo Berglund" <bo.be...@telia.com> wrote in message
>news:h8fg031jodda70l93...@4ax.com...
>
>> I have not used the bindings property at all, but I expect
>> the server to be switched on and off repeatedly, possibly
>> on different ports as well.
>
>When the server is activated, if the Bindings collection is empty then
>an item is automatically added. The server always has to have at
>least 1 Binding to listen on. When the server is deactivated and then
>reactivated, that item remains in the collection, unless you manually
>remove it in between.

Thanks, that was important information for me!

>
>> Anyway, I am now setting the DefaultPort when the server objects are
>> created (in the constructor) and it is also mirrored in the
>> FStatusPort variable.
>
>You don't really need the FStatusPort variable at all, since the
>FsrvCommand.DefaultPort will always be that same value.
>

True, I saw that as well and plan on removing it! :-)

BTW, I notice that you are replying to posts here with an amazing
swiftness! How are you able to do this? Really grateful for your help,
but I am wondering how you can keep up? (I see you everewhere I
look...).

:-)

(Bo

Remy Lebeau (TeamB)

unread,
Mar 26, 2007, 8:22:48 PM3/26/07
to

"Bo Berglund" <bo.be...@telia.com> wrote in message
news:sagg03dgpehk6vb2c...@4ax.com...

> BTW, I notice that you are replying to posts here with an
> amazing swiftness! How are you able to do this? Really
> grateful for your help, but I am wondering how you can
> keep up? (I see you everewhere I look...).

It is simply a matter of how often I check the newsgroups for new
messages, which is pretty often.


Gambit


Remy Lebeau (TeamB)

unread,
Mar 26, 2007, 8:21:32 PM3/26/07
to

"Bo Berglund" <bo.be...@telia.com> wrote in message
news:aifg03hng830a706c...@4ax.com...

> I am still thinking of having an eternal loop in the OnConnect in
> order to grab the handle to the client so I can send commands
> to it during the time it stays connected.

You don't need to do that.

> I don't want to do that in the main line of the code so just setting
a
> local variable to the AThread object and later use that to send data
> to the client

Do not do that, either.

> seems to me to use the thread of the main app to execute the
> Connection.Write method and thus suffer from the blocking
> behaviour.

Write() runs in the context of whatever thread is calling it.

> Instead I plan to put outgoing data into a list that is read in the
> internal loop in OnConnect and is written by the mainline
> code.

Do not loop in the OnConnect event. Assuming your data is
string-based, simply put a TIdThreadSafeStringList into the
TIdPeerThread's Data property, then write to it whenever needed in any
thread, and finally have the OnExecute event send whatever is
currently in the list. For example:

procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
AThread.Data := TIdThreadSafeStringList.Create;
end;

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
List: TIdStringList;
begin
List := TIdThreadSafeStringList(AThread.Data).Lock;
try
AThread.Connection.WriteStrings(List);
finally
List.Clear;
TIdThreadSafeStringList(AThread.Data).Unlock;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
List: TList;
Thread: TIdPeerThread;
begin
List := IdTCPServer1.Threads.LockList;
try
Thread := TIdPeerThread(List[SomeIndex]);
TIdThreadSafeStringList(Thread.Data).Add('your data
here');
finally
IdTCPServer1.Threads.UnlockList;
end;
end;

> When looking at the server example for Indy9 I noticed that they
> have dropped a TIdThreadMgrDefault component on the form
> and connected it to the TIdTCPServer. What is the purpose of this?

The ThreadMgr is what manages the threads that the server uses
internally for client connections. If you do not assign a ThreadMgr,
one is created automatically when the server is activated.

> do I need one such object per server

It is possible to share a single ThreadMgr amongst multiple servers.
I have never done it before, though.


Gambit


0 new messages