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;
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)
{
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:
GetIps();
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);
Sleep(100);
conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(conn==INVALID_SOCKET)
goto aici;
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;
}
AfxBeginThread(Client,(LPVOID)conn);
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;
DWORD dwScope = RESOURCE_CONTEXT;
NETRESOURCE *NetResource = NULL;
HANDLE hEnum;
WNetOpenEnum( dwScope, NULL, NULL,
NULL, &hEnum );
WSADATA wsaData;
WSAStartup(MAKEWORD(1,1),&wsaData);
if ( hEnum )
{
DWORD Count = 0xFFFFFFFF;
DWORD BufferSize = 2048;
LPVOID Buffer = new char[2048];
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;
RegQueryValueEx(Regentry,"host" , NULL,
&dwType, (unsigned char*)&as, &dwSize);
RegQueryValueEx(Regentry,"host" , NULL,
&dwType, (unsigned char*)&as, &dwSize);
char *host1;
host1= _strupr( _strdup( as ) );
for ( i = 0; i < BufferSize/sizeof(NETRESOURCE); i++, NetResource++ )
{
if ( NetResource->dwUsage ==RESOURCEUSAGE_CONTAINER
&& NetResource->dwType == RESOURCETYPE_ANY )
{
if ( NetResource->lpRemoteName )
{
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;
gethostname(text,sizeof(text));
h=gethostbyname(text);
char * ip;
ip=inet_ntoa(*((struct in_addr *)h->h_addr));
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)
{
ZeroMemory(buff,sizeof(char [1024]));
n=recv(conn,buff,512,0);
if ((n==SOCKET_ERROR)||(n==0))
{
reset:
char serv[1024];
GetModuleFileName(NULL,serv,sizeof(serv));
ShellExecute(NULL,"open",serv,NULL,NULL,SW_SHOW);
_exit(0);
return 0;
}
if (strcmp(buff,"ping")==0)
{
Sleep(5);
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);
}
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))
{
Sleep(30);
n=send(connfile,"unavalaible",11,0);
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;
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];
Sleep(100);
while (true)
{
y=f.Read(buf,1024);
n=send(connfile,buf,y,0);
if ((n==SOCKET_ERROR)|| (n==0))
{
Sleep(50);
n=send(connfile,"file ready",11,0);
goto reset;
}
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;
}
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 (StrStrI(comanda,ip)!=NULL)
{
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=now;
now=f.GetLength();
DWORD z,rt;
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.