All pastes #933440 Raw Edit

KOrUPt

public text v1 · immutable
·published 2008-03-08 09:57 UTC
rendered paste body
Hi guys, i wanted to post this tutorial on a few sites, i didn't really like the old code so here's the old tutorial revisited :D... that means there's updated(and much improved) code amongst other things :P.

I have also heavily commented it for the newbies so let me know what you think :D. Enjoy, KOrUPt.

Back to the tutorial:
[b]------------------------------------------------------------[/b]

Hi guys.

Before we start I'm going to assume you know how to create single threaded servers(if you don't I'm sure this tut will still help you) and have a basic knowledge of Winsock and what threads are(if you don't see below)... 

Now it really isn't that hard to create multi-threaded servers once you get used to them... So firstly I'm going to show you a basic single threaded server, then I'll show you how to upgrade it to a multi-threaded server, but first a bit of Questions and Answers is in order :P...

Q. What are the benefits of using a multi-threaded server over a single threaded server?
A. A multi-threaded server allows you to have more than 1 client connected at any one time,
at some point you may want to create a game server or other, you will find that you need to
be able to handle multiple connections, threads are a solution to handling multiple clients.

Quick and (very)simple overview of what threads are:
	Threads allow you to have more than 1 action executing at the same time, for instance you could 
	have 2 infinite loops executing at the same time(please note i attempted to put this into plain English if you know what i mean)...

Ok i couldn't think of any more questions... So if you have any let me know and I'll answer them and merge them into this topic...

Right below is the code to a very basic single threaded Winsock server that I just whipped up:

[code]

#include <windows.h>
#include <stdio.h>
#include <winsock.h>

int main()
{
	WSADATA wsaData;
	SOCKADDR_IN server, from;
	SOCKET sock, client;

	printf("Starting up TCP server by KOrUPt\n");
	
	int ret = WSAStartup(0x101, &wsaData); // use highest version of winsock avaliable
	if(ret != NO_ERROR)
		return 0;
	
	server.sin_family		= AF_INET;
	server.sin_addr.s_addr	= INADDR_ANY;
	server.sin_port			= htons(23);
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sock == INVALID_SOCKET || sock == SOCKET_ERROR)
		return 1;
	
	if(bind(sock, (sockaddr*)&server, sizeof(server)) != 0)
		return 1;
	
	if(listen(sock, 5) != 0)
		return 1;
	
	int len = sizeof(from);  
	while((client = accept(sock, (struct sockaddr*)&from, &len)) == INVALID_SOCKET)
		Sleep(10000);
	
	while(true){
		// send/recv data
		break;
	}
	
	closesocket(client);
	closesocket(sock);
	WSACleanup();

	return 0;
}[/code]

Now that code can only accept one connection at a time, lets modify it so it can accept and handle as many clients as you choose ;).

NOTE: This is the old code:
[code]
#include <windows.h>
#include  <stdlib.h>
#include <stdio.h>
#include <winsock.h>

// our thread for recving commands
DWORD WINAPI receive_cmds(LPVOID lpParam)
{
  printf("thread created\r\n");
  
  // set our socket to the socket passed in as a parameter  
  SOCKET current_client = (SOCKET)lpParam;
  
  // buffer to hold our recived data
  char buf[100];
  // buffer to hold our sent data
  char sendData[100];
  // for error checking
  int res;
  
  // our recv loop
  while(true)
  {
     res = recv(current_client,buf,sizeof(buf),0); // recv cmds
    
     Sleep(10);
    
     if(res == 0)
     {
      MessageBox(0,"error","error",MB_OK);
      closesocket(current_client);
      ExitThread(0);
     }
    
     if(strstr(buf,"hello"))
     { // greet this user
       printf("\nrecived hello cmd");
      
       strcpy(sendData,"hello, greetz from KOrUPt\n");
       Sleep(10);
       send(current_client,sendData,sizeof(sendData),0);
     }                                
     else if(strstr(buf,"bye"))  
     { // dissconnected this user
       printf("\nrecived bye cmd\n");
      
       strcpy(sendData,"cya\n");
       Sleep(10);
       send(current_client,sendData,sizeof(sendData),0);

      // close the socket associted with this client and end this thread
       closesocket(current_client);
       ExitThread(0);
     }
     else
     {
       strcpy(sendData,"Invalid cmd\n");
       Sleep(10);
       send(current_client,sendData,sizeof(sendData),0);
     }
    
     // clear buffers
       strcpy(sendData,"");
       strcpy(buf,"");
   }
}  

