Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Programming With Communication Protocol Stacks


MAR92: PROGRAMMING WITH COMMUNICATION PROTOCOL STACKS

Gordon, a software architect with Traveling Software, can be reached at Traveling Software Inc., 18702 North Creek Parkway, Bothell, WA 98011; or through MCI mail as Gordon Free EMS: 402-5717 MBX:HAL/MISSLINKS/GordonF.


In case you haven't noticed, a quiet revolution has been taking place. Programs that once lived in isolation are now using technologies such as DDE to talk to other programs. PCs that once sat lonely on the desktop are now networked together, and systems now come standard equipped with modems. In this "age of communications," users routinely expect a high degree of intercommunication, which has placed additional demands on software developers. Writing reliable communications software is a demanding task and can eat up a huge chunk of a project's resources. Although there are many communications libraries available to ease the burden, most operate at only the lowest level and still require the application writer to know a great deal about the underlying hardware with which it communicates. Ideally, the application developer should have to concentrate only on the task of interfacing with the remote application and not worry about how data gets there. This requires a fairly high-level interface that is independent of the underlying transmission media.

The ISO reference model specifies just such an interface, and Traveling Software has developed a communications engine based on that specification. This article explores that engine and presents a file transfer program that uses it.

The ISO Reference Model

The ISO model specifies seven layers of abstraction, each successive one more removed from the underlying hardware than its predecessor. These seven layers, shown in Table 1, are referred to as a "protocol stack." Note that each layer can be completely replaced with a new implementation without affecting other layers. Typically, the ISO model refers to communications over LANs and WANs, but it can be extended to almost any connection medium.

Table 1: The seven layers of abstraction as specified in the ISO model

  Layer         Description
  ----------------------------------------------------------------------

  Physical      Defines how data is electrically moved between two
                 physically connected machines.

  Datalink      Breaks data up into manageable packets and ensures that
                 they arrive intact and in order.

  Network       Determines route data will take to reach target machine.

  Transport     Maintains virtual connections between local and remote
                 machine.

  Session       Maintains communication sessions and may include ability to
                 reestablish broken connections automatically.

  Presentation  Specifies the format in which data will be represented.
                 This is especially important when communicating across
                 different platforms.

  Application   The user application level which determines the contents
                 and meaning of data.

The ISO model protocol provides an application with a reliable link to a remote application. Data is guaranteed to arrive intact and in order. Once the application submits data for transmission, it can assume the data will arrive without further intervention.

Blackbird

Blackbird, a communications engine based on the ISO reference model architecture, was developed by Traveling Software for use in its file transfer software. The Blackbird library provides the application with a reliable connection to remote services, regardless of the transmission media connecting the two machines. Server applications register a service name with Blackbird which remote clients can query. A client requests a connection to a remote service and receives a Virtual Connection (VC) handle similar to a file handle. Communications can then proceed with read and write operations that use the VC handle. It's called a "virtual connection" because multiple connections can time-slice the transmission medium simultaneously. It appears to the application, however, that it has exclusive use of the communications channel. Internally, Blackbird implements the four lowest layers of the ISO model. Blackbird handles initialization of the low-level hardware -- splitting up the application data into manageable pieces (packets) -- and retransmissions of data corrupted in transit. Table 2 provides a list of the available Blackbird API functions.

Table 2: Blackbird API routines

  API Function       Description
  -------------------------------------------------------------------

  bbAppPoll          Give a slice of CPU time to aid Blackbird.
  bbAppPollStatus    Set or clear exclusive application polling.
  sGetRemSvc         Get remote service information.
  vcConnect          Connect to remote service.
  vcDisconnect       Disconnect from remote service.
  vcEnd              Uninstall Blackbird.
  vcGetPortHandle    Get the handle of the port associated with a VC.
  vcInit             Initialize Blackbird.
  vcListen           Broadcast a new remote service.
  vcModemAbort       Abort a modem call in progress.
  vcModemDial        Dial out over a modem.
  vcModemHangup      Hang up a modem.
  vcPortDetect       Detect and initialize available H/W ports.
  vcPortQuery        Request communication port configuration info.
  vcPortSet          Set communications port configuration.
  vcPortVCDisconnec  Disconnect all virtual connections over a port.
  vcQuery            Query for list of remote service handles.
  vcRead             Read data over a virtual connection.
  vcReadTerminate    Terminate a pending read operation.
  vcRemsvcUpdate     Request notification of new remote services.
  vcSelfTest         Perform a selftest on specified port.
  vcVersion          Obtain the Blackbird version string.
  vcWrite            Write data over a virtual connection.
  vcWriteTerminate   Terminate a pending write operation.

