Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: how can send data to clients outside indy server excute event


This question is not answered. Helpful answers available: 2. Correct answers available: 1.


Permlink Replies: 25 - Last Post: May 12, 2017 8:39 AM Last Post By: Chen Mingzhu
Chen Mingzhu

Posts: 14
Registered: 6/25/09
how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2017 10:48 AM
in one my application, once client devices connected to indy server, i want to send data from indy server to the selected client or all clients, how can i do this ?
for example , after clients connected to indy server, when i press "send" button, indy server send command data to selected clients or all clients, this mean i need to send data outside indy excute event , how can i do?
can some one give detailed example??

thanks
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2017 11:04 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

in one my application, once client devices connected to indy server,
i want to send data from indy server to the selected client or all
clients, how can i do this ?

See my recent answer on StackOverflow on this same topic:

http://stackoverflow.com/a/43556582/65863

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 27, 2017 12:48 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

in one my application, once client devices connected to indy server,
i want to send data from indy server to the selected client or all
clients, how can i do this ?

See my recent answer on StackOverflow on this same topic:

http://stackoverflow.com/a/43556582/65863

--
Remy Lebeau (TeamB)

Hi Remy Lebeau:

thank you for your so quick reply,actually in my application, once clients connected to server, server wait until button onclick, that mean i want to send all client or selected client in button onclick event , i didn't understand the link you sent very well, can you list a example here? what do i need to do in OnConnect and OnExcute event in such application?

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 27, 2017 8:33 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

i didn't understand the link you sent very well, can you
list a example here?

If you read the link I gave you earlier, it includes a link to an actual
code example that does exactly what you are looking for.

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 28, 2017 2:12 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

i didn't understand the link you sent very well, can you
list a example here?

If you read the link I gave you earlier, it includes a link to an actual
code example that does exactly what you are looking for.

--
Remy Lebeau (TeamB)

thank you , Remy:

i read the link, so can i read response data from the selected client just after indy server send data to the select client in the same button onclick event ?

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 28, 2017 9:14 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

can i read response data from the selected client just after
indy server send data to the select client in the same button
onclick event ?

To do that safely, you would be to block the OnExecute event from running.

Internally, TIdTCPServer calls the client connection's Connected() method in
between calls to OnExecute, and Connected() performs a read operation. If you
are also performing your own reads at the same time, you have a race condition
that can potentially corrupt the content of the connection's InputBuffer.

Otherwise, just do all of your reading in the OnExecute event only, and then
pass around the received data to the rest of your code as needed. If you send
outbound data to the client in the OnExecute event as well (like I demonstrate in
my earlier example), then reading the responses in the same OnExecute event
is trivial.

You really should not be performing ay direct socket I/O from outside of the server's
events, unless you REALLY know what you are doing.

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 29, 2017 1:18 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

can i read response data from the selected client just after
indy server send data to the select client in the same button
onclick event ?

To do that safely, you would be to block the OnExecute event from running.

Internally, TIdTCPServer calls the client connection's Connected() method in
between calls to OnExecute, and Connected() performs a read operation. If you
are also performing your own reads at the same time, you have a race condition
that can potentially corrupt the content of the connection's InputBuffer.

Otherwise, just do all of your reading in the OnExecute event only, and then
pass around the received data to the rest of your code as needed. If you send
outbound data to the client in the OnExecute event as well (like I demonstrate in
my earlier example), then reading the responses in the same OnExecute event
is trivial.

You really should not be performing ay direct socket I/O from outside of the server's
events, unless you REALLY know what you are doing.

--
Remy Lebeau (TeamB)

Hi Remy:
as i mentioned in last post, in my application , once TCP/IP devices( all devices are set as clients) connected to Indy Sever(and now we test all TCP/IP devices can connect to Indy Sever now),Indy Sever send data to all clients or the selected cleint in button OnClick event( that is to say after clients connected to server,data transfer initiate from Indy Sever ,not client),so for safe reason , what your suggestion is i perform writing operation in button OnClick event , but read acknolage data from Indy Sever? OnExecute event ?

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 1, 2017 11:30 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

as i mentioned in last post, in my application , once TCP/IP
devices( all devices are set as clients) connected to Indy Sever(and
now we test all TCP/IP devices can connect to Indy Sever now),Indy
Sever send data to all clients or the selected cleint in button
OnClick event( that is to say after clients connected to server,data
transfer initiate from Indy Sever ,not client),so for safe reason ,
what your suggestion is i perform writing operation in button OnClick
event , but read acknolage data from
Indy Sever? OnExecute event ?

I already answered that. Please re-read my earlier responses. Do all
of the sending and reading in the server's OnExecute event only. Have your
button OnClick event put data into a per-client thread-safe queue that OnExecute
can then check periodically and send to the client. After it sends a message
to the client, it can read the client's response.

OnClick
{
put a message in each desired client's queue ...
}

OnExecute
{
if (this client's queue has data)
{
for (each message in queue)
{
send message to client
read response from client
}
}
}

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 2, 2017 12:39 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

as i mentioned in last post, in my application , once TCP/IP
devices( all devices are set as clients) connected to Indy Sever(and
now we test all TCP/IP devices can connect to Indy Sever now),Indy
Sever send data to all clients or the selected cleint in button
OnClick event( that is to say after clients connected to server,data
transfer initiate from Indy Sever ,not client),so for safe reason ,
what your suggestion is i perform writing operation in button OnClick
event , but read acknolage data from
Indy Sever? OnExecute event ?

I already answered that. Please re-read my earlier responses. Do all
of the sending and reading in the server's OnExecute event only. Have your
button OnClick event put data into a per-client thread-safe queue that OnExecute
can then check periodically and send to the client. After it sends a message
to the client, it can read the client's response.

OnClick
{
put a message in each desired client's queue ...
}

OnExecute
{
if (this client's queue has data)
{
for (each message in queue)
{
send message to client
read response from client
}
}
}

--
Remy Lebeau (TeamB)

Hi Remy :
thank you so much for your help, i looked at the further websit link, i may understand what you mean, but it is a little problem for me to change delphi source code to c++ ,since i am not so familar with Indy Component and delphi, i alway use c++ builder ,here i repaste inherit class ,can you help me to transfor to c++ ? thanks in advance

TMyContext = class(TIdServerContext)
public
ClientName: String;
Queue: TIdThreadSafeStringList;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
end;

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
Queue := TIdThreadSafeStringList.Create;
end;

destructor TMyContext.Destroy;
begin
Queue.Free;
inherited Destroy;
end;

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 2, 2017 8:45 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

i looked at the further websit link, i may understand what you mean,
but it is a little problem for me to change delphi source code to c++ ,
since i am not so familar with Indy Component and delphi, i alway use
c++ builder ,here i repaste inherit class ,can you help me to transfor
to c++ ?

class TMyContext : public TIdServerContext
{
public:
    String ClientName;
    TIdThreadSafeStringList *Queue;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TThreadList *AList = 0);
    __fastcall ~TMyContext();
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList)
{
    Queue = new TIdThreadSafeStringList;
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
}
 
__fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    IdTCPServer1->ContextClass = __classid(TMyContext);
}
 
void __fastcall TForm1::SendCommandToClient(const String &ClientName, const 
String &Command)
{
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int I = 0; I < List->Count; ++I)
        {
            TMyContext *Ctx = (TMyContext*) List->Items[I];
            if (Ctx->ClientName == ClientName)
            {
                Ctx->Queue->Add(Command);
                break;
            }
        }
    }
    __finally
    {
        IdTCPServer1->Context->UnlockList();
    }
}
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    String ClientName = AContext->Connection->IOHandler->ReadLn();
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int I = 0; I < List->Count; ++I)
        {
            TMyContext *Ctx2 = (TMyContext*) List->Items[I];
            if ((Ctx2 != Ctx) && (Ctx->ClientName == ClientName))
            {
                AContext->Connection->IOHandler->WriteLn("That Name is already 
logged in");
                AContext->Connection->Disconnect();
                return;
            }
        }
        Ctx->ClientName = ClientName;
    }
    __finally
    {
        IdTCPServer1->Context->UnlockList();
    }
 
    AContext->Connection->IOHandler->WriteLn("Welcome " + ClientName);
}
 
void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    Ctx->ClientName = "";
    Ctx->Queue->Clear();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        while (Queue->Count > 0)
        {
            AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
            Queue->Delete(0);
            ...
        }
        ...
    }
    __finally
    {
        Ctx->Queue->Unlock();
    }
 
    ...
}