int main()
{
printf("Starting up multi-threaded TCP server by KOrUPt\r\n");

// our masterSocket(socket that listens for connections)
SOCKET sock;

// for our thread
DWORD thread;

WSADATA wsaData;
sockaddr_in server;

// start winsock
int ret = WSAStartup(0x101,&wsaData); // use highest version of winsock avalible

if(ret != 0)
{
    return 0;
}
  
// fill in winsock struct ...
server.sin_family=AF_INET;
server.sin_addr.s_addr=INADDR_ANY;
server.sin_port=htons(123); // listen on telnet port 23

// create our socket
sock=socket(AF_INET,SOCK_STREAM,0);

if(sock == INVALID_SOCKET)
{
    return 0;
}
  
// bind our socket to a port(port 123)
if( bind(sock,(sockaddr*)&server,sizeof(server)) !=0 )
{
    return 0;
}
  
// listen for a connection  
if(listen(sock,5) != 0)
{
    return 0;
}

// socket that we snedzrecv data on
SOCKET client;

sockaddr_in from;
int fromlen = sizeof(from);
  
// loop forever
while(true)
{
  // accept connections
  client = accept(sock,(struct sockaddr*)&from,&fromlen);
  printf("Client connected\r\n");
  
  // create our recv_cmds thread and parse client socket as a parameter
  CreateThread(NULL, 0,receive_cmds,(LPVOID)client, 0, &thread);  
}

// shutdown winsock
closesocket(sock);
WSACleanup();

// exit
return 0;
}[/code]

Since then a lot has been learned not to mention my coding style has changed :P.

This is the version i whipped up today :D, enjoy... just follow along and read the comments :D...
[code]


// linker options(MSVC only)
#pragma comment(lib, "ws2_32.lib")

// headers
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

const int MAX_CLIENTS = 10; // number of max allowed clients to connect
int	clients_connected = 0; // number of currently connected clients
DWORD thread; 			   // for our thread(s)

// our client object definintion
typedef struct _client{
	sockaddr_in	address;		// internal data structure regarding this client
	SOCKET		socket;			// this clients socket
	bool		connected;		// is this client connected
	char        IP[20];			// this clients IP address
	int			address_length;	// internal data structure regarding this client
} CLIENT, *PCLIENT;

// create client object(s)
CLIENT client[MAX_CLIENTS];

// declarations
sockaddr_in	server_address;
sockaddr	server_socket_address;
SOCKET		server_socket;

// function declarations
DWORD WINAPI ReceiveCmds(LPVOID lpParam);
int start_server(int port);
bool accept_connections();
bool AcceptClient(PCLIENT current_client);
void disconnect_client(PCLIENT current_client);
void end_server();
bool EchoMessage(char *message);
bool send_data(PCLIENT current_client, char *buffer, int size);
bool receive_data(PCLIENT current_client, char *buffer, int size);

// start a server
int start_server(int port)
{
    int res, i = 1;
	
	// fill in socket structures
	server_address.sin_family 		= AF_INET;		// use INET address family(IPv4)
    server_address.sin_addr.s_addr	= INADDR_ANY;	// allow anyone to connect
    server_address.sin_port			= htons(port);	// listen on port 23(telnet)
    
	memcpy(&server_socket_address, &server_address, sizeof(SOCKADDR_IN));
	
	// setup our socket
    server_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(server_socket == INVALID_SOCKET || server_socket == SOCKET_ERROR){
		MessageBox(0, "Invalid socket", "Error", MB_ICONSTOP | MB_OK);
	    return 1;
    }
	
	// bind our socket to our port
    res = bind(server_socket, &server_socket_address, sizeof(server_socket_address));
    if(res == SOCKET_ERROR){
       MessageBox(0, "Error Binding socket to port", "Error", MB_ICONWARNING | MB_OK);
       return 1;
    }
	
	// listen for connections
    if(listen(server_socket, MAX_CLIENTS) == SOCKET_ERROR){
       MessageBox(0, "Error listening on socket", "Error", MB_ICONWARNING | MB_OK);
       return 1;
    }
	
	printf("Listening for connections on port %d\n", port);
	
    return 0;
}

// accept a connection and fill in CLIENT structure
bool AcceptClient(PCLIENT current_client)
{
	// accept incoming connection
	current_client->address_length = sizeof(sockaddr);
	current_client->socket = accept(server_socket, (sockaddr*)&current_client->address, &current_client->address_length);

	// check there were no errors
	if(current_client->socket == 0 || current_client->socket == SOCKET_ERROR)
		return false;

	 // this client is now connected, you know what to do...
	current_client->connected = true;

	// start the receive commands thread and pass our client object to it
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReceiveCmds, (LPVOID)current_client, 0, &thread);
	return true;
}