One of Blackbird's strengths is the variety of transmission media it supports. All this diversity is hidden from the application (because it deals only with remote services and VC handles). Any number of transmission methods may be simultaneously managed by Blackbird. For example, Blackbird supports communications between two computers connected through serial ports using a null modem cable. These ports can be any of COM1 through COM4. The application, and hence the user, need not be concerned about which ports are used (although Blackbird does provide the ability for direct manipulation of the ports). A proprietary protocol at the physical layer allows a serial communications rate in excess of 200 KBaud.

Additionally, Blackbird provides even higher transmission rates for machines connected through parallel ports (LPT1 through LPT3). Because data can be transmitted over as many as eight wires at once, much higher transfer rates are possible. The exact rate depends on the CPU and I/O speed of the two machines. Blackbird also supports communications between two distant machines connected via modems with data rates between 1200 and 38,400 baud. This mode of communication does require some additional support from the application (in particular, modem initialization and dialing).

Function Callback

Depending on the bandwidth of the communications channel used, read/write operations may not complete in a timely fashion. It is therefore desirable for the application to continue processing while communications proceed in the background. At some point, the application will need to be notified that the operation has completed. This is done through the use of callback routines which the application provides to Blackbird as a parameter to the read and write functions.

This approach works especially well with applications structured around an event-driven architecture (such as Windows, TurboVision, or D-Flat). In this environment, the callback routine can simply post an appropriate message to the object or window responsible for handling the event.

The callback paradigm works fine for standard procedural architectures as well. In this case, additional considerations must be taken into account. The callback is an asynchronous event which can occur at any time. It will typically occur as a result of a hardware interrupt, and as such, the application is limited to tasks it may perform inside a callback routine. Of course, the golden rule for any interrupt routine is to minimize the amount of time it spends processing. Because the callback routine is part of the interrupt handler, throughput is maximized by keeping callback processing short and to the point.

Interrupts can occur at any time; therefore, applications should not make assumptions about which stack is in use at the time of a callback--it's unlikely to be the application's own stack. As such, the application should not make assumptions about how much free space is left on the stack and should minimize the amount of local variable space used. With large-model applications it may be safest not to assume that DS will equal SS during execution of callback routines.

Most C compilers generate code to test for stack overruns at the start of each subroutine. Because the current stack in use at the time of callback may not belong to the application, this test will likely fail, and an erroneous stack overrun may be reported. This can be fixed by disabling the compiler from generating the stack-checking code for callback routines. This is usually done as a command-line option, but has the disadvantage of disabling it for all routines. A #pragma can be used to disable stack checking for those routines that don't need it. Remember, stack checking must also be disabled for any routines called by the callback routine. This includes runtime library routines. Your compiler documentation should indicate which routines are available without stack checking. The Microsoft C runtime library routines which have stack checking enabled include execvp, execvpe, fprintf, fscanf, printf, scanf, spawnvp, spawnvpe, sprintf, sscanf, system, vprintf, and write.

Callbacks occur asynchronously, so the application has no way of knowing what operation was in process at the time of the interrupt. It could likely be a DOS operation such as file I/O or a keyboard input routine. Because DOS is not reentrant, callback routines should not make DOS calls unless they can ensure that it is safe to do so. (This same method is used for TSRs.) Note too that callback routines are themselves subject to interruptions and should therefore be rewritten as reentrant. Interrupts should be disabled to protect any critical sections of code.