--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 2, 2017 9:30 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

i looked at the further websit link, i may understand what you mean,
but it is a little problem for me to change delphi source code to c++ ,
since i am not so familar with Indy Component and delphi, i alway use
c++ builder ,here i repaste inherit class ,can you help me to transfor
to c++ ?

class TMyContext : public TIdServerContext
{
public:
    String ClientName;
    TIdThreadSafeStringList *Queue;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TThreadList *AList = 0);
    __fastcall ~TMyContext();
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList)
{
    Queue = new TIdThreadSafeStringList;
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
}
 
__fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    IdTCPServer1->ContextClass = __classid(TMyContext);
}
 
void __fastcall TForm1::SendCommandToClient(const String &ClientName, const 
String &Command)
{
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int I = 0; I < List->Count; ++I)
        {
            TMyContext *Ctx = (TMyContext*) List->Items[I];
            if (Ctx->ClientName == ClientName)
            {
                Ctx->Queue->Add(Command);
                break;
            }
        }
    }
    __finally
    {
        IdTCPServer1->Context->UnlockList();
    }
}
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    String ClientName = AContext->Connection->IOHandler->ReadLn();
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int I = 0; I < List->Count; ++I)
        {
            TMyContext *Ctx2 = (TMyContext*) List->Items[I];
            if ((Ctx2 != Ctx) && (Ctx->ClientName == ClientName))
            {
                AContext->Connection->IOHandler->WriteLn("That Name is already 
logged in");
                AContext->Connection->Disconnect();
                return;
            }
        }
        Ctx->ClientName = ClientName;
    }
    __finally
    {
        IdTCPServer1->Context->UnlockList();
    }
 
    AContext->Connection->IOHandler->WriteLn("Welcome " + ClientName);
}
 
void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    Ctx->ClientName = "";
    Ctx->Queue->Clear();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        while (Queue->Count > 0)
        {
            AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
            Queue->Delete(0);
            ...
        }
        ...
    }
    __finally
    {
        Ctx->Queue->Unlock();
    }
 
    ...
}


--
Remy Lebeau (TeamB)

Hi Remy:
the TMyContext class can't be compiled successfully with the following error:

[bcc32 Error] Unit3.cpp(13): E2285 Could not find a match for 'TIdServerContext::TIdServerContext(TIdTCPConnection *,TIdYarn *,TThreadList *)'

i am using c++ builder xe4 with self-contain indy component , i guess it is indy 10 , so i looked into indy 10 manual, i can't find TIdServerContext class, instead i guess it is TIdContext , so changed it to following :

//-------------------------------------------------------------------------------------------------------------------------------------------------------
class TMyContext : public TIdContext
{
public:
String ClientName;
TIdThreadSafeStringList *Queue;

__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn,
TThreadList *AList = 0);
__fastcall ~TMyContext();
};

__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn
*AYarn, TThreadList *AList)
: TIdContext(AConnection, AYarn, AList)
{
Queue = new TIdThreadSafeStringList;
}
//------------------------------------------------------------------------------------------------------------------------
still same error with the following information:

bcc32 Error] Unit3.cpp(13): E2285 Could not find a match for 'TIdContext::TIdContext(TIdTCPConnection *,TIdYarn *,TThreadList *)'

there is one constructor in TIdContext:
constructor Create(
AConnection: TIdTCPConnection,
AYarn: TIdYarn,
AList: TIdThreadList = nil
); virtual; reintroduce;

so it seem there is no problem to call base class constructor , what is problem here? by the way, in constructor ,it is TIdThreadList , but still use TThreadList,is this problem?
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 3, 2017 10:25 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

the TMyContext class can't be compiled successfully with the
following error:

[bcc32 Error] Unit3.cpp(13): E2285 Could not find a match for
'TIdServerContext::TIdServerContext(TIdTCPConnection *,TIdYarn
*,TThreadList *)'

there is one constructor in TIdContext:

constructor Create(
AConnection: TIdTCPConnection,
AYarn: TIdYarn,
AList: TIdThreadList = nil
); virtual; reintroduce;

Then you are using a really old version of Indy. In the AList parameter,
TIdThreadList was replaced with TThreadList way back in 2007, so you will
have to adjust your code accordingly:

__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdThreadList 
*AList = 0);


In 2013 (for XE4, actually), the AList parameter was updated again to use
a new TIdContextThreadList type instead:

__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdContextThreadList 
*AList = 0);


i looked into indy 10 manual, i can't find TIdServerContext class

TIdServerContext is not documented.

instead i guess it is TIdContext , so changed it to following :

Any context class that you assign to the TIdTCPServer::ContextClass property
must be derived from TIdServerContext (which is declared in IdCustomTCPServer.pas/IdCustomTCPServer.hpp).
Delphi enforces this, but C++Builder cannot.

TIdServerContext derives from TIdContext, and exposes a public Server property
that TIdTCPServer populates, so the ContextClass must be set to a proper
TIdServerContext-derived class type.

by the way, in constructor ,it is TIdThreadList , but still use
TThreadList,is this problem?

Yes, it is a problem. TThreadList and TIdThreadList are different class
types. Your derived constructor must match the base constructor exactly.

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 4, 2017 11:10 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

the TMyContext class can't be compiled successfully with the
following error:

[bcc32 Error] Unit3.cpp(13): E2285 Could not find a match for
'TIdServerContext::TIdServerContext(TIdTCPConnection *,TIdYarn
*,TThreadList *)'

there is one constructor in TIdContext:

constructor Create(
AConnection: TIdTCPConnection,
AYarn: TIdYarn,
AList: TIdThreadList = nil
); virtual; reintroduce;

Then you are using a really old version of Indy. In the AList parameter,
TIdThreadList was replaced with TThreadList way back in 2007, so you will
have to adjust your code accordingly:

__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdThreadList 
*AList = 0);


In 2013 (for XE4, actually), the AList parameter was updated again to use
a new TIdContextThreadList type instead:

__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdContextThreadList 
*AList = 0);


i looked into indy 10 manual, i can't find TIdServerContext class

TIdServerContext is not documented.

instead i guess it is TIdContext , so changed it to following :

Any context class that you assign to the TIdTCPServer::ContextClass property
must be derived from TIdServerContext (which is declared in IdCustomTCPServer.pas/IdCustomTCPServer.hpp).
Delphi enforces this, but C++Builder cannot.

TIdServerContext derives from TIdContext, and exposes a public Server property
that TIdTCPServer populates, so the ContextClass must be set to a proper
TIdServerContext-derived class type.

by the way, in constructor ,it is TIdThreadList , but still use
TThreadList,is this problem?

Yes, it is a problem. TThreadList and TIdThreadList are different class
types. Your derived constructor must match the base constructor exactly.

--
Remy Lebeau (TeamB)

Hi Remy:
thank you so much for your help, you are correct , TTHread should be changed to TIdContextThreadList in XE4
//-----------------------------------------------------------------------------------------------------
class TMyContext: public TIdServerContext
{
public:
String ClientName;
TIdThreadSafeStringList *Queue ;
__fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, TIdContextThreadList *AList=0);
__fastcall ~TMyContext();

};

__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn
*AYarn, TIdContextThreadList *AList)
: TIdServerContext(AConnection, AYarn, AList)
{

//TIdContext(AConnection, AYarn, AList);
Queue = new TIdThreadSafeStringList;

}

__fastcall TMyContext::~TMyContext()
{
delete Queue;
}
//--------------------------------------------------------------------------------------------------------------------------------
and now it can work properly with the following two exceptions:

1. /* ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return; */ never got a chance to excute even we set two Client deivces to same Client Name( we send ID number to each Client device ),so if we change

" if ((Ctx2 != Ctx) && _(Ctx->ClientName == ClientName_)) " to "if ((Ctx2 != Ctx) && _(Ctx2->ClientName == ClientName_)) " , then if there is another client device with same client name , this one will be disconnected


void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
int i;
String tmpIP;
AContext->Connection->IOHandler->WriteLn("<QUERYID>");
String ClientName = AContext->Connection->IOHandler->ReadLn();
TMyContext *Ctx = (TMyContext*) AContext;
TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];
if ((Ctx2 != Ctx) && _(Ctx->ClientName == ClientName_))
{
//AContext->Connection->IOHandler->WriteLn("That Name is already logged in");
ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return;
}
}

