Click here to Skip to main content
15,891,845 members
Articles / Desktop Programming / MFC

Multi-threaded Client/Server Socket Class

Rate me:
Please Sign up or sign in to vote.
4.81/5 (180 votes)
10 Feb 2009CPOL7 min read 2.1M   90.1K   651   523
A multi-threaded based Client/Server Socket Communication class

Screenshots

Note: The demo can be started in Client or Server mode, executed with "/C" (or "/CLIENT") or "/S" (or "/SERVER", which is the default).

Server Socket App - Screenshot

Client Socket App - Screenshot

Introduction

This article is about a client/server multi-threaded socket class. The thread is optional since the developer/designer is still responsible for deciding if he/she needs it. There are other Socket classes here and other places over the Internet, but none of them can provide feedback (event detection) to your application like this one does. It provides you with the following events detection: connection established, connection dropped, connection failed and data reception (including 0 byte packet).

Description

This article presents a new socket class which supports both TCP and UDP communication. It provides some advantages compared to other classes that you may find here or on some other Socket Programming articles. First of all, this class doesn't have any limitation like the need to provide a window handle to be used. This limitation is bad if all you want is a simple console application. So, this library doesn't have such a limitation. It also provides threading support automatically for you, which handles the socket connection and disconnection to a peer. It also features some options not yet found in any socket classes that I have seen so far. It supports both client and server sockets. A server socket can be referred as to a socket that can accept many connections. A client socket is a socket that is connected to a server socket. You may still use this class to communicate between two applications without establishing a connection. In the latter case, you will want to create two UDP server sockets (one for each application). This class also helps reduce coding needed to create chat-like applications and IPC (Inter-Process Communication) between two or more applications (processes). Reliable communication between two peers is also supported with TCP/IP with error handling. You may want to use the smart addressing operation to control the destination of the data being transmitted (UDP only). TCP operation of this class deals only with communication between two peers.

Now for those not familiar with IP Socket, the following section will give some details on how it works. This is also the goal with this article: to explain the basic functionality behind socket objects.

TCP/IP Stack

The TCP/IP stack is shorter than the OSI one:

Image 3

TCP is a connection-oriented protocol, while UDP (User Datagram Protocol) is a connectionless protocol.

IP Datagrams

The IP layer provides a connectionless and unreliable delivery system. It considers each datagram independently of the others. Any association between datagrams must be supplied by the higher layers. The IP layer supplies a checksum that includes its own header. The header includes the source and destination addresses. The IP layer handles routing through the Internet. It is also responsible for breaking up large datagrams into smaller ones for transmission and reassembling them at the other end.

UDP

UDP is also connectionless and unreliable. What it adds to IP is a checksum for the contents of the datagram and port numbers. These are used to give a client/server model: see later.

TCP

TCP supplies logic to give a reliable connection-oriented protocol above IP. It provides a virtual circuit that two processes can use to communicate.

Internet Addresses

In order to use a service, you must be able to find it. The Internet uses an address scheme for machines so that they can be located. The address is a 32-bit integer which gives the IP address. This encodes a network ID and more addressing. The network ID falls into various classes according to the size of the network address.

Network Address

Class A uses 8 bits for the network address with 24 bits left over for other addressing. Class B uses 16-bit network addressing; class C uses 24-bit network addressing and class D uses all 32.

Subnet Address

Internally, the Unix network is divided into subnetworks. Building 11 is currently on one subnetwork and uses 10-bit addressing, allowing 1024 different hosts.

Host Address

8 bits are finally used for host addresses within our subnet. This places a limit of 256 machines that can be on the subnet.

Total Address

Image 4

The 32-bit address is usually written as 4 integers separated by dots.

Port Addresses

A service exists on a host and is identified by its port. This is a 16-bit number. To send a message to a server, you send it to the port for that service of the host that it is running on. This is not location transparency! Some of these ports are "well known." For example:

tcpmux1TCP
echo7UDP
echo7TCP
systat11TCP
netstat15TCP
ftp-data20TCP File Transfer Protocol (data)
ftp21TCP File Transfer Protocol
smtp25TCP Simple Mail Transfer Protocol
time37TCP Time Server
time37UDP Time Server
name42UDP Name Server
whois43TCP nicname
domain53UDP
domain53TCP
tftp69UDP
rje77TCP
finger79TCP
link87TCP ttylink
supdup95TCP
hostname101TCP hostname
pop-2109TCP Post Office Protocol
uucp-path117TCP
nntp119TCP Network News Transfer Protocol
ntp123TCP Network Time Protocol