It is entirely possible for an operation to complete before the initial request returns to the application. This would result in the application's callback routine being called before the original read or write call returns! Because of this, the application should initialize any information needed by the callback prior to submitting the request. Given all these restrictions, it is safest to limit callback routines to setting global variables and then setting a semaphore which the main-line code can poll on a periodic basis.

Applications

Blackbird can be used to support almost any application requiring machine-to-machine communication. One of the most common applications is to transfer files from machine to machine. Not surprisingly, Blackbird was designed with this in mind and provides additional features to support it. In particular, applications can specify "breathe" routines, which get called between each packet. This allows timely reporting of progress (perhaps updating a bar graph) as well as fairly quick aborting of operations.

Another common use for remote communications is electronic mail and messaging. While this can be accomplished using standard network support, a peer-to-peer method such as Blackbird allows instantaneous notification and does not require a dedicated server machine. Also, remote control is an increasingly popular type of remote application. It allows a user to access a machine via modem or network. Data traffic tends to occur in bursts and in smaller amounts than with file transfer. Blackbird provides mechanisms for handling small amounts of data with less overhead that required for larger data buffers. Finally, a peer-to-peer library such as Blackbird could be used to extend Dynamic Data Exchange (such as found in Windows) to include applications running on remote machines.

A File Transfer Program

To illustrate how you write programs using the Blackbird engine, I wrote a sample file transfer application, called "FT," which uses Blackbird to exchange files between two machines. The two machines may be connected via serial or parallel cables, network, or modem. In the interest of clarity, FT only transfers files smaller than 64K and does minimal error checking. The user interface is also rather primitive.

One of the more interesting aspects of FT is the fact that one module serves as both a server and a client application. While it would be possible to write separate server and client applications, the chosen approach allows a machine to act as a server while simultaneously accessing another as a client. This creates little overhead to the application because Blackbird handles all routing internally.

Listing One, page 92, presents ft.h. The main portion of FT (see Listing Two, page 92) simply consists of a small initialization section and a loop which alternates between checking for user requests from the keyboard and completed Blackbird events. As with most communications libraries, the first step to using Blackbird is to initialize its internal structures and hook any necessary interrupt vectors. This is done by calling vcInit with a machine and group name. FT gets the machine name from the command line. If a machine name is provided, FT goes on to register a file transfer service, the availability of which will be broadcast to all remote machines. This is done by calling vcListen with the name of the service (FXFR), a group name, and two callback routines. The first callback routine will be called by Blackbird when a remote application requests a virtual connection. The second specifies a routine to be called anytime the connection gets broken.

Applications may query Blackbird for a list of available remote services at any time. Blackbird calls vcQuery to get a list of remote service handles. It then calls sGetRemSvc for each handle; see DisplayServices in Listing One. FT takes advantage of a feature in Blackbird which allows automatic notification anytime a new service appears or goes away. This is done by calling vcRemsvcUpdate immediately after calling vcInit. FT provides Blackbird with a callback routine (in this case cbRemService) which Blackbird will call whenever the remote service list changes. cbRemService simply sets a global flag, fUpdateServices, which tells CheckEvents that it needs to display the list of remote services.

Establishing a Virtual Connection

The user requests FT to establish a virtual connection with a remote machine by pressing the handle number associated with the remote service. FT allows only one outgoing virtual connection at a time. It will support any number of simultaneous remote clients, however (within the limits supported by Blackbird, which is currently about five).

When a virtual connection is established, each side allocates a buffer for use in transferring data and stores pertinent information into the svc structure. A vcRead is the posted to allow reception of remote requests and responses.

Processing User Requests

The user requests a file to be sent to or from the remote server by pressing the S or R keys, respectively. If the request is for a send operation, DoUserRequest will call SendFile to read the contents of the file and post a vcWrite with the target filename and the contents of the file. In the case of a receive operation, only the filename is sent to the remote.

Blackbird actually sends two buffers of data with each write and read operation. One is the data buffer provided by the application and the other is a header packet used for internal routing and sequence checking. Blackbird allows applications to tack their own data onto the header packet, provided it does not exceed 220 bytes in length. FT uses this additional space to send the filename along with its contents all in one operation. In the interest of keeping things simple, the remote does not report whether the file was successfully saved or not. Obviously, this would be a desirable enhancement. Another possible enhancement would provide the ability to query the remote server for a list of files. This would give FT capabilities similar to those found in DR-DOS.