Ctx->ClientName = ClientName;
ListBox1->Items->Add(Ctx->ClientName) ;
tmpIP= AContext->Connection->Socket->Binding->PeerIP;
//StringGrid1->Cells[3][ClientName.ToInt()]= tmpIP;
//StringGrid1->Cells[1][ClientName.ToInt()]= Ctx->ClientName;
ListBox1->Items->Add(tmpIP);
StatusBar1->Panels->Items[0]->Text ="internet connected!";
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}

}

changed to

//----------------------------------------------------------------------------------------------------------------------------------------
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
int i;
String tmpIP;
AContext->Connection->IOHandler->WriteLn("<QUERYID>");
String ClientName = AContext->Connection->IOHandler->ReadLn();
TMyContext *Ctx = (TMyContext*) AContext;
TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];
if ((Ctx2 != Ctx) && _(Ctx2->ClientName == ClientName_))
{
//AContext->Connection->IOHandler->WriteLn("That Name is already logged in");
ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return;
}
}

Ctx->ClientName = ClientName;
ListBox1->Items->Add(Ctx->ClientName) ;
tmpIP= AContext->Connection->Socket->Binding->PeerIP;
//StringGrid1->Cells[3][ClientName.ToInt()]= tmpIP;
//StringGrid1->Cells[1][ClientName.ToInt()]= Ctx->ClientName;
ListBox1->Items->Add(tmpIP);
StatusBar1->Panels->Items[0]->Text ="internet connected!";
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}

}//----------------------------------------------------------------------------------------------------------------------------

2. there is another problem is that i found i can't operate StringGrid both in Connocted event and Excute event, but i don't have any problem to operate Memo or ListBox, i want to put client name, IP address , communication result to each colum for clear view,
how can i operate StringGrid?

void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
String tmpstring;
TMyContext *Ctx = (TMyContext*) AContext;

TStringList *Queue = Ctx->Queue->Lock();
try
{
while (Queue->Count > 0)
{
AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
Queue->Delete(0);
tmpstring=AContext->Connection->IOHandler->ReadLn();
/*
if(tmpstring.Pos("<TEST>"))

ShowMessage("self test finish!");
else
ShowMessage("self test failed!");

*/
Memo1->Lines->Add(AContext->Connection->IOHandler->ReadLn()) ;
//StringGrid1->Cells[1][1] = AContext->Connection->IOHandler->ReadLn();
}
//
}
__finally
{
Ctx->Queue->Unlock();
}

}

thanks in advance

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 5, 2017 11:11 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

1. /* ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return; */

ShowMessage() is not thread-safe, so DO NOT call it in the server's event
handlers. If you need to display a popup message (which I don't recommend
a server do), use the Win32 API MessageBox() function directly. Otherwise,
you need to synchronize with the main UI thread in order to calll ShowMessage()
safely.

never got a chance to excute even we set two Client deivces to
same Client Name( we send ID number to each Client device),

so if we change

"if ((Ctx2 != Ctx) && (Ctx->ClientName == ClientName))"

to

"if ((Ctx2 != Ctx) && (Ctx2->ClientName == ClientName))"

then if there is another client device with same client name , this one
will be disconnected

Yes, that was a typo on my part.

ListBox1->Items->Add(Ctx->ClientName) ;
//StringGrid1->Cells[3][ClientName.ToInt()]= tmpIP;
//StringGrid1->Cells[1][ClientName.ToInt()]= Ctx->ClientName;
ListBox1->Items->Add(tmpIP);
StatusBar1->Panels->Items[0]->Text ="internet connected!";

All of that is not thread-safe, either, and must be synchronized with the
main UI thread.

2. there is another problem is that i found i can't operate StringGrid
both in Connocted event and Excute event

That is because you are not synchronizing with the main UI thread. ANY access
to UI controls MUST be done in the main UI thread only.

but i don't have any problem to operate Memo or ListBox

Yes, you do, you just don't know it. Accessing ANY UI control from outside
of the main UI thread can cause unexpected side effects that are not always
apparent right away.

Try something more like this instead:

...
#include <IdSync.hpp>
 
class TMyContext : public TIdServerContext
{
public:
    int ClientID;
    TIdThreadSafeStringList *Queue ;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TIdContextThreadList *AList=0);
    __fastcall ~TMyContext();
 
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
}
 
...
 
class TAddClientToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = ClientID;
            Form1->StringGrid1->Cells[3][ClientID] = IP;
        }
 
        Form1->ListBox1->Items->AddObject(IP + ": " + String(ClientID), Ctx);
 
        Form1->StatusBar1->Panels->Items[0]->Text = "internet connected!";
    }
 
public:
    int ClientID;
    String IP;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TRemoveClientFromUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = "";
            Form1->StringGrid1->Cells[3][ClientID] = "";
        }
 
        int idx = Form1->ListBox1->Items->IndexOfObject(Ctx);
        if (idx != -1)
            Form1->ListBox1->Items->Delete(idx);
 
        if (Form1->ListBox1->Items->Count == 0)
            Form1->StatusBar1->Panels->Items[0]->Text = "internet disconnected!";
    }
 
public:
    int ClientID;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TAddMessageToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(String(ClientID) + ": " + Message);
 
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
            Form1->StringGrid1->Cells[1][ClientID] = Message;
    }
 
public:
    int ClientID;
    String Message;
};
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    AContext->Connection->IOHandler->WriteLn("<QUERYID>");
    int ClientID = AContext->Connection->IOHandler->ReadLn().ToIntDef(-1);
 
    if (ClientID < 0)
    {
        //AContext->Connection->IOHandler->WriteLn("That ID is not valid");
        AContext->Connection->Disconnect();
        return;
    }
 
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int i = 0; i < List->Count; ++i)
        {
            TMyContext *Ctx2 = (TMyContext*) List->Items[i];
            if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID))
            {
                 //AContext->Connection->IOHandler->WriteLn("That ID is already 
logged in");
                 AContext->Connection->Disconnect();
                 return;
            }
        }
 
        Ctx->ClientID = ClientID;
    }
    __finally
    {
        IdTCPServer1->Contexts->UnlockList();
    }
 
    //AContext->Connection->IOHandler->WriteLn("Hello " + ClientName);
 
    TAddClientToUI *sync = new TAddClientToUI;
    sync->IP = AContext->Connection->Socket->Binding->PeerIP;
    sync->ClientID = ClientID;
    sync->Ctx = AContext;
    sync->Notify();
}
 
void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (sync->ClientID != -1)
    {
        TRemoveClientFromUI *sync = new TRemoveClientFromUI;
        sync->ClientID = Ctx->ClientID;
        sync->Ctx = AContext;
        sync->Notify();
    }
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        while (Queue->Count > 0)
        {
            AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
            Queue->Delete(0);
 
            String tmpString = AContext->Connection->IOHandler->ReadLn();
 
            TAddMessageToUI *sync = new TAddMessageToUI;
            sync->ClientID = Ctx->ClientID;
            sync->Message = tmpString;
            sync->Notify();
        }
    }
    __finally
    {
        Ctx->Queue->Unlock();
    }
}


--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 6, 2017 6:41 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

1. /* ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return; */

ShowMessage() is not thread-safe, so DO NOT call it in the server's event
handlers. If you need to display a popup message (which I don't recommend
a server do), use the Win32 API MessageBox() function directly. Otherwise,
you need to synchronize with the main UI thread in order to calll ShowMessage()
safely.

never got a chance to excute even we set two Client deivces to
same Client Name( we send ID number to each Client device),

so if we change

"if ((Ctx2 != Ctx) && (Ctx->ClientName == ClientName))"

to

"if ((Ctx2 != Ctx) && (Ctx2->ClientName == ClientName))"

then if there is another client device with same client name , this one
will be disconnected

Yes, that was a typo on my part.

ListBox1->Items->Add(Ctx->ClientName) ;
//StringGrid1->Cells[3][ClientName.ToInt()]= tmpIP;
//StringGrid1->Cells[1][ClientName.ToInt()]= Ctx->ClientName;
ListBox1->Items->Add(tmpIP);
StatusBar1->Panels->Items[0]->Text ="internet connected!";

All of that is not thread-safe, either, and must be synchronized with the
main UI thread.

2. there is another problem is that i found i can't operate StringGrid
both in Connocted event and Excute event

That is because you are not synchronizing with the main UI thread. ANY access
to UI controls MUST be done in the main UI thread only.

but i don't have any problem to operate Memo or ListBox

