Phone:(705) 671-2652
    Fax:(705) 671-6127
   Email: tom@ontrak.net
Home Products Programming Applications Custom Designs Ordering News
    
ADRSockSrv Example Code
 

oversrvr.jpg (13,817 bytes)

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.

 
    
Contact Information
Address Telephone Electronic Mail
Ontrak Control Systems Inc.
764 Notre Dame Ave., Unit #1
Tel: (705) 671-2652 General Information Sales or Support
Sudbury,Ontario Fax:(705) 671-6127 tom@ontrak.net
CANADA P3A 2T2    
              
Next-Day shipping anywhere in Continental U.S. via Fed-Ex
Copyright 2012 ONTRAK CONTROL SYSTEMS 764 Notre Dame Ave, Unit #1 , Sudbury, Ontario, CANADA P3A 2T2  
"Quality serial Data Acquisition Interfaces"           PH. (705) 671-2652 FAX (705) 671-6127