Processing Blackbird Events

As discussed previously, Blackbird informs the application of operation completions through the use of callback routines. Listing Three (page 96) shows the callback routines used by FT. Notice that a single callback routine is used for each type of callback.

When a Blackbird event completes, the appropriate callback routine is invoked and provided with the VC handle corresponding to the completed operation. The callback routine uses this information to set a bit in the global flags indicating a completed operation. If Blackbird provided the reason for the callback in addition to the VC handle, we could get by with even fewer callback routines.

Back in the main-line section of code, CheckEvents loops through each virtual connection, looking for Blackbird events. When it finds an event on a particular VC, it determines the type of event and takes appropriate action. Notice that it disables interrupts while it makes a copy of the event flags and then clears them. This ensures that we don't miss an event that might occur between these two steps.

The only Blackbird event that involves significant action is the case in which a read operation has completed. This indicates that the remote has responded to our request for a file or is making a request of its own. CheckEvents simply looks at the command byte in the header buffer to determine whether it needs to write the data buffer to disk or satisfy the remote's request for the contents of a file. The details of these two operations are handled by SaveFile and SendFile, respectively.

Disconnecting is straightforward, and either side may break a virtual connection. This is done by calling vcDisconnect with the appropriate VC handle.

Conclusion

While FT is written to be used with Blackbird, it could very easily be modified to use a NetBIOS or IPX/SPX protocol. Alternatively, a Blackbird-like interface could be written to access the various functions of these protocols, allowing FT to be used as is.



_PROGRAMMING WITH COMMUNCIATION PROTOCOL STACKS_
by Gordon Free


[LISTING ONE]
<a name="0096_0010">

#ifndef FT_H
#define FT_H

#define  HVC_FIRST   2
#define  HVC_LAST    5
#define  MAX_VCS     HVC_LAST-HVC_FIRST+1
#define  HDRSIZE     MAX_APPHDRDATA+sizeof(BBHDR)

/* Message command values */
#define  FT2_SEND    3
#define  FT2_RECEIVE 4

/* Bit fields for Blackbird events */
typedef union {
   struct  {
      unsigned ListenCallback : 1;
      unsigned ConnectCallback : 1;
      unsigned ReadBreathe : 1;
      unsigned ReadCallback : 1;
      unsigned WriteBreathe : 1;
      unsigned WriteCallback : 1;
      unsigned DisconnectCallback : 1;
   }     flags;
   int   all;
} EVENT_T;

/* Structure for info on currently open file */
typedef struct _FILEDATA {
   FILE  *hStream;         /* stream handle                         */
   char  *pszName;         /* name of file                          */
   char  *pszOpenMode;     /* mode file is opened in "r", "w", etc. */
   long  lSize;            /* size of file in bytes                 */
   void  *pvBuffer;        /* ptr to buffer of file contents        */
} FILEDATA_T;

/* Structure for application service, one per VC */
typedef struct _SVCDATA {
   FILEDATA_T  fdCurFile;           /* current file info       */
   char        bbhXmit[HDRSIZE];    /* BB hdr for sending      */
   char        bbhRcv[HDRSIZE];     /* BB hdr for rcving       */
   char        *pszRemoteName;      /* name of remote machine  */
   long        lBytesSoFar;         /* num bytes xferred       */
   int         fActive;             /* TRUE if VC is active    */
   short       sStatus;             /* status of last BB event */
   EVENT_T     fEvent;              /* BB event flags          */
} SVCDATA_T;