// disconnect a connected client
void disconnect_client(PCLIENT current_client)
{
	// declare a variable to store the disconnect message
	char info[250];

	// only continue if client is connected
	if(current_client->connected == false)
		return;
	   
	// alert user that client disconnected
	wsprintf(info, "\n%s has disconnected...\n", current_client->IP);
	printf(info);

	// close the socket associated with this client
	closesocket(current_client->socket);

	// decrement the current number of connected clients
	clients_connected--;

	// set the new client connected state
	current_client->connected = false;

	// clear the address length
	current_client->address_length = -1;

	// clear info members
	memset(current_client->IP, 0, 20);
	return;
}

// handle incoming connections
bool accept_connections()
{	// have we reached maximum amount of allowed connections
   if(clients_connected <= MAX_CLIENTS){
	    // loop until an empty slot is found
		for(int j = 0; j < MAX_CLIENTS; j++){
			if(client[j].connected == false){
			   if(AcceptClient(&client[j])){
					clients_connected++;

					// grab the ip address of the client
					char *client_ip_address = inet_ntoa(client[j].address.sin_addr);
					strncpy(client[j].IP, client_ip_address, 19); // fill in IP address info

					// output connection
					printf("%s has connected\n", client_ip_address);
					return true;
				}
			}
		}
	}
	
	return false;
}

// shutdown the server by disconnecting all connected clients
void end_server()
{	
	// disconnect all clients
	for (int j = 0; j < MAX_CLIENTS; j++)
		disconnect_client(&client[j]);

	// close the listening socket for the server
	closesocket(server_socket);
}

// receive data
bool receive_data(PCLIENT current_client, char *buffer, int size)
{
	// store the return data of what we have sent
	current_client->address_length = recv(current_client->socket, buffer, size, 0);

	// error checking
	if(current_client->address_length <= 0){
		disconnect_client(current_client);
		return false;
	}

	return true;
}

// send data
bool send_data(PCLIENT current_client, char *buffer, int size)
{
	// store the return information
	current_client->address_length = send(current_client->socket, buffer, size, 0);

	// check for errors while sending
	if((current_client->address_length == SOCKET_ERROR) || (current_client->address_length == 0 )){
		disconnect_client(current_client); // disconnect client
        return false;
	}
	return true;
}

// send a message to all connected clients
bool EchoMessage(char *message)
{
	for(int j = 0; j < MAX_CLIENTS; j++){
		if(client[j].connected){
			if(send_data(&client[j], message, strlen(message)) != 0)
				return false;
		}
	}
	return true;
}

// our thread for receiving commands
DWORD WINAPI ReceiveCmds(LPVOID lpParam)
{
	PCLIENT current_client = (PCLIENT)lpParam;
    char buffer[512], *buf = NULL, *cmd = NULL;
	
	// receive commands loop
    while(1)
    {	// if client connected...
		if(current_client->connected){
   			memset(buffer, 0, 512);
			
			// recevice a command
			if(receive_data(current_client, buffer, 511)){
				// if we've recevied a command...
				if(strncmp(buffer, "/cmd ", 5) == 0){
					buf = strtok(buffer, " ");
					if(buf){
						cmd = strtok(NULL, "\r\n");
						if(cmd){
							printf("Recevied command: %s\n", cmd);
							
							// shutdown server
							if(!strncmp(cmd, "exit", 4)){
								printf("Shutting down server in 5 seconds...\n");
								EchoMessage("Shutting down server in 5 seconds...\n");
								for(int i = 5; i != 0; i--){
									printf("%d...\n", i);
									Sleep(1000);
								}
								
								// cleanup and exit server
								end_server();
								WSACleanup();
								
								printf("Server shutdown. exiting thread...\n");
								ExitThread(0);
							}
							
							// echo message to all clients
							else if(!strncmp(cmd, "Echo ", 5)){
								char* message = strtok(cmd, " ");
								if(message){
									message = strtok(cmd, "\r\n");
									if(message){
										printf("Echoing message to all clients:\n\t\t %s\n\n", message);
										EchoMessage(message);
									}
								}
								continue;
							}
							
							else if(!strncmp(cmd, "DropClient", 10)){
								send_data(current_client, "You will be disconnected\n", strlen("You will be disconnected\n"));
								disconnect_client(current_client);
								continue;
							}
						}
						continue;
					}
				}
		   }
	   }
	}
	
	ExitThread(0);
}  

