Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using a Mailslot to read/write data over a network

0.00/5 (No votes)
8 Mar 2004 4  
Shows how to use a Mailslot to easily read/write data between two or more networked Windows computers.

Introduction

A mailslot is a (temporary) RAM-based file to which numerous records of data can be written and read by several computers on the same network (i.e., domain). The chief advantage of using mailslots (to exchange data across a network) over other alternatives such as WinSock, WinINet, etc, is that a mailslot is incredibly easy to implement. It takes a few lines of code, and you don't need to know anything about network protocols.

A program running upon one particular computer creates a mailslot in its computer's memory (while the program is running). That computer and program then becomes the "server". The server program can read any record that any other program running upon any other networked computer writes (i.e., sends) to that particular mailslot. These other programs/computers are then "clients". The operating system takes care of sending the record's contents (i.e., data) over the network. The client program merely needs to call one operating system function to write a record to the mailslot. And the server program merely needs to call one operating system function to read a record.

Note: Only the server program can read the records in the mailslot it creates (unless the server program gives security permission, and a handle, to another program). The clients are restricted to only writing records.

Many client programs can write records to a particular mailslot simultaneously. The messages are "queued" in the order that they arrive at the mailslot, and stored there in the server's RAM until such time as the server program reads them. But, the server program can also simultaneously be reading records from the mailslot while the clients are writing to it. (The server program calls an operating system function to read one record at a time. The records are retrieved in the same order that they are queued. So, a mailslot is an awful lot like a message queue, except it transparently works over a network. But, note that you have no facility to "peek" ahead to other queued messages nor access the queued items in a mailslot except in a strict order based upon their arrival).

As soon all of these programs "close down" the mailslot they're using (i.e., the server program, as well as all client programs that write to that particular mailslot), then the mailslot file is automatically deleted from the memory of the server computer (and any unread data inside it is discarded).

The data inside a record can be in any format. It can be text. Or it could be binary data. The author of the server program will ultimately decide what sort of data format he desires for a record (and of course, the client programs must be aware of the format too in order to write correct records). Therefore a record can really be any amount or type of data. (But the size of a record should be less than 64000 bytes, or the operating system may choke upon it. Reports from programmers who have used mailslots suggest that the size of a record should actually be limited to much less than even that, or data loss may occur). A server program could even allow numerous types of records, and have the first piece of data in a record tell what the remaining data in a record is all about. It's really up to the creator of a mailslot to determine what he wants to do with it.

Boring technical note from Microsoft

Records smaller than 425 bytes are sent using datagrams. Records larger than 426 bytes are sent using a connection-oriented transfer over an SMB session. Connection-oriented transfers are limited to one-to-one communication from one client to one server. Note that Windows does not support records that are 425 or 426 bytes.

A mailslot name

When the server program creates a mailslot, that mailslot must be given a unique name. The server program can choose any name it wishes, although certain guidelines (to be discussed) must be followed. Due to the way that the operating system internally handles the name, it resolves issues with different computers using what appears to be the same name for a mailslot.

A client program must know, and use, this same name in order to write records to that particular mailslot.

When a server program creates a mailslot, the mailslot name must have the following form:

\\.\mailslot\[path]name