/*----- Callback routine prototypes ------*/
void cbRemService (
   unsigned  usNumSvcs   /* Number of remote services  */
);
SHORT cbListen (
   HANDLE   hVC,        /* Virtual connection handle     */
   ULONG    ul1,        /* Nothing at this time          */
   ULONG    ul2         /* Status = SHORT1FROMUL( ul2 )  */
);
SHORT cbConnect (
   HANDLE   hVC,        /* Virtual connection handle     */
   ULONG    ul1,        /* Nothing at this time          */
   ULONG    ul2         /* Status = SHORT1FROMUL( ul2 )  */
);
SHORT cbDisConnect (
   HANDLE   hVC,        /* Virtual connection handle     */
   ULONG    ul1,        /* Nothing at this time          */
   ULONG    ul2         /* Status = SHORT1FROMUL( ul2 )  */
);
SHORT cbRead (
   HANDLE   hVC,        /* Virtual connection handle     */
   ULONG    ul1,        /* Number of bytes read          */
   ULONG    ul2         /* Status = SHORT1FROMUL( ul2 )  */
);
SHORT cbWrite (
   HANDLE   hVC,        /* Virtual connection handle     */
   ULONG    ul1,        /* Number of bytes sent          */
   ULONG    ul2         /* Status = SHORT1FROMUL( ul2 )  */
);
#endif






<a name="0096_0011">
<a name="0096_0012">
[LISTING TWO]
<a name="0096_0012">

/*====================================================================
  (c) Copyright 1991, Gordon Free
  All Rights Reserved.
--------------------------------------------------------------------
 Filename: FT.C
 Project: DDJ Article
 $Author$
 $Revision$
 $Date$
 Purpose: Sample file transfer program written for Blackbird API.
====================================================================*/

/*----- Includes ------*/
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <string.h>
#include <dos.h>
#include <fcntl.h>
#include <io.h>
#include <sys\types.h>
#include <sys\stat.h>
#include "vcapi.h"
#include "ft.h"

/*----- Local Defines and Typedefs ------ */
#define  ESC         0x1B
#define  BUFF_SIZE   (64*1024-1)

/*------ Global Variables -----*/
int            fUpdateServices=FALSE;  /* remote service list has changed */
HANDLE         hvcServer=HVC_NONE;     /* vc handle of remote server      */
SVCDATA_T      svc[MAX_VCS];           /* array of app service info       */
char           acMachineName[MAX_MACHID] = "Unknown";

/*---- DisplayServices: prints a report of all available remote services ----*/
void DisplayServices(void)
{
   int            nServices;           /* num of services reported    */
   REMSERVICES    remserv;             /* service info struct         */
   static HANDLE  ahRS[10];            /* array of remote svc handles */
   int            i;

   puts("\nList of Remote Machines ...");
   puts("---------------------------");

   /* Fill in array of remote service handles */
   nServices = vcQuery(ahRS, HBOUND(ahRS));

   /* Report service handle and machine name for each remote service */
   for (i=0; i<nServices; i++) {
      sGetRemSvc(ahRS[i], &remserv);
      printf("   %d) %s on %s %c\n", ahRS[i], remserv.acMachineName
         , remserv.acPortName
         , remserv.fInUse ? '*' : ' ');
   }
}

/*-- SendFile: read specified file and hand it to Blackbird for delivery --*/
size_t SendFile (
   HANDLE      vch,           /* handle of virtual connection */
   char        *name          /* name of file */
)
{
   int         size=0;        /* number of bytes read */
   int         fileOut;       /* file stream to read from */
   PBBHDR      pbbhXmit;      /* ptr to BB header */
   SVCDATA_T   *psvc;         /* ptr to service struct for this vc */

   printf("Sending %s ...\n", name);
   fileOut = open(name,O_RDONLY|O_BINARY);
   if (fileOut > 0) {
      psvc = &svc[vch-HVC_FIRST];

      /* Read contents of file into buffer */
      size = read(fileOut, psvc->fdCurFile.pvBuffer, BUFF_SIZE);
      close(fileOut);

      /* Put name into packet header */
      pbbhXmit = (PBBHDR)&(psvc->bbhXmit[0]);
      strcpy(&(pbbhXmit->bAppData[1]), name);

      /* Indicate what remote is to do with this data */
      pbbhXmit->bAppData[0] = FT2_RECEIVE;
      pbbhXmit->ubBytesToFollow = strlen(name)+2;

      vcWrite(vch,pbbhXmit, psvc->fdCurFile.pvBuffer, size
         , NULL, cbWrite, TWO_SECS);

   } else {
      perror("Error opening file");
   }
   return size;
}

