|
|
Title |
Sockets without MFC
|
Summary |
This is a Socket class based on the WinSock2 API. It is quite simple to use and uses asynchronous calles to enable smooth program flow. A simple client and server are provided for simple use. |
Contributor |
John McTainsh
|
Published |
1-Dec-2000 |
Last updated |
5-Dec-2000 |
|
|
Download Client and Server files - 31 Kb
Description.
Sockets are a simple way to communicate between computers and processes
without the hassle of security and registration. You simply create
a client and a server and away you go. This tutorial shows how
to use my CWinSock2Async or Microsoft's CAsyncSocket .
Both are similar but CWinSock2Async does not require
MFC support so can be used in ATL controls, Services and other
areas where MFC is not required.
How this tutorial is works.
We will create two applications, one a server and one a client. The server will start
and listen for connections. The client will connect to the server
to send and receive data. The sample applications and the CWinSockAsync
files are included in the downloads.
Project settings.
It is necessary to add the following include files to a global header such as StdAfx.h .
Note only the WinSock2.h is necessary if you are using
MFC. You MUST also add the Ws2_32.lib to be linked into
your application. To do this select Project->Settings to open the
Project settings dialog. Select the Link tab and from the drop down
box select the General Category. In the Object/Link modules add Ws2_32.lib .
//Needed for CWinSock2Async
#include <winsock2.h>
#include "WinSock2Async.h"
#include <windows.h> //Not necessary if using MFC
#include <TChar.h> //..
#include <assert.h> //..
Process flow.
The basic socket process (without errors) is;
Details of the process flow are described in later sections.
Create Client.
Create a new project or open an existing project. Put WinSock2Async.h
and WinSock2Async.cpp in the project directory.
Add WinSock2Async.cpp to the project work space.
Create a new class CSockClient derived from CWinSock2Async .
From the menu select Insert->New Class ..., set the
Name as CSockClient and Base class of CWinSock2Async ,
public.
Add the following prototypes to SockClient.h
//Event handlers
private:
void OnRecieve( int nError );
void OnSend( int nError );
void OnClose( int nError );
Implement the On... handlers to display a message about
each event in the SockClient.cpp file as follows
// ***************************************************************************
//DESCRIPTION:
// An FD_WRITE network event is recorded when a socket is first connected
// with connect/WSAConnect or accepted with accept/WSAAccept, and then
// after a send fails with WSAEWOULDBLOCK and buffer space becomes
// available. Therefore, an application can assume that sends are
// possible starting from the first FD_WRITE network event setting and
// lasting until a send returns WSAEWOULDBLOCK. After such a failure the
// application will find out that sends are again possible when OnSend
// is fired.
//PARAMS:
// nError Error code
//CREATED:
// 26-11-2000, 19:46:38 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnSend( int nError )
{
TRACE( _T("CSockClient::OnSend( %d )\n"), nError );
}
// ***************************************************************************
//DESCRIPTION:
// The FD_CLOSE network event is recorded when a close indication is
// received for the virtual circuit corresponding to the socket. In TCP
// terms, this means that the FD_CLOSE is recorded when the connection
// goes into the TIME WAIT or CLOSE WAIT states. This results from the
// remote end performing a shutdown on the send side or a closesocket.
// FD_CLOSE being posted after all data is read from a socket. An
// application should check for remaining data upon receipt of FD_CLOSE
// to avoid any possibility of losing data.
//PARAMS:
// nError Error code
//CREATED:
// 26-11-2000, 16:23:21 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnClose( int nError )
{
TRACE( _T("CSockClient::OnClose( %d )\n"), nError );
//Check for any remaining data
char chBuff[WINSOCK_READ_BUFF_SIZE+1];
while( Receive( chBuff, WINSOCK_READ_BUFF_SIZE ) )
{
//TODO : Process the read data.
}
}
Note how OnClose empties the buffer before exiting. Now implement
the OnRecieve event to read in any data. Note, a
read must be performed to ensure further events. This is where
you process the incoming data.
// ***************************************************************************
//DESCRIPTION:
// Received data event.
// For FD_READ network event, network event recording and event object
// signalling are level-triggered. This means that if Receive() routine
// is called and data is still in the input buffer after the call, the
// FD_READ event is recorded and FD_READ event object is set. This allows
// an application to be event-driven and not be concerned with the amount
// of data that arrives at any one time
// With these semantics, an application need not read all available data
//PARAMS:
// nError Error code
//CREATED:
// 26-11-2000, 16:54:46 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnRecieve( int nError )
{
TRACE( _T("CSockClient::OnRecieve( %d )\n"), nError );
char chBuff[WINSOCK_READ_BUFF_SIZE+1];
int nRead;
while( ( nRead = Receive( chBuff, WINSOCK_READ_BUFF_SIZE ) ) > 0 )
{
//TODO: Use the data here...
//Display the input data
TRACE( _T("Recieved( ") );
for( int nPos = 0; nPos < nRead; nPos++ )
TRACE( _T("%c"), chBuff[nPos] );
TRACE( _T(" )\n") );
}
}
Now create a socket object and connect to the server. Do this where
it will not go out of scope during use. A member attribute is
suitable.
#include "SockClient.h"
....
if( CSockListener::SocketInit() )
//TODO : Some error handling
....
CSockClient m_sock;
....
if( !m_sock.Create() ||
!m_sock.Connect( _T("127.0.0.1"), 1055 ) )
{
TCHAR szErrorMsg[WSA_ERROR_LEN];
CSockClient::WSAGetLastErrorMessage( szErrorMsg );
TRACE( _T("Socket Create/Connect failed Error: %s"), szErrorMsg );
return -1;
}
...
//When it is time to send
if( m_sock.IsConnected() )
m_sock.Send( "Hello", 5 );
Create Server.
Create a new project or open an existing project. Put WinSock2Async.h
, WinSock2Async.cpp , SockClient.cpp
and SockClient.h from the client project into this
project directory.
Add WinSock2Async.cpp and SockCient.cpp
to the project work space.
From the menu select Insert->New Class ..., set
the Name as CSockListener and Base
class of CWinSock2Async , public.
Add the following prototypes to the SockListener.h
file.
... Before the class definition
#include "SockClient.h"
... In the class definition
//Event handlers
private:
void OnAccept( int nError );
//Attributes
private:
CSockClient* m_psockClient; //Client connection
Implement the OnAccept handler to create a client connection and return
it to communicate on. Note: this implementation allows only a
single client at any one time. If another client attempts to connect
the previous client will be disconnected.
// ***************************************************************************
//DESCRIPTION:
// Called by the framework to notify a listening socket that it can
// accept pending connection requests by calling the Accept member
// function.
//PARAMS:
// The most recent error on a socket. The following error codes
// applies to the OnAccept member function:
// 0 The function executed successfully.
// WSAENETDOWN The Windows Sockets implementation detected
// that the network subsystem failed.
//CREATED:
// 2-12-2000, 7:10:28 AM by john@mctainsh.com
// ***************************************************************************
void CSockListener::OnAccept( int nError )
{
TRACE( _T("CSockListener::OnAccept(%d)\n"), nError );
//Close of the old connection and create a new one
if( m_psockClient )
delete m_psockClient;
Sleep( 1 );
//Create the new connection
m_psockClient = new CSockClient;
if( Accept( m_psockClient ) == 0 )
{
char szErrMessage[WSA_ERROR_LEN+1];
WSAGetLastErrorMessage( szErrMessage );
TRACE( _T("ERROR on Accept %s\n"), szErrMessage );
return;
}
//Display connection details
char szAddress[17];
int nPort;
if( m_psockClient->GetPeerName( szAddress, &nPort ) == 0 )
TRACE( _T("Connected to %s on port %d\n"), szAddress, nPort );
}
... Also Modify the CSockClient::OnRecieve as follows
void CSockClient::OnRecieve( int nError )
{
TRACE( _T("CSockClient::OnRecieve( %d )\n"), nError );
char chBuff[WINSOCK_READ_BUFF_SIZE+1];
int nRead;
while( ( nRead = Recieve( chBuff, WINSOCK_READ_BUFF_SIZE ) ) > 0 )
{
//TODO: Use the data here...
//Modify the data and echo
for( int nPos = 0; nPos < nRead; nPos++ )
{
TRACE( "%c ", chBuff[nPos] );
chBuff[nPos] = chBuff[nPos] + 1;
}
Send( chBuff, nRead );
}
}
Now setup the listener in your applications initialisation routine.
#Include "SockListener.h"
...
//Initialise Winsock API
if( CSockListener::SocketInit() )
{
printf( _T("*** WinSock API could not be initialised\n") );
return -1;
}
//Create a simple listener
CSockListener sockListener;
if( !sockListener.Create( 392 ) ) //enter port to listen in here
{
char szWASError[WSA_ERROR_LEN];
sockListener.WSAGetLastErrorMessage( szWASError );
printf( _T("*** Create Error: %s\n"), szWASError );
return -1;
}
if( !sockListener.Listen() )
{
char szWASError[WSA_ERROR_LEN];
sockListener.WSAGetLastErrorMessage( szWASError );
printf( _T("*** Listen Error: %s\n"), szWASError );
return -1;
}
That should be it. Now just test our application be running telnet.
From the command line type telnet 127.0.0.1 392 .
It should start and echo the characters you type in plus 1. Start
another telnet session and the old one should terminate..
Things to watch out for.
Sockets are simple to use but do require attention in the following
areas;
Reuse our code
In the sample code I have duplicated the Client and Listener code
all over the place. If our are writing both the client and server
share as much code a possible.
Data is not packets
Developers often fall into the trap off assuming data sent with
a single Send() will be result in a single OnRecieve() .
This is NOT guaranteed to occur, and must assume packets will
be broken up or merged together.
Connection loss is not always detectable
The OnClose() event can be used to detect the remote machine shutting
down only if network connection between the machines is available
and the process terminates correctly. For this reason it is often
advisable to maintain a ping or heartbeat message between the
clients to ensure both machines are functioning correctly.
Don't try to reuse sockets
If connection is closed or lost for any reason, do not try to reopen
the connection with the same socket object. It is best to create
the objects on the heap with new and delete
them once they are done with. If you need to reconnect. delete
the object and use new to create a new one before reconnecting.
Local machine
It is handy to test and often connect to a server that is running
on the same machine as the client this is know as the localhost
or IP address 127.0.0.1 .
Resolving address names takes time
Most operations are non-blocking, however when a connection is attempted
the address must be resolved from a name to a 4 digit IP. If a Domain
Name Server is not available this process can block for 30 seconds
or more. To avoid this always use 4 digit IP addresses rather than
domain names.
|