Yes, you do, you just don't know it. Accessing ANY UI control from outside
of the main UI thread can cause unexpected side effects that are not always
apparent right away.

Try something more like this instead:

...
#include <IdSync.hpp>
 
class TMyContext : public TIdServerContext
{
public:
    int ClientID;
    TIdThreadSafeStringList *Queue ;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TIdContextThreadList *AList=0);
    __fastcall ~TMyContext();
 
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
}
 
...
 
class TAddClientToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = ClientID;
            Form1->StringGrid1->Cells[3][ClientID] = IP;
        }
 
        Form1->ListBox1->Items->AddObject(IP + ": " + String(ClientID), Ctx);
 
        Form1->StatusBar1->Panels->Items[0]->Text = "internet connected!";
    }
 
public:
    int ClientID;
    String IP;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TRemoveClientFromUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = "";
            Form1->StringGrid1->Cells[3][ClientID] = "";
        }
 
        int idx = Form1->ListBox1->Items->IndexOfObject(Ctx);
        if (idx != -1)
            Form1->ListBox1->Items->Delete(idx);
 
        if (Form1->ListBox1->Items->Count == 0)
            Form1->StatusBar1->Panels->Items[0]->Text = "internet disconnected!";
    }
 
public:
    int ClientID;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TAddMessageToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(String(ClientID) + ": " + Message);
 
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
            Form1->StringGrid1->Cells[1][ClientID] = Message;
    }
 
public:
    int ClientID;
    String Message;
};
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    AContext->Connection->IOHandler->WriteLn("<QUERYID>");
    int ClientID = AContext->Connection->IOHandler->ReadLn().ToIntDef(-1);
 
    if (ClientID < 0)
    {
        //AContext->Connection->IOHandler->WriteLn("That ID is not valid");
        AContext->Connection->Disconnect();
        return;
    }
 
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int i = 0; i < List->Count; ++i)
        {
            TMyContext *Ctx2 = (TMyContext*) List->Items[i];
            if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID))
            {
                 //AContext->Connection->IOHandler->WriteLn("That ID is already 
logged in");
                 AContext->Connection->Disconnect();
                 return;
            }
        }
 
        Ctx->ClientID = ClientID;
    }
    __finally
    {
        IdTCPServer1->Contexts->UnlockList();
    }
 
    //AContext->Connection->IOHandler->WriteLn("Hello " + ClientName);
 
    TAddClientToUI *sync = new TAddClientToUI;
    sync->IP = AContext->Connection->Socket->Binding->PeerIP;
    sync->ClientID = ClientID;
    sync->Ctx = AContext;
    sync->Notify();
}
 
void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (sync->ClientID != -1)
    {
        TRemoveClientFromUI *sync = new TRemoveClientFromUI;
        sync->ClientID = Ctx->ClientID;
        sync->Ctx = AContext;
        sync->Notify();
    }
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        while (Queue->Count > 0)
        {
            AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
            Queue->Delete(0);
 
            String tmpString = AContext->Connection->IOHandler->ReadLn();
 
            TAddMessageToUI *sync = new TAddMessageToUI;
            sync->ClientID = Ctx->ClientID;
            sync->Message = tmpString;
            sync->Notify();
        }
    }
    __finally
    {
        Ctx->Queue->Unlock();
    }
}


--
Remy Lebeau (TeamB)

Hi Remy :

thanks again, the lastest code you provide now can work on StringGrid properly, but after further testing , i found that in the following both conditions , software didn't function properly:


1. if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID)) , as i mentioned in last post, in this condition, if another Client device is set to same ID numer as one device which already log in ( for example both Client devices ID number are set to 1), this one can still log in

2. if we change to " if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID)) ", then if there is another client with same ID numer which already log in, this another one will be distconnected,this function correctly, but i found an another problem:

if i set different ID number to each client, both clients log in, then i power down or take off net cable(CAT5/6 cable) from one client, and power on or plug CAT5 cable again, this client got connect and disconnect repeatly

another problem is :

if i power down or take CAT5 cable from client, OnDisconnect event never be triggered(TForm1::IdTCPServer1Disconnect(TIdContext *AContext) never got a chance to excute), it seem server don't know that client is disconnected in this two cases,only if you call AContext->Connection->Disconnect() explicity , then OnDisconnect event is triggered

does this mean i have to use the following way to check if client device still connect:

put a timer and send heartbeat package to each client periodly and wait for response ,if got response, then mean that client still connect

is there other way?

looking forward to your further help

Asger Joergensen

Posts: 370
Registered: 11/18/08
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 6, 2017 7:46 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Hi Chen

Please delete unneeded quotes.

Best regards
Asger
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 6, 2017 10:18 AM   in response to: Asger Joergensen in response to: Asger Joergensen
Asger Joergensen wrote:
Hi Chen

Please delete unneeded quotes.

Best regards
Asger

Hi Asger:
that is not point, i just marked it with quotes here to point out what i changed to , real code is if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID)) ,not function properly.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 8, 2017 1:49 PM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

that is not point, i just marked it with quotes here to point out
what i changed to

What Asger is trying to say is that there is no need to quote the same text
over and over. People can see the discussion's history if they want to see
what was said earlier. Please don't over-quote. Only quote the portions
that you are directly responding to. Trim out everything else.

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 7, 2017 8:04 PM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen Mingzhu wrote:
Remy Lebeau (TeamB) wrote:
Chen wrote:

1. /* ShowMessage("Client Name conflit");
AContext->Connection->Disconnect();
return; */

ShowMessage() is not thread-safe, so DO NOT call it in the server's event
handlers. If you need to display a popup message (which I don't recommend
a server do), use the Win32 API MessageBox() function directly. Otherwise,
you need to synchronize with the main UI thread in order to calll ShowMessage()
safely.

never got a chance to excute even we set two Client deivces to
same Client Name( we send ID number to each Client device),

so if we change

"if ((Ctx2 != Ctx) && (Ctx->ClientName == ClientName))"

to

"if ((Ctx2 != Ctx) && (Ctx2->ClientName == ClientName))"

then if there is another client device with same client name , this one
will be disconnected

Yes, that was a typo on my part.

ListBox1->Items->Add(Ctx->ClientName) ;
//StringGrid1->Cells[3][ClientName.ToInt()]= tmpIP;
//StringGrid1->Cells[1][ClientName.ToInt()]= Ctx->ClientName;
ListBox1->Items->Add(tmpIP);
StatusBar1->Panels->Items[0]->Text ="internet connected!";

All of that is not thread-safe, either, and must be synchronized with the
main UI thread.

2. there is another problem is that i found i can't operate StringGrid
both in Connocted event and Excute event

That is because you are not synchronizing with the main UI thread. ANY access
to UI controls MUST be done in the main UI thread only.

but i don't have any problem to operate Memo or ListBox

Yes, you do, you just don't know it. Accessing ANY UI control from outside
of the main UI thread can cause unexpected side effects that are not always
apparent right away.

Try something more like this instead:

...
#include <IdSync.hpp>
 
class TMyContext : public TIdServerContext
{
public:
    int ClientID;
    TIdThreadSafeStringList *Queue ;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TIdContextThreadList *AList=0);
    __fastcall ~TMyContext();
 
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
}
 
...
 
class TAddClientToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = ClientID;
            Form1->StringGrid1->Cells[3][ClientID] = IP;
        }
 
        Form1->ListBox1->Items->AddObject(IP + ": " + String(ClientID), Ctx);
 
        Form1->StatusBar1->Panels->Items[0]->Text = "internet connected!";
    }
 
public:
    int ClientID;
    String IP;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TRemoveClientFromUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
        {
            Form1->StringGrid1->Cells[1][ClientID] = "";
            Form1->StringGrid1->Cells[3][ClientID] = "";
        }
 
        int idx = Form1->ListBox1->Items->IndexOfObject(Ctx);
        if (idx != -1)
            Form1->ListBox1->Items->Delete(idx);
 
        if (Form1->ListBox1->Items->Count == 0)
            Form1->StatusBar1->Panels->Items[0]->Text = "internet disconnected!";
    }
 
public:
    int ClientID;
    TIdContext *Ctx;
 
    __fastcall AddClientToUI() : TIdNotify() {}
};
 