/*----SaveFile: write contents of buffer into file with specified name----*/
int SaveFile (
   char  *name,
   long  lsize,
   void  far *buffer
)
{
   int        size;
   int        fileIn;
   printf("Saved file = %s\n", name);
   fileIn = open(name,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY);
   if (fileIn > 0) {
      size = (size_t) lsize;
      write(fileIn, buffer, size);
      close(fileIn);
   } else {
      perror("Error saving file");
   }
   return 0;
}

/*---CheckEvents: polls each virtual connection looking for events that
   have been flagged by the various callback routines.---*/
void CheckEvents ( void )
{
   EVENT_T     evt;
   SVCDATA_T  *psvc;
   PBBHDR      pbbhRcv;
   HANDLE      vch;
   void       *buff;
   int         i;
   /* Loop through all possible virtual connections */
   for (i=0, psvc=svc; i<MAX_VCS; i++, psvc++) {
      /* Check to see if anything happened */
      if (psvc->fEvent.all) {
         /* Get copy of event flags and then reset them. This is done with
         /* interrupts disabled so that we don't miss any events. */
         _disable();
         evt = psvc->fEvent;
         psvc->fEvent.all = FALSE;
         _enable();

         /* ------------------- Listen --------------------------- */
         /* Somebody connected to us, allocate storage and issue a */
         /* read to get file transfer requests. */
         if (evt.flags.ListenCallback) {
            puts("#Listen Callback");
            pbbhRcv = (PBBHDR)&(psvc->bbhRcv[0]);
            if (psvc->fdCurFile.pvBuffer != NULL) {
               vcRead(HVC_FIRST+i,pbbhRcv, psvc->fdCurFile.pvBuffer
                  , BUFF_SIZE, NULL, cbRead, TWO_SECS);
            } else {
               puts("Error allocating memory");
            }
            /* Register a new service for use by other clients */
            buff = malloc(BUFF_SIZE);
            if (buff != NULL) {
               vch = vcListen ("FXFR","DDJ", cbListen, cbDisConnect);
               svc[vch-HVC_FIRST].fdCurFile.pvBuffer = buff;
            } else {
               puts("Error allocating memory");
            }
         }

         /* ------------------- Connect -------------------------- */
         /* We've successfully connected to a remote server */
         if (evt.flags.ConnectCallback) {
            puts("#Connect Callback");
            hvcServer = HVC_FIRST+i;
         }

         /* ------------------- Read  --------------------------- */
         /* We've gotten a request, so process it! */
         if ((evt.flags.ReadCallback) && (psvc->sStatus == 0)) {

            pbbhRcv = (PBBHDR)&(psvc->bbhRcv[0]);
            /* Is it a request to receive a file? */
            if (pbbhRcv->bAppData[0] == FT2_RECEIVE) {
               SaveFile(&(pbbhRcv->bAppData[1]), psvc->lBytesSoFar
                  , psvc->fdCurFile.pvBuffer);
            /* How about send a file? */
            } else if (pbbhRcv->bAppData[0] == FT2_SEND) {
               SendFile(i+HVC_FIRST, &(pbbhRcv->bAppData[1]));
            /* No comprende */
            } else {
               puts("Huh?");
            }
            vcRead(HVC_FIRST+i,pbbhRcv, psvc->fdCurFile.pvBuffer
               , BUFF_SIZE, NULL, cbRead, TWO_SECS);
         }

         /* ------------------- Write --------------------------- */
         if ((evt.flags.WriteCallback) && (psvc->sStatus == 0)) {
            puts("#Write Complete");
         }

         /* ------------------- Disconnect ---------------------- */
         /* Remote went byebye */
         if (evt.flags.DisconnectCallback) {
            puts("#Disconnect Callback");
            free(psvc->fdCurFile.pvBuffer);
            if (i+HVC_FIRST == hvcServer)
               hvcServer = HVC_NONE;
         }
      }
   }
   if (fUpdateServices) {
      fUpdateServices = FALSE;
      DisplayServices();
   }
}

