Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Network Administrator

0.00/5 (No votes)
19 Dec 2004 6  
The article shows how you can work with a multithreading client server application and administrate computers on a network.

Introduction

The program I will present is a simple application that will point out certain little things that usually you cannot handle while programming a client server application. The application client waits for connection from the computers that are on the network and which are configured to connect to that certain computer as I will show later on. The tricky problems that I've achieved with this program were making the multithreading server and client work in parallel, and I think that I've also achieved some synchronization of the two. There is also the fact that it helps you with many issues regarding your computer (like hiding the desktop or the taskbar or Start button).

Background

The main ideas that I need to present about my article include, first of all, the IP retrieving from the connected computer, the threads work in parallel to synchronize the packet sending, and finally the miscellaneous like retrieving processes, hiding tray...

Using the code

First, I will start by presenting the Server whose task is to try to connect to a certain local network IP that you configure it on. All we have to do is make some basic initialization:

InitCommonControls(); 
hInst=hInstance; //get handle of the current instance to use it later

hicon=(HICON)LoadImage(. . . .);

Now, after we have our icon loaded and we have the handle to the instance in the hInst variable declared as a global, we have to check if the application is running for the first time. The way to do this is to check for a certain key in the registry where we will store later the info about the remote host. If the key and value exists then the application is not running for the first time, otherwise it runs for the first time and we have to make a default host set to 127.0.0.1.

HKEY Regentry;
DWORD dispos,dwSize,dwType;
char primul[1024];

RegCreateKeyEx(HKEY_LOCAL_MACHINE,"Software\\RemoteHost" . .. . . );

RegQueryValueEx(Regentry, "host", NULL, &dwType, 
               (unsigned char*)&primul, &dwSize);
if(RegQueryValueEx(Regentry,"host", NULL, &dwType, 
                  (unsigned char*)&primul, &dwSize)!=ERROR_SUCCESS)
//the aplication runs for the first time 

{
  //initialize the host that will be used later from 

  //the reg when enumerating the computers on the network

  char host[100];
  strcpy(host,"localhost");
  RegSetValueEx(Regentry,"host",NULL,REG_SZ, 
             (unsigned char*)&host,strlen(host)+1);
  RegCloseKey(Regentry);
}

Now, we have to start our first thread which will monitor if a certain key combination from the keyboard is pressed. If that certain key combination is pressed then a dialog will appear and ask you to enter the host of the computer on the network which has the client.

AfxBeginThread(Configure,0)
{
begin:
  while(true)
  {
    if((GetAsyncKeyState(VK_CONTROL)<0)&&((GetAsyncKeyState(VK_MENU)<0))
        &&((GetAsyncKeyState(VK_SHIFT)<0))&&((GetAsyncKeyState('G')<0)))
    {
      DialogBox(0,MAKEINTRESOURCE(IDD_DIALOG1),0,DLGPROC(fereastra));
      goto begin;
    }
    Sleep(1);
  }
  return 0;
}

Importantly, remember that this is a thread that will monitor if these keys are pressed, and if they are, a dialog will come up and you will enter the host in an edit box. After pressing OK, the host will be saved in the registry as the default host to connect to.

case IDOK:
  char host[1024];
  GetDlgItemText(hdlg,IDC_EDIT1,host,sizeof(host));
  if (strcmp(host,"")==0)
  {
    MessageBox(NULL,"Error no host inserted","",MB_OK);
    break;
  }
  HKEY Regentry;
  DWORD dispos;

  RegCreateKeyEx(HKEY_LOCAL_MACHINE,"Software\\RemoteHost",. . . .. . );
  if (Regentry==NULL)
  {
    MessageBox(NULL,"Error setting host","",MB_OK);
    break;
  }
  RegSetValueEx(Regentry,"host",NULL,REG_SZ, 
               (unsigned char*)&host,strlen(host)+1);
  RegCloseKey(Regentry);
  MessageBox(NULL,"Host succesfully saved","",MB_OK);
  EndDialog(hdlg,0);
  break;

Now, let's go on with the code from the WinMain. After starting this thread, we have to enumerate all computers on the network, and if a certain computer matches the name stored in the registry, then get it's IP and try to connect, otherwise sleep a few milliseconds and try again.