class TAddMessageToUI : public TIdNotify
{
protected:
    virtual void __fastcall DoNotify()
    {
        Form1->Memo1->Lines->Add(String(ClientID) + ": " + Message);
 
        if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
            Form1->StringGrid1->Cells[1][ClientID] = Message;
    }
 
public:
    int ClientID;
    String Message;
};
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    AContext->Connection->IOHandler->WriteLn("<QUERYID>");
    int ClientID = AContext->Connection->IOHandler->ReadLn().ToIntDef(-1);
 
    if (ClientID < 0)
    {
        //AContext->Connection->IOHandler->WriteLn("That ID is not valid");
        AContext->Connection->Disconnect();
        return;
    }
 
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int i = 0; i < List->Count; ++i)
        {
            TMyContext *Ctx2 = (TMyContext*) List->Items[i];
            if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID))
            {
                 //AContext->Connection->IOHandler->WriteLn("That ID is already 
logged in");
                 AContext->Connection->Disconnect();
                 return;
            }
        }
 
        Ctx->ClientID = ClientID;
    }
    __finally
    {
        IdTCPServer1->Contexts->UnlockList();
    }
 
    //AContext->Connection->IOHandler->WriteLn("Hello " + ClientName);
 
    TAddClientToUI *sync = new TAddClientToUI;
    sync->IP = AContext->Connection->Socket->Binding->PeerIP;
    sync->ClientID = ClientID;
    sync->Ctx = AContext;
    sync->Notify();
}
 
void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (sync->ClientID != -1)
    {
        TRemoveClientFromUI *sync = new TRemoveClientFromUI;
        sync->ClientID = Ctx->ClientID;
        sync->Ctx = AContext;
        sync->Notify();
    }
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        while (Queue->Count > 0)
        {
            AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
            Queue->Delete(0);
 
            String tmpString = AContext->Connection->IOHandler->ReadLn();
 
            TAddMessageToUI *sync = new TAddMessageToUI;
            sync->ClientID = Ctx->ClientID;
            sync->Message = tmpString;
            sync->Notify();
        }
    }
    __finally
    {
        Ctx->Queue->Unlock();
    }
}


--
Remy Lebeau (TeamB)

Hi Remy :

thanks again, the lastest code you provide now can work on StringGrid properly, but after further testing , i found that in the following both conditions , software didn't function properly:


1. if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID)) , as i mentioned in last post, in this condition, if another Client device is set to same ID numer as one device which already log in ( for example both Client devices ID number are set to 1), this one can still log in

2. if we change to " if ((Ctx2 != Ctx) && (Ctx2->ClientID == ClientID)) ", then if there is another client with same ID numer which already log in, this another one will be distconnected,this function correctly, but i found an another problem:

if i set different ID number to each client, both clients log in, then i power down or take off net cable(CAT5/6 cable) from one client, and power on or plug CAT5 cable again, this client got connect and disconnect repeatly

another problem is :

if i power down or take CAT5 cable from client, OnDisconnect event never be triggered(TForm1::IdTCPServer1Disconnect(TIdContext *AContext) never got a chance to excute), it seem server don't know that client is disconnected in this two cases,only if you call AContext->Connection->Disconnect() explicity , then OnDisconnect event is triggered

does this mean i have to use the following way to check if client device still connect:

put a timer and send heartbeat package to each client periodly and wait for response ,if got response, then mean that client still connect

is there other better way?


now another bigger issue is currently i just connected two client device, and process will take up 29% around CPU usage in fast computer, and in another a little slow computer, it will get to 100%, this is big problem even we connect more clients

by the way, i can check client disconnection for reason such as client power down or CAT5 off using timer ,but this is not the reason that cause process take up so many CPU , after i remove timer, still same problem:


//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{

IdTCPServer1->ContextClass = __classid(TMyContext);
StringGrid1->Cells[1][0] = "ID";
StringGrid1->Cells[2][0] = "online status";
StringGrid1->Cells[3][0] = "IP Address";
StringGrid1->Cells[4][0] = "communication result";
StringGrid1->Cells[5][0] = "station site";
}
//---------------------------------------------------------------------------
class TAddClientToUI : public TIdNotify
{
protected:
virtual void __fastcall DoNotify()
{
if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
{
Form1->StringGrid1->Cells[1][ClientID+1] = IntToStr(ClientID);
Form1->StringGrid1->Cells[3][ClientID+1] = IP;
}

//Form1->ListBox1->Items->AddObject(IP + ": " + String(ClientID), Ctx);

Form1->StatusBar1->Panels->Items[0]->Text = "internet connected!";
Form1->Button1->Enabled = false;
Form1->Button2->Enabled = true;
Form1->Button3->Enabled = true;
Form1->Button4->Enabled = true;
Form1->UpDown1->Enabled = true;
Form1->Button6->Enabled = true;
Form1->Edit1->Enabled = true;

}

public:
int ClientID;
String IP;
TIdContext *Ctx;
__fastcall TAddClientToUI() : TIdNotify(){}

};
//---------------------------------------------------------------------------
class TRemoveClientFromUI : public TIdNotify
{
protected:
virtual void __fastcall DoNotify()
{
if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
{
Form1->StringGrid1->Cells[1][ClientID+1] = "";
Form1->StringGrid1->Cells[3][ClientID+1] = "";
}
/*
int idx = Form1->ListBox1->Items->IndexOfObject(Ctx);
if (idx != -1)
Form1->ListBox1->Items->Delete(idx);
*/
//if (Form1->ListBox1->Items->Count == 0)
//Form1->StatusBar1->Panels->Items[0]->Text = "internet disconnected!";
}

public:
int ClientID;
TIdContext *Ctx;
__fastcall TRemoveClientFromUI(): TIdNotify(){}
//__fastcall AddClientToUI() : TIdNotify();
};
//---------------------------------------------------------------------------
class TAddMessageToUI : public TIdNotify
{
protected:
virtual void __fastcall DoNotify()
{
//Form1->Memo1->Lines->Add(String(ClientID) + ": " + Message);

if ((ClientID >= 0) && (ClientID < Form1->StringGrid1->RowCount))
Form1->StringGrid1->Cells[4][ClientID+1] = Message;
}

public:
int ClientID;
String Message;
};

//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)
{


StringGrid1->Cells[0][1] = "1";
StringGrid1->Cells[0][2] = "2";
StringGrid1->Cells[0][3] = "3";
StringGrid1->Cells[0][4] = "4";
StringGrid1->Cells[0][5] = "5";
StringGrid1->Cells[0][6] = "6";
StringGrid1->Cells[0][7] = "7";
StringGrid1->Cells[0][8] = "8";
StringGrid1->Cells[0][9] = "9";
StringGrid1->Cells[0][10] = "10";
MyCheckedBmp = new Graphics::TBitmap;

ImageList1->GetBitmap(0,MyCheckedBmp);
MyUncheckedBmp = new Graphics::TBitmap;

ImageList1->GetBitmap(1,MyUncheckedBmp);

}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{

IdTCPServer1->DefaultPort = LabeledEdit3->Text.ToInt();
IdTCPServer1->Active = true;
StatusBar1->Panels->Items[1]->Text = "current station:" +LabeledEdit3->Text;


}

//---------------------------------------------------------------------------

void __fastcall TForm1::Button6Click(TObject *Sender)
{

TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];

Ctx2->Connection->Disconnect();

}

}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}

IdTCPServer1->Active = false;
Button1->Enabled = true;
Form1->Button2->Enabled = true;
Form1->Button3->Enabled = true;
Form1->Button4->Enabled = true;
Form1->UpDown1->Enabled = false;
Form1->Button6->Enabled = false;
Form1->Edit1->Enabled = false;

}
//---------------------------------------------------------------------------

void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{


int i;

String tmpIP;


if (ClientID < 0)
{
//AContext->Connection->IOHandler->WriteLn("That ID is not valid");

AContext->Connection->Disconnect();
return;
}

TMyContext *Ctx = (TMyContext*) AContext;


TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];
if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID))
{

//AContext->Connection->IOHandler->WriteLn("That Name is already logged in");

AContext->Connection->Disconnect();
return;

}
}

Ctx->ClientID = ClientID;
TAddClientToUI *sync = new TAddClientToUI;
sync->IP = AContext->Connection->Socket->Binding->PeerIP;
sync->ClientID = ClientID;
sync->Ctx = AContext;
sync->Notify();

}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}

}
//--------------------------------------------------------------------------