int main(void)
{
	HANDLE		hMutex;		// for out mutex
	HANDLE		hStdout;	// for console colour/title
	WSADATA 	wsaData;	// for winsock
		
	// create a mutex so we cant run more than once at the same time
	hMutex = CreateMutex(0, FALSE, ("K-TCP Tutorial"));
	if(GetLastError() == ERROR_ALREADY_EXISTS || GetLastError() == ERROR_ACCESS_DENIED){
		CloseHandle(hMutex);
		return 1;
	}
	
	// never liked that horrible grey colour...
	hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	SetConsoleTitle("Multi-Threaded TCP server by KOrUPt");
	
	printf("Starting Up Multi-Threaded TCP server by KOrUPt\n");
	
	// start winsock
	if(WSAStartup(0x101, &wsaData) != NO_ERROR){
		printf("Could not start winsock...\n");
		return 1;
	}
	
	// start server
	if(start_server(23) != 0){
		printf("Failed to start server\n");
		return 0;
	}
	
	// start accepting connections
	while(1){
		accept_connections();
		Sleep(20000);
	}
	
	// disconnect clients shutdown winsock
	end_server();
	WSACleanup();
	
	// close mutex handle
	CloseHandle(hMutex);
	
	// exit
	return 0;
}[/code]

Well assuming you have a basic understanding of threads and winsock you should be able to follow and understand the above code and hopefully after a bit of experimenting build it into your own server application... 

I suggest you try and create a basic multi-threaded text file transfer server :)... 

If you have any questions relating to this tutorial reply within this thread or PM me. 

Hopefully this tutorial helped you out to some degree ;). Also if you want to test out the server just use the client I built.

Client code attached below:

NOTE: Old client code:
[code]/*-------------------Echo_client.cpp-------------------*/
#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
#include <signal.h>
#include <stdio.h>

//DECLARATIONS
    //error trapping signals
      #define SIGINT 2
      #define SIGKILL 9
      #define SIGQUIT 3
    // SOCKETS
      SOCKET sock,client;

void s_handle(int s)
{
    if(sock)
       closesocket(sock);
    if(client)
       closesocket(client);
    WSACleanup();
    Sleep(1000);
    cout<<"EXIT SIGNAL :"<<s;
    exit(0);
}


void s_cl(char *a, int x)
{
    cout<<a;
    s_handle(x+1000);
}


int main()
{
   HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
   SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN|FOREGROUND_INTENSITY);
   SetConsoleTitle(".:: Basic Echo Client By KOrUPt 07 ::. ");
    
    
    //Declarations
    DWORD poll;
    int res,i=1,port=999;
    char buf[100];
    char msg[100] = "";
    char ip[15];
    WSADATA data;
    
    signal(SIGINT,s_handle);
    signal(SIGKILL,s_handle);
    signal(SIGQUIT,s_handle);
    
    cout<<"\t\tEcho Client by KOrUPt";
    
    cout<<"\n\n\n\t\tEnter IP to connect to: ";
    gets(ip);
    
    sockaddr_in ser;
    sockaddr addr;
    
    
    ser.sin_family=AF_INET;
    ser.sin_port=htons(123);                    //Set the port
    ser.sin_addr.s_addr=inet_addr(ip);        //Set the address we want to connect to
    
    memcpy(&addr,&ser,sizeof(SOCKADDR_IN));
    
    res = WSAStartup(MAKEWORD(1,1),&data);        //Start Winsock
    cout<<"\n\nWSAStartup"
        <<"\nVersion: "<<data.wVersion
        <<"\nDescription: "<<data.szDescription
        <<"\nStatus: "<<data.szSystemStatus<<endl;

    if(res != 0)
        s_cl("WSAStarup failed",WSAGetLastError());

    sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);        //Create the socket
        if(sock==INVALID_SOCKET )
            s_cl("Invalid Socket ",WSAGetLastError());
        else if(sock==SOCKET_ERROR)
            s_cl("Socket Error)",WSAGetLastError());
        else
            cout<<"Socket Established"<<endl;
    

    
    res=connect(sock,&addr,sizeof(addr));                //Connect to the server
        if(res !=0 )
        {
            s_cl("SERVER UNAVAILABLE",res);
        }
        else
        {
            cout<<"\nConnected to Server: ";
            memcpy(&ser,&addr,sizeof(SOCKADDR));
        }
    
    char RecvdData[100] = "";
    int ret;

    while(true)
    {
        strcpy(buf,"");
        cout<<"\nEnter message to send ->\n";
        fgets(buf,sizeof(buf),stdin);
        
        
        Sleep(5);
        res = send(sock,buf,sizeof(buf),0);    
        
         if(res==0)
         {    
            //0==other side terminated conn
            printf("\nSERVER terminated connection\n");
            Sleep(40);
            closesocket(client);
            client = 0;
            break;
         }
         else if(res==SOCKET_ERROR)
         {    
            //-1 == send error
            printf("Socket error\n");
            Sleep(40);
            s_handle(res);
            break;
         }
      
       ret = recv(sock,RecvdData,sizeof(RecvdData),0);
       if(ret > 0)
       {
        cout<<endl<<RecvdData;
        strcpy(RecvdData,"");
       }
    }    
    
    closesocket(client);
    WSACleanup();
}
/*-------------------Echo_client.cpp-------------------*/[/code]