/*---DoUserRequests: check keyboard for activity and process user's
   command. Returns TRUE if user has not requested to exit.---*/
int DoUserRequests ( void )
{
   HANDLE      vch;
   SVCDATA_T  *psvc;

   PBBHDR      pbbhXmit, pbbhRcv;
   int         fContinue=TRUE;
   char        filename[80];
   void       *buff;
   int         c;

   if ( kbhit() ) {
      switch (c = getch()) {

         /* ------------------- Exit ----------------------------- */
         case ESC:
            fContinue = FALSE;
            break;

         /* ------------------- Connect -------------------------- */
         case '0':
         case '1':
         case '2':
         case '3':
         case '4':
         case '5':
         case '6':
         case '7':
         case '8':
         case '9':

            /* Disconnect from current server, if any. */
            /* Buffer will be released when disconnect completes */
            if (hvcServer != HVC_NONE) {
               vcDisconnect(hvcServer);
            }
            /* Allocate buffer for new service */
            buff = malloc(BUFF_SIZE);
            if (buff != NULL) {
               /* Connect to remote server */
               vch = vcConnect(c-'0', cbConnect, cbDisConnect, TWO_SECS);
               svc[vch-HVC_FIRST].fdCurFile.pvBuffer = buff;
            } else {
               puts("Error allocating memory");
            }
            break;

         /* ------------------- Send File ------------------------ */
         case 's':
         case 'S':
            if (hvcServer != HVC_NONE) {
               psvc = &svc[hvcServer-HVC_FIRST];
               printf("\nEnter name of file to send >");
               scanf("%s", filename);
               SendFile(hvcServer, filename);
            } else {
               puts("You must establish a connection first!");
            }
            break;

         /* ------------------- Receive File --------------------- */
         case 'r':
         case 'R':
            if (hvcServer != HVC_NONE) {
               psvc = &svc[hvcServer-HVC_FIRST];

               /* Set up ptrs to BB hdrs for receive and send */
               pbbhXmit = (PBBHDR)&(psvc->bbhXmit[0]);
               pbbhRcv = (PBBHDR)&(psvc->bbhRcv[0]);

               /* Instruct remote server to send specified file */
               printf("\nEnter name of file to receive >");
               scanf("%s", &(pbbhXmit->bAppData[1]));
               pbbhXmit->bAppData[0] = FT2_SEND;

               pbbhXmit->ubBytesToFollow
                  = strlen(&(pbbhXmit->bAppData[1]))+2;
               /* Post read before sending request so that we are ready */
               /* for response */
               vcRead(hvcServer,pbbhRcv, psvc->fdCurFile.pvBuffer
                  , BUFF_SIZE, NULL, cbRead, TWO_SECS);
               /* Send request for file */
               vcWrite(hvcServer,pbbhXmit, NULL, 0, NULL, cbWrite, TWO_SECS);
            } else {
               puts("You must establish a connection first!");
            }
         default:
            break;
      }

   }
   return fContinue;
}

/*----main: initalize Blackbird and alternate between processing Blackbird
      events and user requests. ----*/
int main (
   int   argc,
   char  **argv
)
{
   HANDLE      vch;
   void       *buff;
   puts("Blackbird File Transfer Sample Program ver 1.0");
   puts("   (c) Copyright 1991, Gordon Free.");
   puts("   All Rights Reserved.\n");
   puts(vcVersion());

   /* Parse machine name off command line */
   if (argc >= 2) {
      strncpy(acMachineName, argv[1], sizeof(acMachineName-1));
      acMachineName[sizeof(acMachineName-1)] = '\0';
   }

   /* Initialize Blackbird and request notification of remote services */
   vcInit(acMachineName,"DDJ");
   vcRemsvcUpdate(cbRemService);

   /* Register as a server only if user gave a machine name */
   if (argc >= 2) {
      buff = malloc(BUFF_SIZE);
      if (buff != NULL) {
         vch = vcListen ("FXFR","DDJ", cbListen, cbDisConnect);
         svc[vch-HVC_FIRST].fdCurFile.pvBuffer = buff;
      } else {
         puts("Error allocating memory");
         goto finish;
      }
   }

   /* Alternate between checking for Blackbird events and processing
      user requests. */
   do
   {
      CheckEvents();
   } while (DoUserRequests());
finish:
   puts("Exiting ...");
   vcEnd();
   return 0;
}






