7.3   Zbuf Sockets

VxWorks includes an alternative set of socket calls based on a data abstraction called a zbuf, which permits you to share data buffers (or portions of data buffers) between separate software modules. The zbuf socket interface allows applications to read and write UNIX BSD sockets without copying data between application buffers and network buffers. You can use zbufs with either UDP or TCP applications. The TCP subset of this new interface is sometimes called zero-copy TCP.

Zbuf-based socket calls are interoperable with the standard BSD socket interface: the other end of a socket has no way of telling whether your end is using zbuf-based calls or traditional calls.

However, zbuf-based socket calls are not source-compatible with the standard BSD socket interface: you must call different socket functions to use the zbuf interface. Applications that use the zbuf interface are thus less portable.


*

WARNING: The send socket buffer size (set during configuration, using the macros TCP_SND_SIZE_DFLT or UDP_SND_SIZE_DFLT) must exceed that of any zbufs sent over the socket

To link (and initialize) the zbuf socket interface, reconfigure VxWorks. The relevant configuration macro is INCLUDE_ZBUF_SOCK.

7.3.1   Zbuf Calls to Send Existing Data Buffers

The simplest way to use zbuf sockets is to call either zbufSockBufSend( ) (in place of send( ) for a TCP connection) or zbufSockBufSendto( ) (in place of sendto( ) for a UDP datagram). In either case, you supply a pointer to your application's data buffer containing the data or message to send, and the network protocol uses that same buffer rather than copying the data out of it.


*

WARNING: Using zbufs allows different modules to share the same buffers. This lets your application avoid the performance hit associated with copying the buffer. To make this work, your application must not modify (let alone free!) the data buffer while network software is still using it. Instead of freeing your buffer explicitly, you can supply a free-routine callback: a pointer to a routine that knows how to free the buffer. The zbuf library keeps track of how many zbufs point to a data buffer, and calls the free routine when the data buffer is no longer in use.

To receive socket data using zbufs, see the following sections. 7.3.2 Manipulating the Zbuf Data Structure describes the routines to create and manage zbufs, and 7.3.3 Zbuf Socket Calls introduces the remaining zbuf-specific socket routines. See also the reference entries for zbufLib and zbufSockLib.

7.3.2   Manipulating the Zbuf Data Structure

A zbuf has three essential properties:

Zbuf segments are at the heart of how zbufs minimize data copying: if you have a data buffer, you can incorporate it (by reference, so that only pointers and lengths move around) into a new zbuf segment. Conversely, you can get pointers to the data in zbuf segments, and examine the data there directly.

Zbuf Byte Locations

You can address the contents of a zbuf by byte locations. A zbuf byte location has two parts, an offset and a segment ID.

An offset is a signed integer (type int): the distance in bytes to a portion of data in the zbuf, relative to the beginning of a particular segment. Zero refers to the first byte in a segment; negative integers refer to bytes in previous segments; and positive integers refer to bytes after the start of the current segment.

A segment ID is an arbitrary integer (type ZBUF_SEG) that identifies a particular segment of a zbuf. You can always use NULL to refer to the first segment of a zbuf.

Figure 7-1 shows a simple zbuf with data organized into two segments, with offsets relative to the first segment. This is the most efficient addressing scheme to refer to bytes a, b, or c in the figure.

Figure 7-2 shows the same zbuf, but labelled with offsets relative to the second segment. This is the most efficient addressing scheme to refer to bytes d, e, f, or g in the figure.

Two special shortcuts give the fastest access to either the beginning or the end of a zbuf. The constant ZBUF_END refers to the position after all existing bytes in the zbuf. Similarly, ZBUF_BEGIN refers to the position before all existing bytes. These constants are the only offsets with meanings not relative to a particular segment.

When you insert data in a zbuf, the new data is always inserted before the byte location you specify in the call to an insertion routine. That is, the byte location you specify becomes the address of the newly inserted data.

Creating and Destroying Zbufs

To create a new zbuf, call zbufCreate( ). The routine takes no arguments, and returns a zbuf identifier (type ZBUF_ID) for a zbuf containing no segments. After you have the zbuf ID, you can attach segments or otherwise insert data. While the zbuf is empty, NULL is the only valid segment ID, and 0 the only valid offset.