again:
  //we have to set a tag from where 

  //we have to start the process of reconnection if 

  //the computer was not found or just failed


  GetIps();
  //I wil explain later this procedure 

  //which enumerates the computer on the lo

  //cal network and if any mathces the one get the IP

  // in the as variable which is a char *


  Sleep(100);

  WSADATA wsaData;
  struct hostent *hp;
  unsigned int addr;
  struct sockaddr_in server;
  int wsaret=WSAStartup(0x101,&wsaData);

  if(wsaret)
    return 0;

  strcpy(text,as);
  //..as I said the ip is stored in the as variable, 

  //if no computer was found the ip will be 127.0.0.1 or localhost



  //if this is going to be repetitive cycle 

  //we have to give it some time to sleep

  Sleep(100);

  conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  //init the socket


  if(conn==INVALID_SOCKET)
    goto aici;
  //if any errors we do not return because the server 

  //will stop but we go to the "again" tag 


  if(inet_addr(text)==INADDR_NONE)
  {
    hp=gethostbyname(text);
  }
  else
  {
    addr=inet_addr(text);
    hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
  }

  if(hp==NULL)
  {
    closesocket(conn);
    goto aici;
  }

  server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
  server.sin_family=AF_INET;
  server.sin_port=htons(1100);

  if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
  {
aici:
    closesocket(conn);
    goto again;
    //if we cannot connect the we also go to again

  }

  //if we have a succesful connection the we start 

  //a thread for the client and give it as

  //parameter the connected socket.


  AfxBeginThread(Client,(LPVOID)conn);

  //and ofcourse we need an infinite cycle 

  //for the aplication not to close after we connect

  while (1)
  {
    Sleep(1);
  }
  return 0;
}

Now, I have to tell you and present the GetIps() void. The procedure just uses the WNetOpenEnum and WNetEnumResource functions to enumerate.

  CString strTemp;
  struct hostent *host;
  struct in_addr *ptr; // To retrieve the IP Address 


  DWORD dwScope = RESOURCE_CONTEXT;
  NETRESOURCE *NetResource = NULL;
  HANDLE hEnum;

  /*
  The WNetOpenEnum function starts an enumeration 
  of network resources or existing connections. We have to continue 
  the enumeration by calling the WNetEnumResource function.
  */


  WNetOpenEnum( dwScope, NULL, NULL, 
             NULL, &hEnum );
  //but first we have to get ready to also 

  //take the ip of the computer that matches 

  //the host from the registry

  WSADATA wsaData;
  WSAStartup(MAKEWORD(1,1),&wsaData);

  if ( hEnum )
  {
    DWORD Count = 0xFFFFFFFF;
    DWORD BufferSize = 2048;
    LPVOID Buffer = new char[2048];
    //this is the buffer where 

    //the names will come as an array of

    //LPNETRESOURCE variables


    WNetEnumResource( hEnum, &Count, Buffer, &BufferSize );
    NetResource = (NETRESOURCE*)Buffer;

    char szHostName[200];
    unsigned int i;

    GetModuleFileName(NULL,as,sizeof(as));

    HKEY Regentry ;
    DWORD dispos,dwSize,dwType;

    RegCreateKeyEx(HKEY_LOCAL_MACHINE,"Software\\RemoteHost",. . .. );
    if (Regentry==NULL)
      goto papa; //exit the void


    RegQueryValueEx(Regentry,"host" , NULL, 
           &dwType, (unsigned char*)&as, &dwSize);
    RegQueryValueEx(Regentry,"host" , NULL, 
           &dwType, (unsigned char*)&as, &dwSize);

    char *host1;
    host1= _strupr( _strdup( as ) );

    //start the enumeration of the arrays from the buffer

    for ( i = 0; i < BufferSize/sizeof(NETRESOURCE); i++, NetResource++ )
    {
        if ( NetResource->dwUsage ==RESOURCEUSAGE_CONTAINER 
          && NetResource->dwType == RESOURCETYPE_ANY )
        {
            if ( NetResource->lpRemoteName )
            {
              //the name retreived will be given like this \\remote

              //so we will have to look for the \\ caracters and when found

              //copy what's in the right into a string buffer

              CString strFullName = 
                    NetResource->lpRemoteName;
              if ( 0 == strFullName.Left(2).Compare("\\\\") )
                strFullName = strFullName.Right(strFullName.GetLength()-2);
              gethostname( szHostName, strlen( szHostName ) );
              host = gethostbyname(strFullName);

              //now the ip retreiving
              //after having the host
              if(host == NULL) continue;
              ptr = (struct in_addr *) host->h_addr_list[0];

              int a = ptr->S_un.S_un_b.s_b1;  //127.
              int b = ptr->S_un.S_un_b.s_b2;  //0.
              int c = ptr->S_un.S_un_b.s_b3;  //0.
              int d = ptr->S_un.S_un_b.s_b4;  //1

              strTemp.Format("%d.%d.%d.%d",a,b,c,d);

              if(strcmp(strFullName,as)==0)
              {
                strcpy(as,strTemp.GetBuffer(sizeof(strTemp)));
              }
              //copy it in the as buffer to use it to connect
            }
        }
    }
    delete Buffer;
    WNetCloseEnum( hEnum ); 
  }
  papa: //out
  WSACleanup();
  return 1;
}