<a name="0096_0013">
<a name="0096_0014">
[LISTING THREE]
<a name="0096_0014">

/*--------------------------------------------------------------------
  (c) Copyright 1991, Gordon Free
  All Rights Reserved.
====================================================================
 Filename: FT_CB.C
 Project: DDJ Article
 $Author$
 $Revision$
 $Date$
 Purpose: Callback routines for Blackbird events
====================================================================*/

/*-----Includes-----*/
#include <stdio.h>
#include "vcapi.h"
#include "ft.h"

/*------Global Variables ------*/
extern   SVCDATA_T      svc[MAX_VCS];     /* service info for each VC    */
extern   int            fUpdateServices;  /* set TRUE to display remotes */

/* These routines are called at interrupt time, so no stack checking! */
#pragma check_stack(off)

/* Called whenever remote service list changes */
void cbRemService (
   unsigned  usNumSvcs   /* Number of remote services  */
)
{
   fUpdateServices = TRUE;
}
/* Called when remote machine connects to us as a client */
SHORT cbListen (
   HANDLE   hVC,     /* Virtual connection handle     */
   ULONG    ul1,     /* Nothing at this time          */
   ULONG    ul2      /* Status = SHORT1FROMUL( ul2 )  */
)
{
   svc[hVC-HVC_FIRST].fEvent.flags.ListenCallback = TRUE;
   svc[hVC-HVC_FIRST].sStatus = SHORT1FROMULONG(ul2);
   svc[hVC-HVC_FIRST].fActive = TRUE;
   return (FALSE);
}
/* Called when remote server acknowledges our connection request */
SHORT cbConnect (
   HANDLE   hVC,     /* Virtual connection handle     */
   ULONG    ul1,     /* Nothing at this time          */
   ULONG    ul2      /* Status = SHORT1FROMUL( ul2 )  */
)
{
   svc[hVC-HVC_FIRST].fEvent.flags.ConnectCallback = TRUE;
   svc[hVC-HVC_FIRST].sStatus = SHORT1FROMULONG(ul2);
   svc[hVC-HVC_FIRST].fActive = TRUE;
   return (FALSE);
}
/* Called anytime a VC is broken */
SHORT cbDisConnect (
   HANDLE   hVC,     /* Virtual connection handle     */
   ULONG    ul1,     /* Nothing at this time          */
   ULONG    ul2      /* Status = SHORT1FROMUL( ul2 )  */
)
{
   svc[hVC-HVC_FIRST].fEvent.flags.DisconnectCallback = TRUE;
   svc[hVC-HVC_FIRST].fActive = FALSE;
   return (FALSE);
}
/* Called when we've receive a data buffer from remote */
SHORT cbRead (
   HANDLE   hVC,     /* Virtual connection handle     */
   ULONG    ul1,     /* Number of bytes received      */
   ULONG    ul2      /* Status = SHORT1FROMUL( ul2 )  */
)
{
   svc[hVC-HVC_FIRST].fEvent.flags.ReadCallback = TRUE;
   svc[hVC-HVC_FIRST].lBytesSoFar = ul1;
   svc[hVC-HVC_FIRST].sStatus = SHORT1FROMULONG(ul2);

   return (FALSE);
}
/* Called when data has been successfully sent to remote */
SHORT cbWrite (
   HANDLE   hVC,     /* Virtual connection handle     */
   ULONG    ul1,     /* Nothing at this time          */
   ULONG    ul2      /* Status = SHORT1FROMUL( ul2 )  */
)
{
   svc[hVC-HVC_FIRST].fEvent.flags.WriteCallback = TRUE;
   svc[hVC-HVC_FIRST].sStatus = SHORT1FROMULONG(ul2);

   return (FALSE);
}
#pragma check_stack()


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.