void __fastcall TForm1::Button4Click(TObject *Sender)
{
int i;
String tmpstr;

TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx = (TMyContext*) List->Items[i];
//if (Ctx->ClientName == ClientName)
//{
tmpstr=Label11->Caption.Delete(2,1)+Label12->Caption.Delete(2,1);

Ctx->Queue->Add("<SHOW>"+tmpstr);
//break;
} //}

}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
TMyContext *Ctx = (TMyContext*) AContext;

TStringList *Queue = Ctx->Queue->Lock();
try
{
while (Queue->Count > 0)
{
try
{
AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
Queue->Delete(0);
//AContext->Connection->CheckForGracefulDisconnect();
String tmpString = AContext->Connection->IOHandler->ReadLn();
//if(!tmpString.Pos("<ONLINE>"))
//{
TAddMessageToUI *sync = new TAddMessageToUI;
sync->ClientID = Ctx->ClientID;
sync->Message = tmpString;
sync->Notify();
//}

}
catch (EIdException *E)
{
AContext->Connection->Disconnect();
}
}
//AContext->Connection->Disconnect();
}
__finally
{
Ctx->Queue->Unlock();
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::IdTCPServer1Disconnect(TIdContext *AContext)
{

TMyContext *Ctx = (TMyContext*) AContext;

if (Ctx->ClientID != -1)
{
TRemoveClientFromUI *sync = new TRemoveClientFromUI;
sync->ClientID = Ctx->ClientID;
sync->Ctx = AContext;
sync->Notify();
}

}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)
{
int i;

TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx = (TMyContext*) List->Items[i];
//if (Ctx->ClientName == ClientName)
//{

Ctx->Queue->Add("<TEST>") ;
//break;
} //}

}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}

}
//---------------------------------------------------------------------------

/*
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx = (TMyContext*) List->Items[i];


Ctx->Queue->Add("<ONLINE?>") ;

}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}

*/


looking forward to your further help
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 8, 2017 2:35 PM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

now another bigger issue is currently i just connected two client
device, and process will take up 29% around CPU usage in fast
computer, and in another a little slow computer, it will get to 100%,
this is big problem even we connect more clients

That is because the thread that is calling the OnExecute event is not sleeping
when the handler has nothing to do, so it is eating up CPU cycles.

The OnExecute event is called in a continuous loop for the lifetime of the
thread. It is the handler's responsibility to put the thread to sleep when
appropriate.

Most Indy servers are used in a command/response model, where the thread
is sleeping while it waits for the client to send a new command. But that
is not the case in your situation, so you need to manually make the thread
go to sleep when you don't have anything to do.

A simple way to do that is to call Sleep() when the queue is empty:

void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    bool isEmpty = false;
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        isEmpty = (Queue->Count == 0);
        if (!isEmpty)
        {
            do
            {
                AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
                Queue->Delete(0);
 
                String tmpString = AContext->Connection->IOHandler->ReadLn();
                ...
            }
            while (Queue->Count > 0);
        }
    }
    __finally
    {
        Ctx->Queue->Lock();
    }
 
    if (isEmpty)
        Sleep(1000);
}


Another option is to use a TEvent alongside your queue. Signal the TEvent
when data is in the queue, and reset the TEvent when the queue is empty.
Have OnExecute wait on the TEvent. That way, the thread will sleep while
the TEvent is not signaled, eg:

...
#include <IdSync.hpp>
#include <SyncObjs.hpp>
 
class TMyContext : public TIdServerContext
{
public:
    int ClientID;
    TIdThreadSafeStringList *Queue;
    TEvent *QueueHasData;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TIdContextThreadList *AList=0);
    __fastcall ~TMyContext();
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
    QueueHasData = new TEvent(NULL, true, false, "");
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
    delete QueueHasData;
}
 
...
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) != wrSignaled)
        return;
 
    TStringList *tmpQueue = new TStringList;
    try
    {
        TStringList *Queue = Ctx->Queue->Lock();
        try
        {
            tmpQueue->Assign(Queue);
            Queue->Clear();
            Ctx->QueueHasData->ResetEvent();
        }
        __finally
        {
            Ctx->Queue->Unlock();
        }
 
        for (int i = 0; i < tmpQueue->Count; ++i)
        {
            AContext->Connection->IOHandler->WriteLn(tmpQueue->Strings[i]);
            String tmpString = AContext->Connection->IOHandler->ReadLn();
            ...
        }
    }
    __finally
    {
        delete tmpQueue;
    }
}
 
...
 
void __fastcall TForm1::Button4Click(TObject *Sender)
{
     int i;
     String tmpstr;
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int i = 0; i < List->Count; ++i)
        {
            TMyContext *Ctx = (TMyContext*) List->Items[i];
 
            TStringList *Queue = Ctx->Queue->LockList();
            try
            {
                Queue->Add("<SHOW>"+tmpstr);
                Ctx->QueueHasData->SetEvent();
            }
            __finally
            {
                Ctx->Queue->UnlockList();
            }
        }
    }
    __finally
    {
        IdTCPServer1->Contexts->UnlockList();
    }
}


void __fastcall TForm1::FormCreate(TObject *Sender)

DO NOT use the OnCreate event in C++! It is a Delphi idiom that can cause
illegal behavior in C++. Use the actual class constructor instead.

TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];
Ctx2->Connection->Disconnect();
}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
IdTCPServer1->Active = false;

You DO NOT need to manually disconnect the clients before deactivating the
server. TIdTCPServer already does that internally for you. The ONLY reason
to loop through the clients before deactivation would be if you change the
call to TEvent::WaitFor() to use an INIFINITE timeout, then you could loop
through the clients to "wake" them up.

if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID))

Looks like YOU are the one making the mistake this time! Should be Ctx2->ClientID
;-)

catch (EIdException *E)
{
AContext->Connection->Disconnect();
}

You don't need that (plus, you should be catching exceptions by reference
instead of by pointer). TIdTCPServer already disconnects a client and stops
its thread if a server event handler throws an uncaught exception back into
the server (in which case, the server's OnException event will be triggered).

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx = (TMyContext*) List->Items[i];
Ctx->Queue->Add("<ONLINE?>") ;

}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}

That would be better handled in the OnExecute event instead of a TTimer in
the main UI thread. Let OnExecute keep track of the last time it received
a message from a client, and then send "<ONLINE?>" if no message has been
received for awhile. Let the client threads keep track of their own statuses.

...
 
class TMyContext : public TIdServerContext
{
public:
    ...
    TIdTicks IdleCheck;
    ...
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
    QueueHasData = new TEvent(NULL, true, false, "");
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
    delete QueueHasData;
}
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    AContext->Connection->IOHandler->ReadTimeout = 5000;
    Ctx->IdleCheck = Ticks64();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) == wrSignaled)
    {
        TStringList *tmpQueue = new TStringList;
        try
        {
            TStringList *Queue = Ctx->Queue->Lock();
            try
            {
                tmpQueue->Assign(Queue);
                Queue->Clear();
                Ctx->QueueHasData->ResetEvent();
            }
            __finally
            {
                Ctx->Queue->Unlock();
            }
 
            for (int i = 0; i < tmpQueue->Count; ++i)
            {
                AContext->Connection->IOHandler->WriteLn(tmpQueue->Strings[i]);
                String tmpString = AContext->Connection->IOHandler->ReadLn();
                ...
            }
 
            Ctx->IdleCheck = Ticks64();
        }
        __finally
        {
            delete tmpQueue;
        }
 
        return;
    }
 
    if (GetElapsedTicks(Ctx->IdleCheck) >= 30000)
    {
        AContext->Connection->IOHandler->WriteLn("<ONLINE?>");
        AContext->Connection->IOHandler->ReadLn();
        Ctx->IdleCheck = Ticks64();
    }
}


--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 10, 2017 8:50 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

now another bigger issue is currently i just connected two client
device, and process will take up 29% around CPU usage in fast
computer, and in another a little slow computer, it will get to 100%,
this is big problem even we connect more clients

That is because the thread that is calling the OnExecute event is not sleeping
when the handler has nothing to do, so it is eating up CPU cycles.

The OnExecute event is called in a continuous loop for the lifetime of the
thread. It is the handler's responsibility to put the thread to sleep when
appropriate.

Most Indy servers are used in a command/response model, where the thread
is sleeping while it waits for the client to send a new command. But that
is not the case in your situation, so you need to manually make the thread
go to sleep when you don't have anything to do.

A simple way to do that is to call Sleep() when the queue is empty:

