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*)¤t_client->address, ¤t_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...