When you no longer need a particular zbuf, call zbufDelete( ). Its single argument is the ID for the zbuf to delete. The zbufDelete( ) routine calls the free routine associated with each segment in the zbuf, for segments that are not shared by other zbufs. After you delete a zbuf, its zbuf ID is meaningless; any reference to a deleted zbuf ID is an error.

Table 7-4:  Zbuf Creation and Deletion Routines


Call
 
Description
 

zbufCreate( )
 
Create an empty zbuf.
 
zbufDelete( )
 
Delete a zbuf and free any associated segments.
 

Getting Data In and Out of Zbufs

The usual way to place data in a zbuf is to call zbufInsertBuf( ). This routine builds a zbuf segment pointing to an existing data buffer, and inserts the new segment at whatever byte location you specify in a zbuf. You can also supply a callback pointer to a free routine, which the zbuf library calls when no zbuf segments point to that data buffer.

Because the purpose of the zbuf socket interface is to avoid data copying, the need to actually copy data into a zbuf (rather than designating its location as a shareable buffer) occurs much less frequently. When that need does arise, however, the routine zbufInsertCopy( ) is available. This routine does not require a callback pointer to a free routine, because the original source of the data is not shared.

Similarly, the most efficient way to examine data in zbufs is to read it in place, rather than to copy it to another location. However, if you must copy a portion of data out of a zbuf (for example, to guarantee the data is contiguous, or to place it in a data structure required by another interface), call zbufExtractCopy( ) specifying what to copy (zbuf ID, byte location, and the number of bytes) and where to put it (an application buffer).

Table 7-5:  Zbuf Data Copying Routines


Call
 
Description
 

zbufInsertBuf( )
 
Create a zbuf segment from a buffer and insert into a zbuf.
 
zbufInsertCopy( )
 
Copy buffer data into a zbuf.
 
zbufExtractCopy( )
 
Copy data from a zbuf to a buffer.
 

Operations on Zbufs

The routines listed in Table 7-6 perform several fundamental operations on zbufs.

Table 7-6:  Zbuf Operations


Call
 
Description
 

zbufLength( )
 
Determine the length of a zbuf, in bytes.
 
zbufDup( )
 
Duplicate a zbuf.
 
zbufInsert( )
 
Insert a zbuf into another zbuf.
 
zbufSplit( )
 
Split a zbuf into two separate zbufs.
 
zbufCut( )
 
Delete bytes from a zbuf.
 

The routine zbufLength( ) reports how many bytes are in a zbuf.

The routine zbufDup( ) provides the simplest mechanism for sharing segments between zbufs: it produces a new zbuf ID that refers to some or all of the data in the original zbuf. You can exploit this sort of sharing to get two different views of the same data. For example, after duplicating a zbuf, you can insert another zbuf into one of the two duplicates, with zbufInsert( ). None of the data in the original zbuf segments moves, yet after some byte location (the byte location where you inserted data) addressing the two zbufs gives completely different data.

The zbufSplit( ) routine divides one zbuf into two; you specify the byte location for the split, and the result of the routine is a new zbuf ID. The new zbuf's data begins after the specified byte location. The original zbuf ID also has a modified view of the data: it is truncated to the byte location of the split. However, none of the data in the underlying segments moves through all this: if you duplicate the original zbuf before splitting it, three zbuf IDs share segments--the duplicate permits you to view the entire original range of data, another zbuf contains a leading fragment, and the third zbuf holds the trailing fragment.

Similarly, if you call zbufCut( ) to remove some range of bytes from within a zbuf, the effects are visible only to callers who view the data through the same zbuf ID you used for the deletion; other zbuf segments can still address the original data through a shared buffer.

For the most part, these routines do not free data buffers or delete zbufs, but there are two exceptions:

The data-buffer free routine runs only when none of the data in a segment is part of any zbuf; to avoid data copying, zbuf manipulation routines such as zbufCut( ) record which parts of a segment are currently in a zbuf, postponing the deletion of a segment until no part of its data is in use.