Ports in the region 1-255 are reserved by TCP/IP. The system may reserve more. User processes may have their own ports above 1023. The function getservbyname can be used to find the port for a service that is registered.

Sockets

A socket is a data structure maintained by the system to handle network connections. A socket is created using the call socket. It returns an integer that is like a file descriptor. In fact, under Windows, this handle can be used with the ReadFile and WriteFile functions.

C++
#include <sys/types.h>
#include <sys/socket.h>
int socket(int family, int type, int protocol);

Here, family will be AF_INET for IP communications, protocol will be zero and type will depend on whether TCP or UDP is used. Two processes wishing to communicate over a network create a socket each. These are similar to two ends of a pipe, but the actual pipe does not yet exist.

Connection Oriented (TCP)

One process (server) makes its socket known to the system using bind. This will allow other sockets to find it. It then "listens" on this socket to "accept" any incoming messages. The other process (client) establishes a network connection to it and then the two exchange messages. As many messages as needed may be sent along this channel, in either direction.

Server

  • Create endpoint (socket())
  • Bind address (bind())
  • Specify queue (listen())
  • Wait for connection (accept())
  • Transfer data (read()/write())

Client

  • Create endpoint (socket())
  • Connect to server (connect())
  • Transfer data (read()/write())

Connectionless (UDP)

In a connectionless protocol, both sockets have to make their existence known to the system using bind. This is because each message is treated separately, so the client has to find the server each time it sends a message and vice versa. When bind is called, it binds to a new port. It cannot bind to one already in use. If you specify the port as zero, the system gives you a currently unused port. Because of this extra task on each message send, the processes do not use read/write, but recvfrom/sendto. These functions take as parameters the socket to write to and the address of the service on the remote machine.

Server

  • Create endpoint (socket())
  • Bind address (bind())
  • Transfer data (sendto()/recvfrom())

Client

  • Create endpoint (socket())
  • Bind address (bind()) (optional if connect is called)
  • Connect to server (connect())
  • Transfer data (sendto()/recvfrom())

Version History

////////////////////////////////////////////////////////////////////////
//    File:        SocketComm.cpp
//    Version:     1.4
//
//  1.0 - Initial release.
//  1.1 - Added support for Smart Addressing mode
//  1.2 - Fixed various issues with address list (in UDP mode)
//  1.3 - Fix bug when sending message to broadcast address
//  1.4 - Add UDP multicast support
////////////////////////////////////////////////////////////////////////

How to Use

This class can be used to create a TCP or UDP socket. Its use is very simple. First of all, the CSocketComm class is not completed by itself for server operation. This class must be derived. Fortunately, only two functions need to be created, OnDataReceived and OnEvent. The default functions don't do anything. Now to create and start a server socket, do the following:

C++
// To use TCP socket
// no smart addressing - we use connection oriented
m_SocketObject.SetSmartAddressing( false ); 
m_SocketObject.CreateSocket( m_strPort, AF_INET, SOCK_STREAM,0); // TCP

// To use UDP socket
m_SocketObject.SetSmartAddressing( true );
m_SocketObject.CreateSocket( m_strPort, 
   AF_INET, SOCK_DGRAM, SO_BROADCAST); // UDP

// Now you may start the server/client thread to do the work for you...
m_SocketObject.WatchComm();

To create and start a client socket, do the following:

C++
// To use TCP socket
m_SocketObject.ConnectTo( strServer, m_strPort, AF_INET, SOCK_STREAM); // TCP

// To use UDP socket
m_SocketObject.ConnectTo( strServer, m_strPort, AF_INET, SOCK_DGRAM); // UDP

// Now you may start the server/client thread to do the work for you...
m_SocketObject.WatchComm();

References

History

  • Aug 31, 2002: Updated source code
  • Mar 01, 2004: Updated source code
  • Apr 02, 2004: Fixed bug when sending message to broadcast address
  • Feb 07, 2009: Updated source code (Visual Studio 2005 project)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Ernest is a multi-discipline software engineer.