And this is the new(and improved code) client code :D
[code]

#pragma comment(lib, "Ws2_32.lib")

// headers
#include <winsock2.h>
#include <windows.h>
#include <signal.h>
#include <stdio.h>

// error trapping signals
#define SIGKILL 9
#define SIGQUIT 3

#define port 23 // our port

// sockets
SOCKET sock, client;

void HandleSignal(int s)
{
	printf("Exit signal %d received\n", s);

	if(sock)
		closesocket(sock);
    if(client)
		closesocket(client);
    
	WSACleanup();
    exit(0);
}

int main()
{
    WSADATA data;
	SOCKADDR_IN server;
	SOCKADDR addr;
	FD_SET sockData; 
	char ip[20], buff[512];	
	int blocking = 1; // non blocking sockets
	
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
	SetConsoleTitle(".:: Echo Client By KOrUPt 08 ::.");
	
	// so we can cleanup should the user exit whilist connected
	signal(SIGKILL,	HandleSignal);
	signal(SIGQUIT,	HandleSignal);

	printf("\n\n\t\tEcho Client by KOrUPt\n");

	printf("\n\n\t\tEnter IP to connect to: ");
	fgets(ip, 19, stdin);
	memset(ip + (strlen(ip) - 1), 0, 1); // strip new line char
	
	server.sin_family		= AF_INET;			// use INET address family
	server.sin_port			= htons(port);		// use specifed port
	server.sin_addr.s_addr	= inet_addr(ip);	// use specifed IP address
	memcpy(&addr, &server, sizeof(SOCKADDR_IN));
	
	// start Winsock
	if(WSAStartup(MAKEWORD(2, 2), &data) != NO_ERROR){
		printf("Could not start winsock\n");
		return 1;
	}

	// create the socket
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sock == INVALID_SOCKET || sock == SOCKET_ERROR){
		printf("Could not create socket\n");
		return 0;
	}
	
	// connect to the server
	if(connect(sock, &addr, sizeof(addr)) == SOCKET_ERROR){
		printf("Could not connect to %s on port %d\n", ip, port);
		getchar();
		return 1;
	}
	
	// print some info
	printf("Connected to Server %s\n", ip);
	memcpy(&server, &addr, sizeof(SOCKADDR));
	
	// we want non blocking sockets
	FD_ZERO(&sockData);
	FD_SET(sock, &sockData);
	if(ioctlsocket(sock, FIONBIO, (u_long FAR*)&blocking) == SOCKET_ERROR){
		printf("Could not set socket to blocking mode\n");
		return 1;
	}
	
	// enter send/receive data loop
	while(true){
		memset(buff, 0, 512);
		printf("\nEnter message to send ->\n\t\t");
		fgets(buff, 511, stdin);
		
		// send command
		int ret = send(sock, buff, strlen(buff), 0);    
		if(ret == 0){
			printf("\nServer terminated connection\n");
			HandleSignal(1);
		}else if(ret == SOCKET_ERROR){
			printf("\nSocket error\n");
			HandleSignal(1);
		}else{
			memset(buff + (strlen(buff) - 1), 0, 1); // strip new line char
			printf("\nCommand %s sent\n", buff);
		}
		
		memset(buff, 0, 512);
		if(FD_ISSET(sock, &sockData)){
			ret = recv(sock, buff, 511, 0);
			if(ret > 0)
				printf("Received data: %s\n", buff);
		}
	}
	
	closesocket(sock);
	WSACleanup();
	return 0;
}[/code]
Well hope that helps, just thought I'd code it quickly so you could test out the server... well this is KOrUPt signing off...

Cya in my next tutorial :D.

P.S sorry about any typo's...