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