Solved

BCB: Multithreaded ServerClientSockets: Build a (visual) list so I can send 2 specific connected Client(s)? :(

Posted on 2003-11-25
8
917 Views
Last Modified: 2012-06-27
Hi Experts,

I'm using Borland C++ Builder(6). Relatively new to programming, but excited!

I'm enthusiastically working on a chat server/client that works with standard (T)Server-/(T)ClientSockets. I managed to make it multithreaded, so when a Client Connects;


// --------


void __fastcall TMain::ServerSocketGetThread(TObject *Sender,
      TServerClientWinSocket *ClientSocket, TServerClientThread *&SocketThread)
{
  // New connection -> new Thread...
  SocketThread = new TMyServerThread(false, ClientSocket);
}


// --------


void __fastcall TMyServerThread::ClientExecute(void)
{
   // make sure connection is active...
   while (!Terminated && ClientSocket->Connected)
   {
      try
      {
         // Now, use TWinSocketStream to read or write information
         // over a blocking socket connection...
         TWinSocketStream *pStream = new TWinSocketStream(ClientSocket, CLIENTWAITTIME);

         try
         {
          etc...


Now the real question is how I can make a (visual) List of all the connected Clients and how can I send data to a specific Client. (Thru somekind of protocol that I can make.)

I think I should use std::map/pointers... but I'm lost... I don't anything (yet) about that, except that Pointers of course 'Point' to a specific place in memory.


Does anybody have/know an example/a tutorial or maybe some useful code(with explanation)/links?

Best regards, Roland(BE).
0
Comment
Question by:Roland1980
  • 5
  • 3
8 Comments
 
LVL 16

Expert Comment

by:George Tokas
ID: 9826218
Drop a ListView Item and a Timer.
Populate it using the OnTimer:
int Connections = ServerSocket->Socket->ActiveConnections;//Number of connections to server socket.
for(int x = 0; x < Connections; x++)
{
AnsiString RemoteHost = ServerSocket->Socket->Connections[x]->RemoteHost;
AnsiString RemoteIP     = ServerSocket->Socket->Connections[x]->RemoteAddress;
//Add those to ListView
}
After that you will have the info you want in the ListView.
When you select a line you selecting the socket coresponding to the client.
You pick up the info you need and after a loop like that i.e.:
for(int i = 0; i < ServerSocket->Socket->ActiveConnections; i++)
{
   if(RemoteIP ==  ServerSocket->Socket->Connections[i]->RemoteAddress)
  {
  //Found the Socket. Send what you want.
  }
}

Feel free to ask if you didn't understand.
gtokas.
0
 

Author Comment

by:Roland1980
ID: 9828577
Hi gtokas,

First of all thx. for responding!


I knew that it was possible with a loop of "ServerSocket->Socket->Connections[x]" to get the connections, but your example has a few disadvantages, that is, it has for what I want to create;

-When, let's say, Client[1] disconnects and you had 5 connected Clients, Client[0] stays [0], but Client[2] becomes [1], [3] becomes [2] etc. So you then have to Repopulate the list...

-(in your example) The ReadOut-Event is Timed. I could also trigger it on disconnect/connect, but the previous 'problem' stays. Plus I want to handle a lot of Clients and think that this is not the best way to handle it.

Then... I can do this without loops..? so;
Incomming connection->To list in memory(->for test purposes also build a visual listview or something)
Outgoing data->Get specific Client from list->Send to specific Client

So I really want a 'solid' list. Someone form the Borland-crew on their newsgroups adviced me to take a look at std::map/pointers... He sad that they should do the trick, but unfortunately he didn't respond on my question to advice me on that or to give me tutorials/examples/links/etc. Like I said, I'm still learning (a lot).

I know I can also use the data property of the Socket, but I think(I'm not an expert) that the solution above maybe is the best.


Btw: I want to 'get' Clients by their ID that they send on their first CONnection-string.
(I have a remote sql-server running where new Clients can be registered with their ID, md5(Password) and a bit of User-Info <- that part is working fine.)


Roland(BE).
0
 
LVL 16

Expert Comment

by:George Tokas
ID: 9831191
Ok.. Check this example out....:
/*******************************************************
This is an example of how to set up a server listening
to incomming Telnet connections. I've tried to make it
as well structured as possible with the possibility to,
without any major modifications, build up a fully
functional telnet server for whatever use there might be.
I have commented it heavily, but if there still are any
questions (or suggestions) mail them to karlpest@home.se

Please visit KTRK's webpage at http://www.ktrk.net or
http://ktrk.tripod.com

Coded by Karl Tillstr&#966;m 2000-10-11

*******************************************************/

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;



const Max_Connections = 3;  //Set how many sockets you wish to have open

struct Connected_User
{
    int SocketHandle;   //The Socket the user is connected to
    AnsiString Name;    //If you want to attatch a name to the user
    AnsiString Data;    //To get whole lines (look into ClientRead())
    AnsiString IP;      //The clients IP
    //Add more if needed, like TimeOut, PasswordAccepted, AccessLevel etc...
};

Connected_User User[Max_Connections];   //Handles the connected users
int ConnectionCount;    //Index used by User[]

void RemoveUser(TCustomWinSocket *Socket);      //Removes a user from a socket
int GetConnectionNo(int SocketHandle);  //Finds which User[] that represents the SocketHandle
void ClearUser(int SocketHandle);   //Clears the User[] for the specific user
void BroadCastMessage(AnsiString Message, int SkipUser);  //Use this to send a message to all connected users except SkipUser (SocketHandle)

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

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

void __fastcall TForm1::cmdCloseClick(TObject *Sender)
{
//Closes the Server Socket
Server->Active = false;
Memo1->Lines->Add("Server shut down!");
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ServerAccept(TObject *Sender,
      TCustomWinSocket *Socket)
{
if (Server->Socket->ActiveConnections-1  == Max_Connections)
    {
        //The server is full!
        Socket->SendText("Too many users!");
        Memo1->Lines->Add("OnAccept: Too many users");
        delete Socket;  //Free memory
        return;
    }



while (User[ConnectionCount].SocketHandle)    //Find free entrypoint
{
    ConnectionCount++;  //Unique indexes for the Connected users!
    if (ConnectionCount > Max_Connections)
        {
            ConnectionCount = 0;
        }
}

//Attatches the SocketHandle to the User[] in use
User[ConnectionCount].SocketHandle = Socket->SocketHandle;
//This is not how it's done when you are really naming the users
User[ConnectionCount].Name = "Guest" + IntToStr(Socket->SocketHandle);
Memo1->Lines->Add("OnAccept");



User[ConnectionCount].IP = Socket->RemoteAddress;
Memo1->Lines->Add(User[ConnectionCount].IP + " connected!");


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


void __fastcall TForm1::ServerClientDisconnect(TObject *Sender,
      TCustomWinSocket *Socket)
{

ClearUser(Socket->SocketHandle);
Memo1->Lines->Add("ClientDisconnected " + Socket->SocketHandle);
delete Socket;  //free memory (dunno if it's needed here...)
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerClientError(TObject *Sender,
      TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode)
{

switch (ErrorCode)
{
    case 10054 :
        //Client closed the connection
        RemoveUser(Socket);   //Removes the user
        Memo1->Lines->Add("Client closed connection");
        break;
    //add more error handlers as they are needed (e.g case <ErrorCode>)
    default :
        RemoveUser(Socket);
}

ErrorCode = 0;  //Prevents an error raise
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerClientRead(TObject *Sender,
      TCustomWinSocket *Socket)
{
//Occurs when a connected user sends some text to the server!

AnsiString Data = Socket->ReceiveText();    //Get the sent data!
int UserNo = GetConnectionNo(Socket->SocketHandle); //Finds out who sent it
if (UserNo == -1)
{
    //This shouldn't happen ever, but if it does you'll at least know about it
    ShowMessage("User cannot be found!");
    return;
}

/* Telnet users doesn't send complete lines at once,
therefore this will store all data in a temporary
AnsiString until a CRLF is received (\r\n)*/

User[UserNo].Data += Data;
if (User[UserNo].Data.AnsiPos("\r\n") == 0)
{
    //Not a complete line sent!
    return;
}
else
{
    //A complete Line sent!
    //SubString removes the ending CRLF ("\n\r") since we don't want them when we Parse out data!
    Memo1->Lines->Add("<" + User[UserNo].Name + "> " + User[UserNo].Data.SubString(1, User[UserNo].Data.Length() -2));
    //An example of how to use the BroadCastMessage function
    BroadCastMessage(User[UserNo].Data.SubString(1, User[UserNo].Data.Length() -2), Socket->SocketHandle);
    User[UserNo].Data = "";
}
       
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerClientWrite(TObject *Sender,
      TCustomWinSocket *Socket)
{
Memo1->Lines->Add("ClientWrite");

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerGetSocket(TObject *Sender, int Socket,
      TServerClientWinSocket *&ClientSocket)
{
    //GetSocket is the Socket->SocketHandle
    Memo1->Lines->Add("GetSocket " + IntToStr(Socket));
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerListen(TObject *Sender,
      TCustomWinSocket *Socket)
{
Memo1->Lines->Add("Listens on Port: " + IntToStr(Server->Port));

}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
//Opens the server (e.g Makes the server start Listening)
//Server->Active = true;

ConnectionCount = 1;    //Start counting on 1
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerGetThread(TObject *Sender,
      TServerClientWinSocket *ClientSocket,
      TServerClientThread *&SocketThread)
{
Memo1->Lines->Add("GetThread");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerThreadStart(TObject *Sender,
      TServerClientThread *Thread)
{
Memo1->Lines->Add("OnThreadStart");

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ServerThreadEnd(TObject *Sender,
      TServerClientThread *Thread)
{
Memo1->Lines->Add("OnThreadEnd");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    //Updates the current # of connections
    Label1->Caption = IntToStr(Server->Socket->ActiveConnections) + "/" + IntToStr(Max_Connections);
}
//---------------------------------------------------------------------------

void ClearUser(int SocketHandle)
{
//Use this when a user gets disconnected for whatever reason
int UserNo = GetConnectionNo(SocketHandle);
if (UserNo == -1) return;

/*Don't forget to update this section is you add more stuff to the User struct!*/
    User[UserNo].SocketHandle = 0;
    User[UserNo].Name = "";
    User[UserNo].Data = "";
    User[UserNo].IP = "";
/*_____________________________________________________________________________*/
}
//---------------------------------------------------------------------------

int GetConnectionNo(int SocketHandle)
{
//Retrieves which connection that represents the SocketHandle in question
for (int i=0; i<Max_Connections; i++)
{
    if (User[i].SocketHandle == SocketHandle) return i;
}
return -1;
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
//Activates the Server socket (e.g Starts to listen)
Memo1->Lines->Add("Server started!");
Server->Open();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ServerClientConnect(TObject *Sender,
      TCustomWinSocket *Socket)
{
Memo1->Lines->Add("connect");

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

void BroadCastMessage(AnsiString Message, int SkipUser)
{

//Loops through the Socket->Connections array and sends the message to all users but SkipUser
//(You don't always want to broadcast the message to the user who sent it!)
//If you want to send to all the users including the one who sent it, call the function with SkipUser = -1 or something

for (int i=0;i<(Form1->Server->Socket->ActiveConnections); i++)
{
    if (Form1->Server->Socket->Connections[i]->SocketHandle == SkipUser)
    {
        // NOP
    }
    else
    {
        Form1->Server->Socket->Connections[i]->SendText(Message + AnsiString("\r\n"));
    }

}
}

void RemoveUser(TCustomWinSocket *Socket)
{
/*Clears the User[] and removes the Socket instance*/
    Form1->Memo1->Lines->Add("User " + User[GetConnectionNo(Socket->SocketHandle)].Name + " removed!");
    ClearUser(Socket->SocketHandle);
    delete Socket;

/*_________________________________*/
}
void __fastcall TForm1::cmdChangePortClick(TObject *Sender)
{
if (Server->Active == true)
{

 if (Application->MessageBox("Server must be shut down in order to change Listening port! \n Do you wish to continue?", "Temporarily shut down server?", MB_OKCANCEL + MB_DEFBUTTON1) == IDCANCEL)
   return;

    //Server must be shut down to be able to change port!
    cmdClose->Click();
    Server->Port = StrToInt(txtPort->Text);
    Memo1->Lines->Add("Listening port changed to " + txtPort->Text);
    Button1->Click();
}
else
{
    Server->Port = StrToInt(txtPort->Text);
        Memo1->Lines->Add("Listening port changed to " + txtPort->Text);
}

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

0
 

Author Comment

by:Roland1980
ID: 9840362
Hi Gtokas,

Thx again of course for your time, it is really appreciated.


The example show greatly how a Server can be set up and how a direct connection from a Client can be processed and how data can be send directly back to that Client. Also how they can be identified (with SocketHandle) and how to Broadcast to all(with or without skipping a Client) thru a loop.

But... I want the Server to be a gateway for the Clients;
-Client A Connects and is verified, the Server send Client A's Online_Status to all other Online Clients
-Client A now Sends a message (thru the Server) to Client B...


Not much real examples can be found unfortunately, but from the things that I could find and read I was thinking of two possibilities maybe?

1.
For each connection I create a separate Thread with a ServerClientSocket:

void __fastcall TMyServerThread::ClientExecute(void)
{
TWinSocketStream *pStream = new TWinSocketStream(ClientSocket, ClientWAITTIME);"

...shouldn't it then be possible to send something directly to a specific Client?



2.
Can I, and how, make the Socket parameter one of the parameters of the functions?


I have learned a lot the last few days, but still haven't found a real solution.

I know I can do this;
>  // Identify Clients by setting a Data property...
>  for(int i = 0; i < ServerSocket->Socket->ActiveConnections; i++)
>  {
>     if( ID ==  (int)ServerSocket->Socket->Connections[i]->Data)
>    {
>    //Found the Socket. Send what you want.
>    }


But I really want to have a solution without that ever-returning Loop :|.

Roland.
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 16

Expert Comment

by:George Tokas
ID: 9842179
What I tried 2 explain is that ANYWAY each client connection represented to the server seperatetly.
When you know the info of each socket (server side ) then you know who is trying 2 communicate and send packets you need.
Of course its NOT an easy task.
My projects using sockets working with no blocking sockets and by default each socket (server side ) use its own thread.
I'm proccessing the messages at the  Socket->OnRead event.
You will need loops i.e. when you want 2 send a packet to a specific Client because you want to do something without first the client asks anything from server.
You need to know:
1. if it is online
2. the ipaddress and port
3. send the packet.
Thats why you will need loops.

Hope it helps a bit.
gtokas.
0
 
LVL 16

Accepted Solution

by:
George Tokas earned 140 total points
ID: 9842188
Btw..
There is a chat example in BCB package and its located at:
CBuilderx\examples\internet\chat.
Did u check it out??
Also a good chat program is from Calvert's "C++ Builder3 Unleashed" book.
I think the source can be found online.

Regards
gtokas.
0
 

Author Comment

by:Roland1980
ID: 9892708
(checking out the chat-example was the 1st thing I did, but that's just so simple, didn't help)

Wasn't exactly what I was looking for, but I found it now anyway after days of searching te web. I want to give you the points for your effort.

Roland.
0
 
LVL 16

Expert Comment

by:George Tokas
ID: 9896095
I hope those I wrote helped you.
Many times the strategy we choose for client/server applications is the key to build the app...
Watch out for bugs in the code!!!
thx..

gtokas.

PS. The example I posted was the most clever one as a thought from all I searched..
Add your ideas and ideas of others and you will came up with a very robust server.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Templates For Beginners Or How To Encourage The Compiler To Work For You Introduction This tutorial is targeted at the reader who is, perhaps, familiar with the basics of C++ but would prefer a little slower introduction to the more ad…
Introduction This article is the first in a series of articles about the C/C++ Visual Studio Express debugger.  It provides a quick start guide in using the debugger. Part 2 focuses on additional topics in breakpoints.  Lastly, Part 3 focuses on th…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

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

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

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now