void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
    bool isEmpty = false;
 
    TStringList *Queue = Ctx->Queue->Lock();
    try
    {
        isEmpty = (Queue->Count == 0);
        if (!isEmpty)
        {
            do
            {
                AContext->Connection->IOHandler->WriteLn(Queue->Strings[0]);
                Queue->Delete(0);
 
                String tmpString = AContext->Connection->IOHandler->ReadLn();
                ...
            }
            while (Queue->Count > 0);
        }
    }
    __finally
    {
        Ctx->Queue->Lock();
    }
 
    if (isEmpty)
        Sleep(1000);
}


Another option is to use a TEvent alongside your queue. Signal the TEvent
when data is in the queue, and reset the TEvent when the queue is empty.
Have OnExecute wait on the TEvent. That way, the thread will sleep while
the TEvent is not signaled, eg:

...
#include <IdSync.hpp>
#include <SyncObjs.hpp>
 
class TMyContext : public TIdServerContext
{
public:
    int ClientID;
    TIdThreadSafeStringList *Queue;
    TEvent *QueueHasData;
 
    __fastcall TMyContext(TIdTCPConnection *AConnection, TIdYarn *AYarn, 
TIdContextThreadList *AList=0);
    __fastcall ~TMyContext();
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
    QueueHasData = new TEvent(NULL, true, false, "");
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
    delete QueueHasData;
}
 
...
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) != wrSignaled)
        return;
 
    TStringList *tmpQueue = new TStringList;
    try
    {
        TStringList *Queue = Ctx->Queue->Lock();
        try
        {
            tmpQueue->Assign(Queue);
            Queue->Clear();
            Ctx->QueueHasData->ResetEvent();
        }
        __finally
        {
            Ctx->Queue->Unlock();
        }
 
        for (int i = 0; i < tmpQueue->Count; ++i)
        {
            AContext->Connection->IOHandler->WriteLn(tmpQueue->Strings[i]);
            String tmpString = AContext->Connection->IOHandler->ReadLn();
            ...
        }
    }
    __finally
    {
        delete tmpQueue;
    }
}
 
...
 
void __fastcall TForm1::Button4Click(TObject *Sender)
{
     int i;
     String tmpstr;
 
    TList *List = IdTCPServer1->Contexts->LockList();
    try
    {
        for (int i = 0; i < List->Count; ++i)
        {
            TMyContext *Ctx = (TMyContext*) List->Items[i];
 
            TStringList *Queue = Ctx->Queue->LockList();
            try
            {
                Queue->Add("<SHOW>"+tmpstr);
                Ctx->QueueHasData->SetEvent();
            }
            __finally
            {
                Ctx->Queue->UnlockList();
            }
        }
    }
    __finally
    {
        IdTCPServer1->Contexts->UnlockList();
    }
}


void __fastcall TForm1::FormCreate(TObject *Sender)

DO NOT use the OnCreate event in C++! It is a Delphi idiom that can cause
illegal behavior in C++. Use the actual class constructor instead.

TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx2 = (TMyContext*) List->Items[i];
Ctx2->Connection->Disconnect();
}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
IdTCPServer1->Active = false;

You DO NOT need to manually disconnect the clients before deactivating the
server. TIdTCPServer already does that internally for you. The ONLY reason
to loop through the clients before deactivation would be if you change the
call to TEvent::WaitFor() to use an INIFINITE timeout, then you could loop
through the clients to "wake" them up.

if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID))

Looks like YOU are the one making the mistake this time! Should be Ctx2->ClientID
;-)

catch (EIdException *E)
{
AContext->Connection->Disconnect();
}

You don't need that (plus, you should be catching exceptions by reference
instead of by pointer). TIdTCPServer already disconnects a client and stops
its thread if a server event handler throws an uncaught exception back into
the server (in which case, the server's OnException event will be triggered).

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
TList *List = IdTCPServer1->Contexts->LockList();
try
{
for (int i = 0; i < List->Count; ++i)
{
TMyContext *Ctx = (TMyContext*) List->Items[i];
Ctx->Queue->Add("<ONLINE?>") ;

}
}
__finally
{
IdTCPServer1->Contexts->UnlockList();
}
}

That would be better handled in the OnExecute event instead of a TTimer in
the main UI thread. Let OnExecute keep track of the last time it received
a message from a client, and then send "<ONLINE?>" if no message has been
received for awhile. Let the client threads keep track of their own statuses.

...
 
class TMyContext : public TIdServerContext
{
public:
    ...
    TIdTicks IdleCheck;
    ...
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
    QueueHasData = new TEvent(NULL, true, false, "");
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
    delete QueueHasData;
}
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    AContext->Connection->IOHandler->ReadTimeout = 5000;
    Ctx->IdleCheck = Ticks64();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) == wrSignaled)
    {
        TStringList *tmpQueue = new TStringList;
        try
        {
            TStringList *Queue = Ctx->Queue->Lock();
            try
            {
                tmpQueue->Assign(Queue);
                Queue->Clear();
                Ctx->QueueHasData->ResetEvent();
            }
            __finally
            {
                Ctx->Queue->Unlock();
            }
 
            for (int i = 0; i < tmpQueue->Count; ++i)
            {
                AContext->Connection->IOHandler->WriteLn(tmpQueue->Strings[i]);
                String tmpString = AContext->Connection->IOHandler->ReadLn();
                ...
            }
 
            Ctx->IdleCheck = Ticks64();
        }
        __finally
        {
            delete tmpQueue;
        }
 
        return;
    }
 
    if (GetElapsedTicks(Ctx->IdleCheck) >= 30000)
    {
        AContext->Connection->IOHandler->WriteLn("<ONLINE?>");
        AContext->Connection->IOHandler->ReadLn();
        Ctx->IdleCheck = Ticks64();
    }
}


--
Remy Lebeau (TeamB)


Hi Remy :

thanks,QueueHasData = new TEvent(NULL, true, false, "") need to be changed to QueueHasData = new TEvent(NULL, true, false, "",false) in XE4; and i test ok, but the following can't be compiled succefully(in bottom source code),
the following items can't be compiled , which headfile they are in ?

Ticks64();(it is GetTickCount? )

TIdTick (i think it is long int for IdleCheck?)

GetElapsedTicks();

//--------------------------------------------------------------------

<div class="jive-quote">...
 
class TMyContext : public TIdServerContext
{
public:
    ...
    TIdTicks IdleCheck;
    ...
};
 
__fastcall TMyContext::TMyContext(TIdTCPConnection *AConnection, TIdYarn 
*AYarn, TIdContextThreadList *AList)
    : TIdServerContext(AConnection, AYarn, AList), ClientID(-1)
{
    Queue = new TIdThreadSafeStringList;
    QueueHasData = new TEvent(NULL, true, false, "");
}
 
__fastcall TMyContext::~TMyContext()
{
    delete Queue;
    delete QueueHasData;
}
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    AContext->Connection->IOHandler->ReadTimeout = 5000;
    Ctx->IdleCheck = Ticks64();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) == wrSignaled)
    {
        TStringList *tmpQueue = new TStringList;
        try
        {
            TStringList *Queue = Ctx->Queue->Lock();
            try
            {
                tmpQueue->Assign(Queue);
                Queue->Clear();
                Ctx->QueueHasData->ResetEvent();
            }
            __finally
            {
                Ctx->Queue->Unlock();
            }
 
            for (int i = 0; i < tmpQueue->Count; ++i)
            {
                AContext->Connection->IOHandler->WriteLn(tmpQueue->Strings[i]);
                String tmpString = AContext->Connection->IOHandler->ReadLn();
                ...
            }
 
            Ctx->IdleCheck = Ticks64();
        }
        __finally
        {
            delete tmpQueue;
        }
 
        return;
    }
 
    if (GetElapsedTicks(Ctx->IdleCheck) >= 30000)
    {
        AContext->Connection->IOHandler->WriteLn("<ONLINE?>");
        AContext->Connection->IOHandler->ReadLn();
        Ctx->IdleCheck = Ticks64();
    }
}

//----------------------------------------------------------------------------------------------------------------------
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 10, 2017 12:03 PM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

thanks,QueueHasData = new TEvent(NULL, true, false, "") need to
be changed to QueueHasData = new TEvent(NULL, true, false, "",false)
in XE4

That is an old Delphi compiler bug. The last parameter of the TEvent constructor
(UseCOMWait) is optional and has a default value of false, but the Delphi
compiler did not emit the default value when it generated the SyncObjs.hpp
file:

Delphi:

