CAVEAT: This document assumes that you are familiar with ANSI C
programming. It is intended as a brief introduction to the concept of TCP/IP socket access
to ADR cards. The sample code is provided for demonstration purposes only. Although the
programs compile and run they are not a complete implementation.
This page describes the C code in the server example program.
First we include a pile of header files for all the API's and structures that we use.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <Winsock.h>
#include <errno.h>
#include <ctype.h>
#include <winbase.h>
The serial port read/write routines are compiled separately and will not be described
here. You can read the details on the page about accessing serial ports using
Visual C++ with MFC. (note: converting the C++ code to plain C code is
trivial).
#include "ComPort.h" // declaration of our serial port routines
The PrintHelp function provides some terse assistance for the user
void PrintHelp(); // declaration of our help function
Now we declare our main procedure and our local variables
//
// ADRSockSrv main entry point
//
void main (int argc, char *argv[])
{
BOOL bStillOK;
BOOL bReadRC;
BOOL bWriteRC;
BOOL bAwaitingReturn;
int iSinLen; // sockaddr_in length
int iLength;
int bindrc; // return code from bind
int iRC;
int iSpot;
struct sockaddr_in sock_addr; // socket address
struct servent *pServent; // server entry
SOCKET iSocket; // main TCP socket
SOCKET fd; // socket for each connection
WORD wVersionRequested; // Microsoftism
WSADATA wsaData;
// char buffers placed after fixed length variables so any
// overflow corrupts locals first... <-- easier to debug
char sBuffer[1024]; // data buffer
char sReplyBuffer[256];
bStillOK = TRUE; // assume things are OK
We expect 2 arguments: the first is the program name and the second is the serial port
that the ADR card is plugged into. We show our help messages for fewer than 2 arguments or
if the user requests some help.
// first check args
if (argc < 2 || !strcmp(argv[1],"-h") || !strcmp(argv[1],"-H")
|| !strcmp(argv[1],"-?") || !strcmp(argv[1],"/h")
|| !strcmp(argv[1],"/H") || !strcmp(argv[1],"/?")
|| !strcmp(argv[1],"help") || !strcmp(argv[1],"HELP"))
{
PrintHelp();
bStillOK = FALSE;
} // end if
We invoke the separately compiled routine to initialize the serial port. (ie. open it
for I/O).
if (bStillOK)
{
// setup serial port and check ADR card
bStillOK = InitializeComPort(argv[1]);
} // end if
Microsoft introduced the WSAStartup call back in Windows 3.1 days to initialize the
Windows Sockets Architecture. Unix systems had socket support built in and so did not
require a special startup call. Socket support is now built into all current Windows
operating systems, but Microsoft continues to use the WSAStartup call as a mechanism for
version control. Below we are requesting version 1.1 support.
if (bStillOK)
{
// set up the TCP/IP protocol
wVersionRequested = MAKEWORD (1,1);
if((iRC = WSAStartup(wVersionRequested, &wsaData)) != 0)
{
printf ("startup failed, rc= %d\n", iRC);
bStillOK = FALSE;
} // end if
} // end if
The getservbyname call retrieves the port number for the ADRlink service from the
Services file. Notice that we only support tcp (Transmission Control Protocol) and do not
support udp (User Datagram Protocol).
if (bStillOK)
{
if ((pServent = getservbyname ("ADRlink", "tcp")) == NULL)
{
printf ("%s: unknown service ADRlink: ", argv[0]);
iRC = WSAGetLastError();
printf ("error code is %d - check SERVICES file\n",iRC);
WSACleanup();
bStillOK = FALSE;
} // end if
} // end if
Here we create a socket. It will be a stream socket (SOCK_STREAM) within the protocol
family for the Internet (PF_INET). Note that TCP implies a stream socket. Also note that
Microsoft does not support any other protocol families. It appears that the PF_INET
parameter is present for consistency with Unix implementations.
if (bStillOK)
{
if ((iSocket = socket (PF_INET, SOCK_STREAM, 0)) < 0)
{
printf ("socket error:");
iRC = WSAGetLastError();
printf (" error code is %d\n", iRC);
WSACleanup();
bStillOK = FALSE;
} // end if
} // end if
Next we bind the socket to the port number that we retrieved earlier. This socket will
accept only connections that want the ADRlink service. This prevents interference with
other services running on the computer. Each service has its own unique port number.
Any client's incoming address will be accepted (INADDR_ANY). AF_INET is just a synonym
for the PF_INET value that was already used (in fact Microsoft #defines these to be the
same)
if (bStillOK)
{
sock_addr.sin_port = pServent->s_port;
sock_addr.sin_addr.s_addr = INADDR_ANY;
sock_addr.sin_family = AF_INET;
if ((bindrc = bind (iSocket, (struct sockaddr *) &sock_addr,
sizeof (sock_addr))) < 0)
{
printf ("bind error: rc is %d ",bindrc);
iRC = WSAGetLastError();
printf ("error code is %d\n",iRC);
bStillOK = FALSE;
} // end if
} // end if
Now we prepare the socket to listen for connections. The number, 5, indicates the
number of backlogged requests that can be queued before our server starts refusing
connections.
if (bStillOK)
{
listen (iSocket, 5);
printf ("server is now listening\n");
} // end if
We are now ready for the real core of the server processing
The while-loop allows us to handle multiple clients. Commands from clients are serviced
serially, (ie. we finish up all processing for a command before we start working on the
next command. Multi-threading is beyond the scope of this tutorial.)
The accept call waits for a client to request a connection. When a connection is
accepted a new socket is created and connected to the client. In our example fd is a new
socket that is connected to a client. It is important to realize that fd is a socket in
its own right and is separate from the original iSocket. (Aside: fd stands for file
descriptor)
while (bStillOK)
{
printf(" accepting a socket \n");
iSinLen = sizeof(sock_addr);
fd = accept (iSocket, (struct sockaddr *) &sock_addr,
&iSinLen);
//Sleep(500); // for testing
if (fd == INVALID_SOCKET)
{
printf ("accept error: ");
iRC = WSAGetLastError();
printf ("error code is %d\n",iRC);
WSACleanup();
bStillOK = FALSE;
} // end if
We will receive (recv) the message from the client through the new socket. We loop over
the recv call until a complete command is received (indicated by a trailing carriage
return).
WARNING: the code in this loop is not robust enough for production applications.
if (bStillOK)
{
printf ("socket %d accepted as fd = %d\n", iSocket, fd);
// paranoid, who me?
memset(sBuffer, 0, sizeof(sBuffer));
memset(sReplyBuffer, 0, sizeof(sReplyBuffer));
bAwaitingReturn = TRUE;
while (bAwaitingReturn
&& (iLength = recv (fd, sBuffer, 1024, 0)) > 0)
{
for (iSpot = 0; iSpot < iLength; iSpot++)
{
if (sBuffer[iSpot] == '\r')
{
bAwaitingReturn = FALSE;
} // end if
} // end loop
sBuffer[iLength] = '\0'; // terminate the string
printf (" Message received: LL:%d Data: %s \n",
iLength,sBuffer);
} // end loop
if (iLength < 0)
{
printf ("error on read: %d ",iLength);
iRC = WSAGetLastError();
printf ("error code is %d\n",iRC);
bStillOK = FALSE;
strcpy(sReplyBuffer,"Server Error");
} // end if
} // end if
The WriteComPort call is an output function in ComPort.c that writes the command to the
serial port. The ADR card then executes the command and optionally returns a response over
the serial port. Notice that the command starts in the second byte of sBuffer.
if (bStillOK)
{
bWriteRC = WriteComPort(&sBuffer[1]);
if (!bWriteRC)
{
printf("error invoking WriteComPort\n");
// damn the torpedoes - keep going
//bStillOK = FALSE;
} // end if
} // end if
The ReadComPort call is an input function in ComPort.c that reads from the serial port.
The response from the serial card is read if the first character of the input message is a
"y".
if(bStillOK && (sBuffer[0] == 'y' || sBuffer[0] == 'Y'))
{
bReadRC = ReadComPort(sReplyBuffer,
sizeof(sReplyBuffer));
if (!bReadRC)
{
printf("error invoking ReadComPort\n");
//bStillOK = FALSE;
strcpy(sReplyBuffer,"No Response");
} // end if
} // end if
The response is now transmitted back to the client. The send call uses the socket that
was opened for the client connection.
if(bStillOK && (sBuffer[0] == 'y'|| sBuffer[0] == 'Y'))
{
printf ("Reply: %s\n",sReplyBuffer);
iRC = send (fd, sReplyBuffer, strlen(sReplyBuffer), 0);
if (iRC < 0)
{
printf ("send reply error: RC = %d ",iRC);
iRC = WSAGetLastError();
printf ("error code is %d\n",iRC);
} // end if
} // end if
We close the client connection and loop back to the accept call to wait for the next
client connection. Note that the fd socket is closed. The iSocket remains open for new
connections. (The fd socket is returned to the pool of free sockets and is available for
reuse.)
printf ("terminating socket %d\n",fd);
closesocket (fd);
} // end of forever loop
When the server terminates we close the serial port, close the socket and release the
WSA resources. Observant readers may notice that the termination code is never executed
since the only mechanisms for stopping the server involve terminating it prematurely.
(i.e. cancel it with <ctrl><c>, close its window or end its task.). The
example simply continues trying to accept client connections until the program is
cancelled. You will have to modify this behaviour to detect the termination conditions
appropriate for your application.
TerminateComPort();
closesocket (iSocket);
WSACleanup();
} // end main
To retrieve the source code used in this example via email, send your name and
address to tfortin@vianet.on.ca ( for our snail
mail list ) and we will send the example via email.
Please indicate that you want the ADRSocketDemo. |