Skilled at software design and development for all Windows platforms.
-
MCSD (C#, .NET)
Interests: User Interface, GDI/GDI+, Scripting, Android, iOS, Windows Mobile.
Programming Skills: C/C++, C#, Java (Android), VB and ASP.NET.

I hope you will enjoy my contributions.

Comments and Discussions

 
GeneralRe: Multiple TCP Clients Pin
MichaelDB4-Jan-04 21:04
MichaelDB4-Jan-04 21:04 
Questionhow to create UDP socket Pin
Anonymous22-Sep-03 10:23
Anonymous22-Sep-03 10:23 
AnswerRe: how to create UDP socket Pin
Ernest Laurentin23-Sep-03 18:27
Ernest Laurentin23-Sep-03 18:27 
GeneralCan't get clean compile on console program Pin
rromerot11-Sep-03 8:33
rromerot11-Sep-03 8:33 
GeneralRe: Can't get clean compile on console program Pin
Anthony_Yio6-Oct-03 16:29
Anthony_Yio6-Oct-03 16:29 
QuestionUDP client needs to do a send before it can recieve? Pin
IamJimW7-Sep-03 19:22
IamJimW7-Sep-03 19:22 
AnswerRe: UDP client needs to do a send before it can recieve? Pin
Ernest Laurentin8-Sep-03 19:51
Ernest Laurentin8-Sep-03 19:51 
GeneralRe: UDP client needs to do a send before it can recieve? Pin
IamJimW9-Sep-03 11:23
IamJimW9-Sep-03 11:23 
Thank you.

Can I ask you for some advice on UDP sockets?
I am trying to set up a client/server system with UDP sockets.
I'm using UDP sockets, as I believe they're connectionless?
I want the server to broadcast data, regardless of what the client is doing.
I want the client to be able to connect to the server, and recieve the transmitted data. The client should be able to connect, disconnect, and reconnect at will, without affecting the server.
Is this possible?

I've set up a test windows console application. Which is mostly working, but still has a few problems.
I can connect to the server, and recieve data, but as I mentioned, the client must do a send before it can recieve. You've just explained this problem, thank you.
When I disconnect the client, I cannot then reconnect to the server.
Why is this?

Apologies if this is off topic,

Thanks,

Jim

Here's the my code- It might be of use to someone!

// SocketTest.h
//
#include "SocketComm.h"
#define WSA_VERSION MAKEWORD(2,0)

class CSocketTest : public CSocketComm
{
public:
bool isUDP; //true = UDP false = TCP
bool isReceiving;
bool isSending;
CSocketTest();
CSocketTest(bool useUDP);
virtual ~CSocketTest();


public:
virtual void OnDataReceived(const LPBYTE lpBuffer, DWORD dwCount);
virtual void OnEvent(UINT uEvent);

protected:

};

// SocketTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "SocketTest.h"

CSocketTest::CSocketTest()
{
isUDP = false;
isReceiving = false; //default to doing nothing!
isSending = false;
}

CSocketTest::CSocketTest(bool useUDP)
{
isUDP =useUDP;
isReceiving = false; //default to doing nothing!
isSending = false;
}

CSocketTest::~CSocketTest()
{

}

void CSocketTest::OnDataReceived(const LPBYTE lpBuffer, DWORD dwCount)
{
if(isReceiving)
{
SockAddrIn saddr_in;
LPBYTE lpData = lpBuffer;
if (IsSmartAddressing())
{
saddr_in.SetAddr((SOCKADDR_IN*) lpBuffer);
lpData = &lpData[sizeof(SOCKADDR_IN)];

dwCount -= sizeof(SOCKADDR_IN);
}

if(m_bServer)
{
printf("Server ");
}
else
{
printf("Client ");
}
printf(" has recieved data : %d Bytes, as string: %s\n",dwCount,lpData);
}
}

void CSocketTest::OnEvent(UINT uEvent)
{
switch( uEvent )
{
case EVT_CONSUCCESS:
printf("Connection Established\n");
break;
case EVT_CONFAILURE:
printf("Connection Failed\n");
break;
case EVT_CONDROP:
printf("Connection Abandoned\n");
break;
case EVT_ZEROLENGTH:
printf("Zero Length Message\n");
break;
default:
printf("Unknown Socket event\n");
break;
}
}


bool ArgTrue(char* argv[],int argc,int n)
{
bool ret = false;
if(!(n>argc))
{
if(!strcmp(argv[n],"true")||!strcmp(argv[n],"True")||!strcmp(argv[n],"TRUE")||!strcmp(argv[n],"t")||!strcmp(argv[n],"T"))
{
ret = true;
}
}
return ret;
}

int main(int argc, char* argv[])
{
BYTE byBuffer[128];
int err,time,prevTime,period,count,len,localtime;
char strServer[256],strAddr[256],strData[128];
char strPort[256];
bool bResult;

//pass these in as arguments!
bool UseUDP = true;
bool UseSmartAddressing = false;
bool IsServer = true;
bool AreSending = true;
bool AreRecieving = false;
strcpy(strPort,"2000");
strcpy(strServer,"JIM");

printf("SocketTest socket testing program!\n");

if(argc == 8)
{
if(ArgTrue(argv,argc,1))
{
IsServer = true;
}
else
{
IsServer = false;
}


strcpy(strPort,argv[2]); // a string of the port, eg "2000"

strcpy(strServer,argv[3]); // a string of the server name "JIM"

if(ArgTrue(argv,argc,4))
{
UseUDP = true;
}
else
{
UseUDP = false;
}
if(ArgTrue(argv,argc,5))
{
UseSmartAddressing = true;
}
else
{
UseSmartAddressing = false;
}

if(ArgTrue(argv,argc,6))
{
AreSending = true;
}
else
{
AreSending = false;
}
if(ArgTrue(argv,argc,7))
{
AreRecieving = true;
}
else
{
AreRecieving = false;
}
}
else
{
printf("Unrecognised parameters - \n");
printf("Do SocketTest IsServer Port Server UseUDP UseSmartAddressing AreSending AreRecieving \n");
printf(" where IsServer, UseUDP,UseSmartAddressing,AreSending,AreRecieving = t or f \n");
printf(" Port is port name, eg '2000, Server is server name, eg 'JIM'\n");
printf(" If IsServer = t, then ignores Server\n");
printf(" If UseUDP =f, then use TCP\n");
printf(" eg for a UDP Server 'SocketTest t 2000 BOB t f t f' (BOB ignored)\n");
printf(" for a UDP Client 'SocketTest f 2000 JIM t f f t'\n");
printf("Assuming defaults:\n");
}
printf(" IsServer = ");
if(IsServer)
{
printf("true , We're A Server, port = ");
printf(strPort);
printf("\n");
}
else
{
printf("false, We're A Client, server =");
printf(strServer);
printf(" port = ");
printf(strPort);
printf("\n");
}
printf(" UseUDP = ");
if(UseUDP) printf("true \n"); else printf("false \n");
printf(" UseSmartAddressing = ");
if(UseSmartAddressing) printf("true \n"); else printf("false \n");
printf(" AreSending = ");
if(AreSending) printf("true \n"); else printf("false \n");
printf(" AreRecieving = ");
if(AreRecieving) printf("true \n"); else printf("false \n");

//startup socket
WSADATA WSAData = { 0 };
err = WSAStartup( WSA_VERSION, &WSAData );
if ( 0 != err )
{
printf("SocketTest WSAStartup failed!\n");
// Tell the user that we could not find a usable
// WinSock DLL.
if ( LOBYTE( WSAData.wVersion ) != LOBYTE(WSA_VERSION) ||
HIBYTE( WSAData.wVersion ) != HIBYTE(WSA_VERSION) )
{
printf("Incorrect version of Winsock.dll found");
}

WSACleanup( );
return 0;
}

CSocketTest* st = new CSocketTest(UseUDP);//true = UDP, false = TCP
st->isReceiving = AreRecieving;
st->isSending = AreSending;
st->SetServerState(IsServer);
if(st->isUDP)
{
printf("Using UDP \n");
}
else
{
printf("Using TCP \n");
}
st->SetSmartAddressing(UseSmartAddressing);
if(IsServer)
{
printf("---Server Mode!------\n");
if(st->isUDP)
{
bResult = st->CreateSocket(strPort, AF_INET, SOCK_DGRAM, SO_BROADCAST); //UDP
}
else
{
bResult = st->CreateSocket(strPort, AF_INET, SOCK_STREAM,SO_REUSEADDR); //TCP
}

if(!bResult)
{
printf("Server - CreateSocket failed");
return 0;
}

bResult = st->WatchComm();
if(bResult)
{
st->GetLocalName( strServer, 256);
st->GetLocalAddress( strAddr, 256);
printf("Server- %s @Address: %s is running on port %s\n",strServer,strAddr,strPort);


//AddToList(const SockAddrIn& saddr_in);

}
else
{
printf("Server - WatchComm failed");
return 0;
}
}
else
{

printf("---Client Mode!------\n");
if(st->isUDP)
{
bResult = st->ConnectTo(strServer, strPort, AF_INET, SOCK_DGRAM); // UDP
}
else
{
bResult = st->ConnectTo(strServer, strPort, AF_INET, SOCK_STREAM); //TCP
}
bResult = st->WatchComm();
if(bResult)
{
if(st->isUDP)
{
SockAddrIn addrin;
st->GetSockName( addrin ); // just to get our current address
LONG uAddr = addrin.GetIPAddr();
BYTE* sAddr = (BYTE*) &uAddr;
short nPort = ntohs( addrin.GetPort() );

// Address is stored in network format...
printf("Client- Socket created: IP: %u.%u.%u.%u, Port: %d \n",(UINT)(sAddr[0]), (UINT)(sAddr[1]),
(UINT)(sAddr[2]), (UINT)(sAddr[3]), nPort);
}
else
{
//TCP
printf("Client- Connection established with server: %s on port %s\n",strServer,strPort);
}
//st->GetPeerName(SockPeer);//Don't do anything with this yet?
//st->AddToList(SockPeer);
}
}

if(AreRecieving && (!IsServer) && st->isUDP)
{
//client receiving
//for UDP, Have to send data to server before can recieve! Find out why!
BYTE byData[7] = "Hello\n";
st->WriteComm(byData,7,10);
}
if(AreSending)
{
count =0;
//dodgy timer code - haven't allowed for wrap around!
period = 1*CLOCKS_PER_SEC;//1s
prevTime = clock();
}
while(1) //waiting to recieve
{
if(AreSending)
{
time = clock();
if((time - prevTime)>period)
{
prevTime = time;
if(IsServer)
{
sprintf(strData,"Data from Server, count %d time %d \n",count,time);
}
else
{
sprintf(strData,"Data from Client, count %d time %d \n",count,time);
}
len = strlen(strData)+1;
memcpy(byBuffer, strData, len);
st->WriteComm(byBuffer,len,10/*ms timeout*/);
count++;
}
}
if(AreRecieving)
{
; //nothing to do - this is done in OnDataReceived()
}
localtime = clock();
//printf(" Doing nothing - local time %d \n",localtime);
}

delete st; //this does a cleanup on the socket
printf("Normal termination of SocketTest program!\n");
return 1;
}
GeneralRe: UDP client needs to do a send before it can recieve? Pin
Ernest Laurentin9-Sep-03 19:16
Ernest Laurentin9-Sep-03 19:16 
GeneralRe: UDP client needs to do a send before it can recieve? Pin
IamJimW10-Sep-03 15:17
IamJimW10-Sep-03 15:17 
GeneralRe: UDP client needs to do a send before it can recieve? Pin
jackred19-Feb-09 2:11
jackred19-Feb-09 2:11 
GeneralMessages Run Together Pin
Kevingy7-Sep-03 18:13
Kevingy7-Sep-03 18:13 
QuestionIs it possible to use this Class without MFC? Pin
IamJimW2-Sep-03 18:27
IamJimW2-Sep-03 18:27 
AnswerRe: Is it possible to use this Class without MFC? Pin
Ernest Laurentin3-Sep-03 14:22
Ernest Laurentin3-Sep-03 14:22 
GeneralRe: Is it possible to use this Class without MFC? Pin
JimatKC3-Sep-03 16:12
JimatKC3-Sep-03 16:12 
GeneralRe: Is it possible to use this Class without MFC? Pin
JimatKC3-Sep-03 19:08
JimatKC3-Sep-03 19:08 
GeneralRe: Is it possible to use this Class without MFC? Pin
Ernest Laurentin4-Sep-03 3:14
Ernest Laurentin4-Sep-03 3:14 
GeneralRe: Is it possible to use this Class without MFC? Pin
JimatKC4-Sep-03 16:13
JimatKC4-Sep-03 16:13 
GeneralHigh CPU utilization Pin
17-Aug-03 11:44
suss17-Aug-03 11:44 
GeneralRe: High CPU utilization Pin
Ernest Laurentin20-Aug-03 9:47
Ernest Laurentin20-Aug-03 9:47 
GeneralMany clients sending UDP s at same time. Pin
Geckops6-Aug-03 3:07
Geckops6-Aug-03 3:07 
GeneralRe: Many clients sending UDP s at same time. Pin
Ernest Laurentin13-Aug-03 10:43
Ernest Laurentin13-Aug-03 10:43 
QuestionWhat is the maximum number of byte size I could send Pin
Anthony_Yio1-Aug-03 1:00
Anthony_Yio1-Aug-03 1:00 
AnswerRe: What is the maximum number of byte size I could send Pin
Anthony_Yio1-Aug-03 22:50
Anthony_Yio1-Aug-03 22:50 
GeneralRe: What is the maximum number of byte size I could send Pin
Ernest Laurentin2-Aug-03 20:04
Ernest Laurentin2-Aug-03 20:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.