Now to tell you what the server can do and also explain the client thread. As you've seen, all that the WinMain does is it tries to connect to a certain computer on the network, the computer whose name is in the registry. Now, after we have a successful connection, we have to begin the client thread which will interpret the messages sent from the client. The first thing we should do is send a short message to the client so it will know that the connection was completed successfully.

conn=(SOCKET)pParam;
char buff[1024];
send(conn,"Gabby",6,0);
int n;
n=recv(conn,buff,sizeof(buff),0);
if ((n==0)||(n==SOCKET_ERROR))
    ExitThread(0);

Now after we receive the message from the client, the client will need the IP of the local computer which he is connected to, so we will send the IP. First, we have to get the host, and then turn it into a four dotted char IP address with inet_ntoa().

char text[100];
struct hostent *h; //structure that will hold info on th ip address


gethostname(text,sizeof(text)); //get local host's name

h=gethostbyname(text); //get the info on the host

char * ip;
ip=inet_ntoa(*((struct in_addr *)h->h_addr));
//convert it to an ip address

//and send it


char detr[1024];
strcpy(detr,text);
strcat(detr," ");
strcat(detr,ip);
recv(conn,buff,sizeof(buff),0);
send(conn,detr,strlen(detr),0);

Now, we have to enter a so called infinite cycle that will work the messages received from the client. If any errors occur then we will exit the thread and the client will notice because it won't receive the ping from the server. Very importantly, know that if we want to receive a text from the client, we should always set the memory of the buffer in which we receive 0. So...

while (1)  //the infinite cycle