Segments of Zbufs

The routines in Table 7-7 give your applications access to the underlying segments in a zbuf.

Table 7-7:  Zbuf Segment Routines


Call
 
Description
 

zbufSegFind( )
 
Find the zbuf segment containing a specified byte location.
 
zbufSegNext( )
 
Get the next segment in a zbuf.
 
zbufSegPrev( )
 
Get the previous segment in a zbuf.
 
zbufSegData( )
 
Determine the location of data in a zbuf segment.
 
zbufSegLength( )
 
Determine the length of a zbuf segment.
 

By specifying a NULL segment ID, you can address the entire contents of a zbuf as offsets from its very first data byte. However, it is always more efficient to address data in a zbuf relative to the closest segment. Use zbufSegFind( ) to translate any zbuf byte location into the most local form.

The pair zbufSegNext( ) and zbufSegPrev( ) are useful for going through the segments of a zbuf in order, perhaps in conjunction with zbufSegLength( ).

Finally, zbufSegData( ) allows the most direct access to the data in zbufs: it gives your application the address where a segment's data begins. If you manage segment data directly using this pointer, bear the following restrictions in mind:

Example: Manipulating Zbuf Structure

The following interaction illustrates the use of some of the previously described zbufLib routines, and their effect on zbuf segments and data sharing. To keep the example manageable, the zbuf data used is artificially small, and the execution environment is the Tornado shell (for details on this shell, see the Tornado User's Guide: Shell).

To begin with, we create a zbuf, and use its ID zId to verify that a newly created zbuf contains no data; zbufLength( ) returns a result of 0.

-> zId = zbufCreate() 
new symbol "zId" added to symbol table. 
zId = 0x3b58e8: value = 3886816 = 0x3b4ee0 
-> zbufLength (zId) 
value = 0 = 0x0

Next, we create a data buffer buf1, insert it into zbuf zId, and verify that zbufLength( ) now reports a positive length. To keep the example simple, buf1 is a literal string, and therefore we do not supply a free-routine callback argument to zbufInsertBuf( ).

-> buf1 = "I cannot repeat enough!" 
new symbol "buf1" added to symbol table. 
buf1 = 0x3b5898: value = 3889320 = 0x3b58a8 = buf1 + 0x10 
-> zbufInsertBuf (zId, 0, 0, buf1, strlen(buf1), 0, 0) 
value = 3850240 = 0x3ac000 
-> zbufLength (zId) 
value = 23 = 0x17

To examine the effect of other zbuf operations, it is useful to have a zbuf-display routine. The remainder of this example uses a routine called zbufDisplay( ) for that purpose; for the complete source code, see Example 7-4.

For each zbuf segment, zbufDisplay( ) shows the segment ID, the start-of-data address, the offset from that address, the length of the segment, and the data in the segment as a character string. The following display of zId illustrates that the underlying data in its only segment is still at the buf1 address (0x3b58a8), because zbufInsertBuf( ) incorporates its buffer argument into the zbuf without copying data.

-> ld </usr/jane/zbuf-examples/zbufDisplay.o 
value = 3890416 = 0x3b5cf0 = zbufDisplay.o_bss + 0x8 
-> zbufDisplay zId 
segID 0x3ac000 at 0x3b58a8 + 0x0 (23 bytes): I cannot repeat enough! 
value = 0 = 0x0

When we copy the zbuf, the copy has its own IDs, but still uses the same data address:

-> zId2 = zbufDup (zId,0,0,23) 
new symbol "zId2" added to symbol table. 
zId2 = 0x3b5ff0: value = 3886824 = 0x3b4ee8 
-> zbufDisplay zId2 
segID 0x3abf80 at 0x3b58a8 + 0x0 (23 bytes): I cannot repeat enough! 
value = 0 = 0x0

If we insert a second buffer into the middle of the existing data in zId, there is still no data copying. Inserting the new buffer gives us a zbuf made up of three segments--but notice that the address of the first segment is still the start of buf1, and the third segment points into the middle of buf1:

-> buf2 = " this" 
new symbol "buf2" added to symbol table. 
buf2 = 0x3b5fb0: value = 3891136 = 0x3b5fc0 = buf2 + 0x10 
-> zbufInsertBuf (zId, 0, 15, buf2, strlen(buf2), 0, 0) 
value = 3849984 = 0x3abf00 
-> zbufDisplay zId 
segID 0x3ac000 at 0x3b58a8 + 0x0 (15 bytes): I cannot repeat 
segID 0x3abf00 at 0x3b5fc0 + 0x0 ( 5 bytes):  this 
segID 0x3abe80 at 0x3b58b7 + 0x0 ( 8 bytes):  enough! 
value = 0 = 0x0

Because the underlying buffer is not modified, both buf1 and the duplicate zbuf zId2 still contain the original string, rather than the modified one now in zId:

-> printf ("%s\n", buf1) 
I cannot repeat enough! 
value = 24 = 0x18 
-> zbufDisplay zId2 
segID 0x3abf80 at 0x3b58a8 + 0x0 (23 bytes): I cannot repeat enough! 
value = 0 = 0x0

The zbufDup( ) routine can also select part of a zbuf without copying, for instance to incorporate some of the same data into another zbuf--or even into the same zbuf, as in the following example:

-> zTmp = zbufDup (zId, 0, 15, 5) 
new symbol "zTmp" added to symbol table. 
zTmp = 0x3b5f70: value = 3886832 = 0x3b4ef0
-> zbufInsert (zId, 0, 15, zTmp) value = 3849728 = 0x3abe00 -> zbufDisplay zId segID 0x3ac000 at 0x3b58a8 + 0x0 (15 bytes): I cannot repeat segID 0x3abe00 at 0x3b5fc0 + 0x0 ( 5 bytes): this segID 0x3abf00 at 0x3b5fc0 + 0x0 ( 5 bytes): this segID 0x3abe80 at 0x3b58b7 + 0x0 ( 8 bytes): enough! value = 0 = 0x0

After zbufInsert( ) combines two zbufs, the second zbuf ID (zTmp in this example) is automatically deleted. Thus, zTmp is no longer a valid zbuf ID--for example, zbufLength( ) returns ERROR:

-> zbufLength (zTmp) 
value = -1 = 0xffffffff = zId2 + 0xffc4a00f

However, you must still delete the remaining two zbuf IDs explicitly when they are no longer needed. This releases all associated zbuf-structure storage. In a real application, with free-routine callbacks filled in, it also calls the specified free routine on the data buffers, as follows:

-> zbufDelete (zId) 
value = 0 = 0x0 
-> zbufDelete (zId2) 
value = 0 = 0x0

Example 7-4:  Zbuf Display Routine

The following is the complete source code for the zbufDisplay( ) utility used in the preceding example:

/* zbufDisplay.c - zbuf example display routine */ 
 
/* includes */ 
 
#include "vxWorks.h" 
#include "zbufLib.h" 
#include "ioLib.h" 
#include "stdio.h"
/******************************************************************** * * zbufDisplay - display contents of a zbuf * * RETURNS: OK, or ERROR if the specified data could not be displayed. */
STATUS zbufDisplay ( ZBUF_ID zbufId, /* zbuf to display */ ZBUF_SEG zbufSeg, /* zbuf segment base for <offset> */ int offset, /* relative byte offset */ int len, /* number of bytes to display */ BOOL silent /* do not print out debug info */ ) { int lenData; char * pData;
/* find the most-local byte location */
if ((zbufSeg = zbufSegFind (zbufId, zbufSeg, &offset)) == NULL) return (ERROR);
if (len <= 0) len = ZBUF_END;
while ((len != 0) && (zbufSeg != NULL)) { /* find location and data length of zbuf segment */
pData = zbufSegData (zbufId, zbufSeg) + offset; lenData = zbufSegLength (zbufId, zbufSeg) - offset; lenData = min (len, lenData); /* print all of seg ? */
if (!silent) printf ("segID 0x%x at 0x%x + 0x%x (%2d bytes): ", (int) zbufSeg, (int) pData, offset, lenData); write (STD_OUT, pData, lenData); /* display data */ if (!silent) printf ("\n");
zbufSeg = zbufSegNext (zbufId, zbufSeg); /* update segment */ len -= lenData; /* update length */ offset = 0; /* no more offset */ }
return (OK); }

Limitations of the Zbuf Implementation

The following zbuf limitations are due to the current implementation; they are not inherent to the data abstraction. They are described because they can have an impact on application performance.

7.3.3   Zbuf Socket Calls

The zbuf socket calls listed in Table 7-8 are named to emphasize parallels with the standard BSD socket calls: thus, zbufSockSend( ) is the zbuf version of send( ), and zbufSockRecvfrom( ) is the zbuf version of recvfrom( ). The arguments also correspond directly to those of the standard socket calls.

Table 7-8:  Zbuf Socket Library Routines


Call
 
Description
 

zbufSockLibInit( )
 
Initialize socket libraries (called automatically if the configuration has zbuf sockets enabled. The relevant configuration macro is INCLUDE_SOCK_ZBUF).
 
zbufSockSend( )
 
Send zbuf data to a TCP socket.
 
zbufSockSendto( )
 
Send a zbuf message to a UDP socket.
 
zbufSockBufSend( )
 
Create a zbuf and send it as TCP socket data.
 
zbufSockBufSendto( )
 
Create a zbuf and send it as a UDP socket message.
 
zbufSockRecv( )
 
Receive data in a zbuf from a TCP socket.
 
zbufSockRecvfrom( )
 
Receive a message in a zbuf from a UDP socket.
 

For a detailed description of each routine, see the corresponding reference entry.

Standard Socket Calls and Zbuf Socket Calls

The zbuf socket calls are particularly useful when large data transfer is a significant part of your socket application. For example, many socket applications contain sections of code like the following fragment:

pBuffer = malloc (BUFLEN); 
while ((readLen = read (fdDevice, pBuffer, BUFLEN))  > 0) 
    write (fdSock, pBuffer, readLen);

You can eliminate the overhead of copying from the application buffer pBuffer into the internal socket buffers by changing the code to use zbuf socket calls. For example, the following fragment is a zbuf version of the preceding loop:

pBuffer = malloc (BUFLEN * BUFNUM);           /* allocate memory */ 
for (ix = 0; ix < (BUFNUM - 1); ix++, pBuffer += BUFLEN) 
    appBufRetn (pBuffer);                    /* fill list of free bufs */ 
 
while ((readLen = read (fdDevice, pBuffer, BUFLEN)) > 0) 
    { 
    zId = zbufCreate ();                     /* insert into new zbuf */ 
    zbufInsertBuf (zId, NULL, 0, pBuffer, readLen, appBufRetn, 0); 
    zbufSockSend (fdSock, zId, readLen, 0);   /* send zbuf */ 
    pBuffer = appBufGet (WAIT_FOREVER);       /* get a fresh buffer */ 
    }

The appBufGet( ) and appBufRetn( ) references in the preceding code fragment stand for application-specific buffer management routines, analogous to malloc( ) and free( ). In many applications, these routines do nothing more than manipulate a linked list of free fixed-length buffers.

Example 7-5:  The TCP Example Server Using Zbufs

For a small but complete example that illustrates the mechanics of using the zbuf socket library, consider the conversion of the client-server example in Example 7-1 to use zbuf socket calls.

No conversion is needed for the client side of the example; the client operates the same regardless of whether or not the server uses zbufs. The next example illustrates the following changes to convert the server side to use zbufs:

The following example shows the auxiliary zbufFioSockRecv( ) routine and the zbuf version of tcpServerWorkTask( ). To run this code:

  1. Start with tcpServer.c as defined in Example 7-1.

  1. Include the header file zbufSockLib.h.

  1. Insert the zbufDisplay( ) routine from Example 7-4.

  1. Replace the tcpServerWorkTask( ) definition with the following two routines:

/************************************************************************ 
* 
* zbufFioSockRecv - receive <len> bytes from a socket into a zbuf 
* 
* This routine receives a specified amount of data from a socket into a 
* zbuf, by repeatedly calling zbufSockRecv() until <len> bytes 
* are read. 
* 
* RETURNS: 
* The ID of the zbuf containing <len> bytes of data, 
* or NULL if there is an error during the zbufSockRecv() operation. 
* 
* SEE ALSO: zbufSockRecv() 
*/
ZBUF_ID zbufFioSockRecv ( int fd, /* file descriptor of file to read */ int len /* maximum number of bytes to read */ ) { BOOL first = TRUE; /* first time thru ? */ ZBUF_ID zRecvTotal = NULL; /* zbuf to return */ ZBUF_ID zRecv; /* zbuf read from sock */ int nbytes; /* number of recv bytes */
for (; len > 0; len -= nbytes) { nbytes = len; /* set number of bytes wanted */
/* read a zbuf from the socket */
if (((zRecv = zbufSockRecv (fd, 0, &nbytes)) == NULL) || (nbytes <= 0)) { if (zRecvTotal != NULL) zbufDelete (zRecvTotal); return (NULL); }
/* append recv'ed zbuf onto end of zRecvTotal */
if (first) zRecvTotal = zRecv; /* cannot append to empty zbuf */ else if (zbufInsert (zRecvTotal, NULL, ZBUF_END, zRecv) == NULL) { zbufDelete (zRecv); zbufDelete (zRecvTotal); return (NULL); }
first = FALSE; /* can append now... */ }
return (zRecvTotal); }
/************************************************************************ * * tcpServerWorkTask - process client requests * * This routine reads from the server's socket, and processes client * requests. If the client requests a reply message, this routine * sends a reply to the client. * * RETURNS: N/A. */
VOID tcpServerWorkTask ( int sFd, /* server's socket fd */ char * address, /* client's socket address */ u_short port /* client's socket port */ ) { static char replyMsg[] = "Server received your message"; ZBUF_ID zReplyOrig; /* original reply msg */ ZBUF_ID zReplyDup; /* duplicate reply msg */ ZBUF_ID zRequest; /* request msg from client */ int msgLen; /* request msg length */ int reply; /* reply requested ? */
/* create original reply message zbuf */
if ((zReplyOrig = zbufCreate ()) == NULL) { perror ("zbuf create"); free (address); /* free malloc from inet_ntoa() */ return; }
/* insert reply message into zbuf */
if (zbufInsertBuf (zReplyOrig, NULL, 0, replyMsg, sizeof (replyMsg), NULL, 0) == NULL) { perror ("zbuf insert"); zbufDelete (zReplyOrig); free (address); /* free malloc from inet_ntoa() */ return; }
/* read client request, display message */
while ((zRequest = zbufFioSockRecv (sFd, sizeof(struct request))) != NULL) { /* extract reply field into <reply> */
(void) zbufExtractCopy (zRequest, NULL, 0, (char *) &reply, sizeof (reply)); (void) zbufCut (zRequest, NULL, 0, sizeof (reply));
/* extract msgLen field into <msgLen> */
(void) zbufExtractCopy (zRequest, NULL, 0, (char *) &msgLen, sizeof (msgLen)); (void) zbufCut (zRequest, NULL, 0, sizeof (msgLen));
/* duplicate reply message zbuf, preserving original */
if ((zReplyDup = zbufDup (zReplyOrig, NULL, 0, ZBUF_END)) == NULL) { perror ("zbuf duplicate"); zbufDelete (zRequest); break; }
printf ("MESSAGE FROM CLIENT (Internet Address %s, port %d):\n", address, port); /* display request message zbuf */
(void) zbufDisplay (zRequest, NULL, 0, msgLen, TRUE); printf ("\n"); if (reply) { if (zbufSockSend (sFd, zReplyDup, sizeof (replyMsg), 0) < 0) perror ("zbufSockSend"); }
/* finished with request message zbuf */
zbufDelete (zRequest); }
free (address); /* free malloc from inet_ntoa() */ zbufDelete (zReplyOrig); close (sFd); }


*

CAUTION: In the interests of brevity, the STATUS return values for several zbuf socket calls are discarded with casts to void. In a real application, check these return values for possible errors.