A mailslot name starts with the following, required parts: two backslashes to begin the name, a period, a backslash following the period, the word mailslot, and a trailing backslash. After that is an optional "path" to the mailslot (and note that the brackets simply indicate that it's optional -- they are not actually supposed to be there), and then the name of the mailslot. Names are not case-sensitive, so "BLORT" is the same as "blort".

A mailslot name can be preceded by a path consisting of the names of one or more directories, separated by backslashes. For example, if a server program expects records on the subject of taxes from Bob, Pete, and Sue, then the program may create 3 mailslots (ie, one for each person) with the following names:

\\.\mailslot\taxes\bob
\\.\mailslot\taxes\pete
\\.\mailslot\taxes\sue
Above the author of the server program will create a mailslot named "bob" (in the "taxes" directory, although remember that this directory exists only in memory -- not on disk), a mailslot named "pete" (in the taxes directory), and a mailslot named "sue" (in taxes).

To write a message to a mailslot, a client program "opens" that mailslot by its name. The client uses the following form for the mailslot name:

\\ComputerName\mailslot\[path]name

This is similar to how the server specifies the name, except now, instead of the dot, the client specifies the name of the computer upon which the mailslot resides.

So if the client wants to send a record to the "sue" mailslot on the computer named "JoesComputer", it would use the name:

\\JoesComputer\mailslot\taxes\sue
If there are several computers that happen to have a mailslot named "sue" in the taxes folder, then the client can simultaneously write the same record to all of those mailslots (with a single call to one operating system function) by substituting the domain name in lieu of a particular computer's name. For example, assume that both JoesComputer and JohnsComputer both have that sue mailslot in taxes, and they are on a domain named "WorkGroup". The client program can send the same record to both by using the mailslot name:
\\WorkGroup\mailslot\taxes\sue

If the client wants to write the same record to every sue mailslot in a network's primary domain, then it can simply use an asterisk for the domain name as so:

\\*\mailslot\taxes\sue

Note: If the client and server programs are both running on the same computer, then the client can use the same name that the server uses to create the mailslot. Above, that would be "\\.\mailslot\taxes\sue". This may be useful for testing purposes, when running the client and server programs on the same computer.

Creating a Mailslot

The server program creates a mailslot by calling the operating system function (in kernel32.dll) CreateMailslot. An example of a server program (called serverslot.c) is included with this tutorial. You should persue this to see how to create and read records from a mailslot.

The first argument to CreateMailslot is a pointer to the null-terminated name of the mailslot.

The second argument is the maximum size (in bytes) of a single record that you allow to be written to the mailslot. If you wish to allow any size record, then you can pass 0.

The third arg is the number of milliseconds you wish to wait for a record to arrive before your program can resume doing other things. As you'll see later, you call a particular operating system function to read the next record in your mailslot. But if there is no record already waiting there, then that operating system function will suspend your program (inside of that call), and wait for a record to arrive before it returns. So, this arg tells how long you're willing to wait inside of that call to the operating system function for a record to arrive before the operating system function gives up and returns without reading a record (if no record arrives during that time). If you're not willing to wait at all, you can pass a 0. If you're willing to wait forever (ie, the operating system function never returns until some record arrives), then you pass MAILSLOT_WAIT_FOREVER (-1). If you're willing to wait any other amount of time, pass the number of milliseconds. For example, 60000 will wait one minute.

The last arg you pass is a pointer to a SECURITY_ATTRIBUTES struct you fill in. This arg needs to be passed only if your server program plans to pass off the mailslot handle to another program to allow it to read messages from the mailslot. If you don't need this ability, then pass a 0.

If CreateMailslot is successful at creating the mailslot, it will return a handle that you'll need to pass to other mailslot functions. If CreateMailslot fails, it returns INVALID_HANDLE_VALUE (-1), and you'll have to call GetLastError() to get a real error number (and FormatMessage() to get an appropriate error message).

For example, the following calls CreateMailslot to create a mailslot named "blort". (Note that, because of the way that the C language treats a backslash character in a string, we have to double up the backslashes in the mailslot name).

HANDLE handle;

/* Create a mail slot named blort, allowing any size records
 * to be received (up to 64K). Specify that we'll wait forever
 * when reading a record.
 */
handle = CreateMailslot("\\\\.\\mailslot\\blort",
         0,
         MAILSLOT_WAIT_FOREVER,
         NULL);
if (handle == INVALID_HANDLE_VALUE) 
{
   printf("CreateMailslot failed: %d\n", GetLastError());
}
If you wish to create a mailslot that you want other programs you launch (via ShellExecute for example) to be able to open and read from, or you plan to somehow "pass" your mailslot handle to some other program running on the computer and allow it to read from that mailslot, then you should fill in and pass a SECURITY_ATTRIBUTES structure. Set the bInheritHandle of this structure to TRUE.

Reading records from a Mailslot

To read the next (i.e., one) record from the mailslot, the server program first calls GetMailslotInfo to retrieve the size (in bytes) of the next record (if any).

The first arg is the handle returned by CreateMailslot.

The second arg is a pointer to a DWORD that you have set to the maximum number of bytes you wish to allow for the next record. If you do not wish to set a specific limit, then you can pass a 0.

The third arg is a pointer to a DWORD where you want GetMailslotInfo to return the size (in bytes) of the next record.

The fourth arg is a pointer to a DWORD where you wish GetMailslotInfo to return the count of how many records are currently queued in the mailslot. If you do not need this count, then you can pass a 0.

The fifth arg is a pointer to a DWORD you have set to the number of milliseconds that you're prepared to wait for the information about the next record (which may not yet have arrived). Pass a 0 if you don't wish to set this.

If GetMailslotInfo fails to return information about the next record, then it returns 0. You'll have to get the real error number from GetLastError().

Even if GetMailslotInfo does not return 0, it may still be that there is no record to read. After all, if you have told GetMailslotInfo not to wait forever for a record to arrive, it may return without obtaining any information about the next record's size. In this case, GetMailslotInfo will not return an empty string, but rather, will indicate that the next record's size is MAILSLOT_NO_MESSAGE (-1).

DWORD   msgSize;
BOOL    err;

/* Get the size of the next record */
err = GetMailslotInfo(handle, 0, &msgSize, 0, 0);

/* Check for an error */
if (!err) printf("GetMailslotInfo failed: %d\n", GetLastError());

/* Check if there was a record. If so, the size is not -1 */
else if (msgSize != (DWORD)MAILSLOT_NO_MESSAGE) 
  printf("Next record is %u bytes.\n", msgSize);
After you get the size of the next record, you can then read it. You first need to obtain a memory buffer (of the required size) into which you can read the contents of the record. We'll use GlobalAlloc to do that. Then you need to call the function ReadFile to read the actual record. Once the record has been read, it is removed from the mailslot. ReadFile is a standard Windows file reading function and it works on a mailslot handle just like it works on any other file.

If all goes well, ReadFile will read the contents of the record into our buffer, return a 1, and it will set our variable to the proper number of bytes that it read. Here we read the record:

void *   buffer;

/* Allocate memory to read in the record */
buffer = GlobalAlloc(GMEM_FIXED, msgSize);
if (!buffer) printf("An error getting a memory block.\n");
else
{
   DWORD    numRead;

   /* Read the record */
   err = ReadFile(handle, buffer, msgSize, &numRead, 0);

   /* See if an error */
   if (!err) printf("ReadFile error: %d\n", GetLastError());

   /* Make sure all the bytes were read */
   else if (msgSize != numRead) printf(
     "ReadFile did not read the correct number of bytes!\n");

   else
   {
      /* "buffer" now contains the contents of the record. Don't
       * forget to GlobalFree() it at some point!
       */
   }
}

Closing a Mailslot

When your server program is finally done with its mailslot, you call CloseHandle, passing the mailslot handle.

CloseHandle(handle);

Opening a Mailslot on the client

Using a mailslot on the client is incredibly easy. You can use the standard CreateFile function to open it, just like a regular file. But you must use the FILE_SHARE_READ mode. Here then is how we would open the "blort" mailslot on a computer named "MyComputer":

HANDLE   handle;

/* Open the blort mailslot on MyComputer */
handle = CreateFile("\\\\MyComputer\\mailslot\\blort",
    GENERIC_WRITE, 
    FILE_SHARE_READ,
    0, 
    OPEN_EXISTING, 
    FILE_ATTRIBUTE_NORMAL, 
    0); 
if (handle == INVALID_HANDLE_VALUE) 
{
   printf("CreateFile failed: %d\n", GetLastError());
}

Writing a record to a Mailslot

To write one record, you can simply make a call to the standard WriteFile function. This writes exactly one record (so you'll need to initialize a buffer with the entire contents of the record). You can make several calls to write several records.

static LPTSTR MyMessage = "This is some text";
BOOL     err;
DWORD    numWritten;

/* Write out our nul-terminated string to a record */
err = WriteFile(handle, MyMessage, sizeof(MyMessage), &numWritten, 0);

/* See if an error */
if (!err) printf("WriteFile error: %d\n", GetLastError());

/* Make sure all the bytes were written */
else if (sizeof(MyMessage) != numWritten) printf(
  "WriteFile did not read the correct number of bytes!\n");
After you're finally done writing out records and do not wish to write out anything more, you can use CloseHandle to close the mailslot.

There is an example named clientslot.c with this tutorial that shows how to write a client program.

Setting a timeout for a Mailslot

If you need to change the time-out value for reading from a mailslot, you can call SetMailslotInfo.

The first arg is the handle to the mailslot.

The second arg is the desired timeout in milliseconds. Pass MAILSLOT_WAIT_FOREVER to wait forever for records.

if (!SetMailslotInfo(handle, MAILSLOT_WAIT_FOREVER))
   printf("SetMailslotInfo error: %d\n", GetLastError());

Caveats

One caveat is that sometimes your server program will receive two or more copies of a record written by the client. This is because Windows automatically ships out a copy of the record using every installed protocol on the client.

One way around this is to have the client embed (perhaps as the first DWORD of a record) the value of GetTickCount. Then when your server program reads a record, it could check the contents of a record against the contents of previously received records and if identical, discard what is very likely a duplicate record.

Another caveat with mailslots is that there is no way for a client to confirm whether the server program properly receives a record (unless you have your client program create its own mailslot in which it receives "acknowledgement" records back from the server. In other words, both programs become a server as well as a client, sending records to each others' mailslot).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here