{
  ZeroMemory(buff,sizeof(char [1024])); //set it to 0

  n=recv(conn,buff,512,0);
  if ((n==SOCKET_ERROR)||(n==0))
  {
    reset: //this tag will be very usefull later it resets the server

    char serv[1024];
    GetModuleFileName(NULL,serv,sizeof(serv));
    ShellExecute(NULL,"open",serv,NULL,NULL,SW_SHOW);

    _exit(0);
    return 0;
  }
  /* From time to time the client will send 
  the char ping to the server and waits from
  the pong reply. If it does not receives it 
  or the socket has any errors will 
  consider the server disconnected.
  */

  if (strcmp(buff,"ping")==0)
  {
    Sleep(5); //it is very imporant to know that 

    //before sending any packets of info

    //the computer should sleep a little. I will explain later.


    n=send(conn,"pong",5,0);
}
if ((n==SOCKET_ERROR)||(n==0)) goto reset;

This way, all the communication is done. Now the essence of the thread is, giving info on the existing files on the computer, and of course, the synchronization.

So if the client wants to see the files on a certain computer it's connected to, it sends a string that the computer will interpret. The string for the files view is "files". When the client browses the files of the server, it can also make downloads and also delete any files or execute them. The thing is that if he has to do it only from this thread, then he will not be able to deliver any pings or give info on the computer. The things I did were: after the server receives the string "files", it opens another thread called FileClient() that connects to the client again through a totally different socket and port. Of course, the client opens a thread that accepts connection on that port. After it connects, it will create a thread similar to the client thread but it will wait for messages from the fileclient socket. So...

if ((StrStrI(buff,"files")!=NULL))
{
  Sleep(50);
  send(conn,"OK",3,0);
  fsvr=CreateThread(NULL,0,StartConn, 
       (LPVOID)text,0,0); //text being the ip

}

Now a little bit about synchronization. For example, if the server receives a message from the client that tells it to send the list of processes. After the client sends the message, if it has to do any initialization and the server immediately sends a string, the client might not receive it and that's why I let the server sleep every time it has to send something. It is very important because this happens very often if the connection speed is very good. Now about the file sending process. The server receives a string with a file name. Now the file might also be a directory or might not even exist if the user deletes it after the last refresh of file was made. The thing that we should do is check if the file exists, and then if it is a directory, if it is, send a string that will tell that, otherwise send it.

DWORD attrb;
attrb=GetFileAttributes(host);
if ((attrb==FILE_ATTRIBUTE_DIRECTORY)||(attrb==-1)||(attrb==17))
{
   //17 means that the file is a customized directory 

   //with a different picture and stuff 

   Sleep(30);
   n=send(connfile,"unavalaible",11,0); //send the unavalaible string

   if ((n==SOCKET_ERROR)|| (n==0)) goto reset;
}

If the file is OK, tell the client that. Now we have to open it and tell the client the file's length, and then send it by reading from the file and sending to the client that will write to the file whatever it gets until the file is all sent.

else
  Sleep(30);
n=send(connfile,"file ok",8,0);
if ((n==SOCKET_ERROR)|| (n==0)) goto reset;

n=recv(connfile,buff,sizeof(buff),0);
if ((n==SOCKET_ERROR)|| (n==0)) goto reset;

/*
I did with CFile because it is much easier to get the length and stuff although
I beleive that MFC is less good that WIN32 Apis
*/

CFile f;
f.Open(host,CFile::modeRead);

DWORD size;
size=f.GetLength();
_itoa(size,buff,10);
Sleep(5);
n=send(connfile,buff,strlen(buff),0);
if ((n==SOCKET_ERROR)|| (n==0)) goto reset;

n=recv(connfile,buff,sizeof(buff),0);
if ((n==SOCKET_ERROR)|| (n==0)) goto reset;


int y;
char buf[2048];
//before starting to send sleep 100 miliseconds 

//so the client will be ready to receive

Sleep(100);
while (true)
{
  y=f.Read(buf,1024); //try to read 1024 caracters

  //Sleep(1);

  n=send(connfile,buf,y,0);// send the actual numbers of bites read

  if ((n==SOCKET_ERROR)|| (n==0))
  {
    Sleep(50);
    n=send(connfile,"file ready",11,0);
    goto reset;
  }
  //we should also recv something if we want

  //the file to be send succesfully

  ZeroMemory(buff,sizeof(buff));
  n=recv(connfile,buff,sizeof(buff),0);
  if ((n==SOCKET_ERROR)|| (n==0))
  {
    n=send(connfile,"file ready",11,0);
    goto reset;
  }
  if (strcmp(buff,"break")==0)
  {
    goto rename;
  }
  //and we do this cycle untill the bytes read

  //are less than 1024 which means the

  //file is over

  if (y<1024) break;
}

Now all we have to do is send a string to the client to announce to it that the file is over so it will close the file and stop its cycle. This is all I can tell you about the server, or should I say the main things that it can do? About the client: now, here I don't really have much to say because they are very similar. The tricks that should be mentioned from the client are: sending messages only from certain threads or getting the download speed. The client starts with a thread that waits for connections from any computer on the LAN that has a server started. After one connects, it opens a thread for that certain computer and stores its IP. It is very important to know that after you start a thread, you can only pause it, resume it or kill it, so if I want to send a certain message to a server at a certain time, I have to declare a global variable called command which is a boolean and which each thread checks to see if it has any command to be sent. Take a look:

MSG msg;
SetTimer(NULL,0,1000,0);

while(1)
{
  GetMessage(&msg,0,0,0);

  if (command==true) //if there is any command to be sent

  if (StrStrI(comanda,ip)!=NULL)
  // check if the ip of the connected computer is in the command string

  {
    //if it is send it

    n=send(client,comanda,strlen(comanda),0);
    if ((n==SOCKET_ERROR)||(n==0)) goto out;
    command=false;
    ZeroMemory(buff,sizeof(buff));

The command boolean will be maneuvered by the user. If it presses the button KillServer for example, the command string will be like this "host+ip killserver", and of course, command=true which means that the computer with that host and IP should terminate its server. The client thread checks if the command boolean is true and if it contains the IP of the connected computer. If yes, send the server message to exit the process. To get the download speed now: I thought that this would be very simple to be done if I compare the length of the file each second and see how much it has grown. I want to know the KB/second speed but this doesn't mean that I should check every second because that would be too much time; you can check every 500 milliseconds and rapport it to 1 second:

500 . . . . . . . . . 124235 KB

1000 . . . . . . . . . X

So if in 500 milliseconds we got 12435 KB, in 1000 milliseconds, we would have got X which equals: X=((1000)*(124235))/500, and this is the number of KB/second the speed of download.

case WM_TIMER:
  if (downloading)
  {
    /* prev and now are the DWORDs that contain info on the
       previous and current file length
    */

    prev=now;
    now=f.GetLength();
    DWORD z,rt;
    /* now-prev is the quantity of bytes written 
    in the file in the las t 500 miliseconds
    By dividing it by 1000 we get the number of KB written
    */
    z=(now-prev)/1000;
    rt=(1000*z)/500; // :)

    char rat[1024];
    _itoa(rt,rat,10);
    strcat(rat," ");
    strcat(rat,"KB/s");
    SetDlgItemText(hdlg,RATE,rat);
  }
  else
    SetDlgItemText(hdlg,RATE,"0 KB/s" );
  break;

That's kind of all. If you have any questions regarding the project, please ask me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here