constructor Create(EventAttributes: PSecurityAttributes; ManualReset,
  InitialState: Boolean; const Name: string; UseCOMWait: Boolean = False); 
overload;


C++

__fastcall TEvent(Winapi::Windows::PSecurityAttributes EventAttributes, bool 
ManualReset, bool InitialState, const System::UnicodeString Name, bool UseCOMWait)/* 
overload */;


That bug was fixed in a later version of the Delphi compiler.

and i test ok, but the following can't be compiled succefully (in bottom
source code), the following items can't be compiled , which headfile they
are in ?

They are all in Indy's IdGlobal.hpp header file.

Ticks64();(it is GetTickCount? )

Ticks64() is an Indy function that was introduced a few years ago. It uses
GetTickCount64() internally (on Windows versions that have it, otherwise
it falls back to GetTickCount()).

Indy also has a deprecated Ticks() function (which is just a wrapper for
GetTickCount() only).

TIdTick (i think it is long int for IdleCheck?)

No, actually TIdTicks is an 'unsigned __int64'. It was added when Ticks64()
was introduced.

GetElapsedTicks();

Same as above.

Most Indy headers already include IdGlobal.hpp for you. But you can always
include it manually in your own code, too. But either way, if IdGlobal.hpp
is included in your code and you are still getting errors about unknown identifers
and such, then you are likely using an outdated version of Indy and need
to upgrade.

--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 11, 2017 7:47 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

thanks,QueueHasData = new TEvent(NULL, true, false, "") need to
be changed to QueueHasData = new TEvent(NULL, true, false, "",false)
in XE4

That is an old Delphi compiler bug. The last parameter of the TEvent constructor
(UseCOMWait) is optional and has a default value of false, but the Delphi
compiler did not emit the default value when it generated the SyncObjs.hpp
file:

Delphi:

constructor Create(EventAttributes: PSecurityAttributes; ManualReset,
  InitialState: Boolean; const Name: string; UseCOMWait: Boolean = False); 
overload;


C++

__fastcall TEvent(Winapi::Windows::PSecurityAttributes EventAttributes, bool 
ManualReset, bool InitialState, const System::UnicodeString Name, bool UseCOMWait)/* 
overload */;


That bug was fixed in a later version of the Delphi compiler.

and i test ok, but the following can't be compiled succefully (in bottom
source code), the following items can't be compiled , which headfile they
are in ?

They are all in Indy's IdGlobal.hpp header file.

Ticks64();(it is GetTickCount? )

Ticks64() is an Indy function that was introduced a few years ago. It uses
GetTickCount64() internally (on Windows versions that have it, otherwise
it falls back to GetTickCount()).

Indy also has a deprecated Ticks() function (which is just a wrapper for
GetTickCount() only).

TIdTick (i think it is long int for IdleCheck?)

No, actually TIdTicks is an 'unsigned __int64'. It was added when Ticks64()
was introduced.

GetElapsedTicks();

Same as above.

Most Indy headers already include IdGlobal.hpp for you. But you can always
include it manually in your own code, too. But either way, if IdGlobal.hpp
is included in your code and you are still getting errors about unknown identifers
and such, then you are likely using an outdated version of Indy and need
to upgrade.

--
Remy Lebeau (TeamB)

Hi Remy :
still getting error on TIdTick ,Ticks64() and GetElapsedTicks() after i include IdGlobal header file, i will try after i upgrade indy version , but i can finish my project using first option u provide,anyway thank you very much again for your help,

i will try and follow the second solution after i finish current urgent project, the second solution seem better, but need to some time to research how to upgrade current version to lastest version.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 11, 2017 11:14 AM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

still getting error on TIdTick ,Ticks64() and GetElapsedTicks()
after i include IdGlobal header file

Then you are using an old version of Indy and should upgrade. If that is
not an option, then just adjust the code I gave you earlier to use GetTickCount()
instead:

class TMyContext : public TIdServerContext
{
public:
    ...
    DWORD IdleCheck;
    ...
};
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    Ctx->IdleCheck = GetTickCount();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) == wrSignaled)
    {
        ...
        Ctx->IdleCheck = GetTickCount();
        return;
    }
 
    ...
 
    DWORD CurTicks = GetTickCount();
    DWORD Elapsed = (CurTicks >= Ctx->IdleCheck)
      ? (CurTicks - Ctx->IdleCheck)
      : (((MAXDWORD - Ctx->IdleCheck) + CurTicks) + 1);
 
    if (Elapsed >= 30000)
    {
        ...
        Ctx->IdleCheck = GetTickCount();
    }
}


--
Remy Lebeau (TeamB)
Chen Mingzhu

Posts: 14
Registered: 6/25/09
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 12, 2017 8:39 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Chen wrote:

still getting error on TIdTick ,Ticks64() and GetElapsedTicks()
after i include IdGlobal header file

Then you are using an old version of Indy and should upgrade. If that is
not an option, then just adjust the code I gave you earlier to use GetTickCount()
instead:

class TMyContext : public TIdServerContext
{
public:
    ...
    DWORD IdleCheck;
    ...
};
 
...
 
void __fastcall TForm1::IdTCPServer1Connect(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    ...
 
    Ctx->IdleCheck = GetTickCount();
}
 
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
    TMyContext *Ctx = (TMyContext*) AContext;
 
    if (Ctx->QueueHasData->WaitFor(1000) == wrSignaled)
    {
        ...
        Ctx->IdleCheck = GetTickCount();
        return;
    }
 
    ...
 
    DWORD CurTicks = GetTickCount();
    DWORD Elapsed = (CurTicks >= Ctx->IdleCheck)
      ? (CurTicks - Ctx->IdleCheck)
      : (((MAXDWORD - Ctx->IdleCheck) + CurTicks) + 1);
 
    if (Elapsed >= 30000)
    {
        ...
        Ctx->IdleCheck = GetTickCount();
    }
}


--
Remy Lebeau (TeamB)

Hi Remy:

as you suggested, i used GetTickCount(), now it seem work fine, i will do more further test.

anyway ,thanks
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: how can send data to clients outside indy server excute event  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 8, 2017 1:46 PM   in response to: Chen Mingzhu in response to: Chen Mingzhu
Chen wrote:

but after further testing , i found that in the following both conditions
,
software didn't function properly:

1. if ((Ctx2 != Ctx) && (Ctx->ClientID == ClientID)) , as i
mentioned in last post, in this condition, if another Client device is
set to same ID numer as one device which already log in ( for example
both Client devices ID number are set to 1), this one can still log
in

If you look at the latest example I gave you, I had corrected that mistake,
per your earlier advise.

if i set different ID number to each client, both clients log in, then
i power down or take off net cable(CAT5/6 cable) from one client,
and power on or plug CAT5 cable again, this client got connect
and disconnect repeatly

When you pull out the cable, or otherwise doing ANYTHING that kills a connection
abnormally, the OS MAY NOT know the connection is gone right away (TCP
is designed to be resilient to network outages, and is capable of re-connecting
lost connections automatically within a certain time frame). It can take
awhile for the OS to invalidate the dead connection and start reporting I/O
errors on it. Until that happens, Indy has no indication that the old connection
is dead, and so will not stop that client's thread and remove the client
from the Contexts list. Until it does so, the current example code will
just disconnect a client's new connections until its old connection eventually
goes away.

If that does not suit your needs, then you need to change the code accordingly.
Remember, it was just an EXAMPLE, not LAW. You can Disconnect() the old
connection instead of the new connection. Or just remove the validation
altogether and let a client have multiple connections to the server, etc.

Either way, you should consider adding a heartbeat message to your communications
protocol (or at least turn on TCP keep-alives on the connections) so that
dead connections can be cleaned out periodically.

if i power down or take CAT5 cable from client, OnDisconnect event
never be triggered

It will, EVENTUALLY, but it can take awhile, depeding on the OS. That is
why you should be using timeouts in your own code.

it seem server don't know that client is disconnected in this two cases,

Correct, because the OS itself does not know yet.

only if you call AContext->Connection->Disconnect() explicity , then
OnDisconnect event is triggered

Yes, that will trigger it sooner rather than later.

does this mean i have to use the following way to check if client
device still connect

Yes, you should (if feisible to add it to your protocol). Though, I wouldn't
use an actual timer. I woud require the device to send heartbeats to the
server periodically, which the server responds to (not the other way around).
Have the server keep track of the last time a message was received from
the client, and have OnExecute check that periodically. If too long a time
elapses between messages, just Disconnect() the client.

--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02