Guidance
指路人
g.yi.org
Software / Reginald / Examples / mailslot / mailslots.htm

Register 
注册
Search 搜索
首页 
Home Home
Software
Upload

  
Mailslots

Using Mailslots with the Reginald REXX Interpreter

A Mailslot name
Creating a Mailslot
Reading a record from a Mailslot
Closing a Mailslot
Opening a Mailslot on the client
Writing a record to a Mailslot
Setting a timeout for a Mailslot
Caveats

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 (ie, domain).

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

Note: Only the server script can read the records in the mailslot it creates. The clients are restricted to only writing records.

Many client scripts 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 script reads them. But, the server script can also simultaneously be reading records from the mailslot while the clients are writing to it. (The server script calls an operating system function to read one record at a time. The records are retrieved the same order that they are queued. So, a mailslot is an awful lot like a REXX DATA QUEUE, except it transparently works over a network and is RAM-based).

As soon all of these scripts end (ie, the server script, as well as all client scripts 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 (which would be typical for a REXX script since data in REXX is inherently all text-based). Or it could be binary data. The author of the server script will ultimately decide what sort of data format he desires for a record (and of course, the client scripts 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). A server script 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

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 script creates a mailslot, that mailslot must be given a unique name. The server script 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 script must know, and use, this same name in order to write records to that particular mailslot.

When a server script 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 script expects records on the subject of taxes from Bob, Pete, and Sue, then the script 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 script will create a mailslot named "bob" (in the "taxes" folder, although remember that this folder exists only in memory -- not on disk), a mailslot named "pete" (in the taxes folder), and a mailslot named "sue" (in the taxes folder).

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

\\ComputerName\mailslot\[path]name
This is similiar 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 script 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 scripts 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.


Creating a Mailslot

The server script creates a mailslot by FUNCDEF'ing, and then calling, the operating system function CreateMailslot. An example of a server script (called serverslot.rex) is included with this tutorial. You should persue this to see how to create and read records from a mailslot.

Note: To make error-checking very simple, serverslot.rex FUNCDEFs the MailSlot functions so that they raise a USER condition upon returning an error value. Then it traps the USER condition instead of checking for error returns. In the examples below, we FUNCDEF the MailSlot functions so that they do not raise any condition, so we must check for error returns manually. This is just to show you how to do both styles of error checking so you can see which you prefer. In general, using raised conditions means you'll have to write a lot less error checking and your script will be easier to read.

The first argument to CreateMailslot is the 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 omit this arg (or pass 0).

The third arg is the number of milliseconds you wish to wait for a record to arrive before your script 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 script (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 omit this arg or pass 0. If you're willing to wait forever (ie, the operating system function never returns until some record arrives), then you pass a -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 the name of a REXX variable that is a SECURITY_ATTRIBUTES struct you fill in. This arg needs to be passed only if your server script plans to pass off the mailslot handle to another script to allow it to read messages from the mailslot. If you don't need this ability, then omit this arg.

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, we'll FUNCDEF it to return an empty string, and also tell Reginald to save the error number from the operating system so that we can fetch it with RXFUNCERRMSG(). We can also get an error message by passing that number to UNIXERROR().

For example, the following FUNCDEFs a few Mailslot functions we need to call, and then calls CreateMailslot to create a mailslot named "blort". Note that error-checking on the FUNCDEF statements is omitted for brevity. See serverslot.rex for real error-checking.

/* Register some Mailslot functions */
err = FUNCDEF("CreateMailslot", "void, str, 32u, 32, void", "kernel32",,"O -1")
err = FUNCDEF("GetMailslotInfo", "32, void, 32u *, 32 * stor, 32u * stor, 32u * dual", "kernel32",,"O 0")
err = FUNCDEF("ReadFile", "32, void, void, 32u, 32u * stor, void", "kernel32",,"O 0")
err = FUNCDEF("CloseHandle", "32, void", "kernel32", "O 0")

/* 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", , -1)
IF handle == "" THEN DO
   SAY "ERROR:" UNIXERROR(RXFUNCERRMSG())
   RETURN
END

Reading records from a Mailslot

To read the next (ie, one) record from the mailslot, the server script 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 the name of a variable 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 omit this arg (and do not need to set any variable).

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

The fourth arg is the name of the variable 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 omit the arg.

The fifth arg is the name of a variable you have set to the number of milliseconds that you're prepared to wait for ReadFile to return the contents of the next record (which may not yet have arrived). You can omit this arg if you don't wish to set a limit.

We have FUNCDEF'ed GetMailslotInfo so that, if it fails to return information about the next record, then it will return an empty string and save the operating system error number so that we can fetch it with RXFUNCERRMSG().

Even if GetMailslotInfo does not return an error, 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 -1.

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

/* Check for an error */
IF err == 0 THEN DO
   SAY "ERROR:" UNIXERROR(RXFUNCERRMSG())
END

/* Check if there was a record. If so, the size is not -1 */
ELSE IF msgSize \= -1 THEN DO
   SAY "Next record is" msgSize "bytes."
END
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 Reginald's CONVERTDATA with the "A" option 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. Finally, we'll use CONVERTDATA once more to convert the record from any binary datatype, to REXX variables. If your record is already text characters (such as what REXX clients scripts typically would write), then it doesn't need any conversion. It merely needs to be assigned to a variable, which CONVERTDATA's "=" option can do quickly and easily. (Otherwise, if the record contains binary, you'd want to FUNCDEF some datatype, such as a struct that describes the datatype, and then use CONVERTDATA's "T" option to stuff it into a REXX variable).

To allocate a simple memory buffer with CONVERTDATA, we pass a 0 for the first arg, the desired size for the second arg, omit the third arg, and pass an "A" for the last arg. If all goes well, CONVERTDATA will return a handle to the memory, which we will pass to ReadFile. If an error, CONVERTDATA will raise SYNTAX condition.

/* Allocate memory to read in the record */
buffer = CONVERTDATA(0, msgSize, , "A")

Now that we have a buffer to read in the record, we call ReadFile to do that.

The first arg is the handle to our mailslot.

The second arg is the handle to our memory buffer.

The third arg is the number of bytes we want to read.

The fourth arg is the name of a REXX variable where ReadFile will return how many bytes it actually read. We need to check this on return to make sure we read as many bytes as we expected.

The last arg you pass is the name of a REXX variable that is a SECURITY_ATTRIBUTES struct you fill in. We don't need this arg, so we can omit it.

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

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

/* See if an error */
IF err == "" THEN SAY "ERROR:" UNIXERROR(RXFUNCERRMSG())

/* Make sure all the chars were read */
ELSE IF msgSize \= numRead THEN SAY "ERROR: The correct number of characters weren't received!"
Finally, the last step is to assign the contents of the record to a REXX variable so that we can directly manipulate it. We use CONVERTDATA to do so (and assign it to the same variable name, although you can use any name):
/* Assign the record chars to the REXX variable named "buffer" */
err = CONVERTDATA(buffer, "buffer", , "=")
IF err == "" THEN SAY "Error assigning the record to a variable"

/* Display the received record. Here you'd normally
 * do something useful with it
 */
SAY buffer

Closing a Mailslot

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

Note: If you don't do this, Windows will do it for you, but it's better to explicitly close the handle, especially if running from RexxEd (where you could otherwise get an error about another mailslot already having the same name if you run the script a second time).

/* Close the mailslot */
CALL CloseHandle handle

Opening a Mailslot on the client

Using a mailslot on the client is incredibly easy. You can use the STREAM built-in function to open it, just like a regular file. But you must use the 'OPEN WRITE SHARE' mode. Here then is how we would open the "blort" mailslot on a computer named "MyComputer":

/* Open the blort mailslot on MyComputer */
mailslotname = "\\MyComputer\mailslot\blort"
IF STREAM(mailslotname, 'C', 'OPEN WRITE SHARED') \== "READY:" THEN
   SAY "ERROR getting mailslot:" STREAM(mailslotname, 'D')

Writing a record to a Mailslot

To write one record, you can simply make a call to CHAROUT or LINEOUT. A call to either one writes exactly one record. You can make several calls to write several records. Typically, you'll use CHAROUT to write the record:

/* Write out some text to a record */
IF CHAROUT(mailslotname, "This is some text") \== 0 THEN
   SAY "ERROR writing mailslot:" STREAM(mailslotname, 'D')
Normally, you'll be limited to writing one line of text in a single record, unless you insert the sequence '0D0A'X between concatenated lines. For example, here we write out 2 lines of text to a record:
/* Write out some text to a record */
IF LINEOUT(mailslotname, "This is line 1." || '0D0A'X || "This is line 2") \== 0 THEN
   SAY "ERROR writing mailslot:" STREAM(mailslotname, 'D')
After you're finally done writing out records and do not wish to write out anything more, you can use STREAM's 'CLOSE' command to close the mailslot. (Do not use CloseHandle). But if you forget to do this, REXX will automatically close it for you when your script ends.

To handle errors, you can use REXX's NOTREADY condition just like you would with any other stream.

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


Setting a timeout for a Mailslot

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

The first arg is the handle to the mailslot.

The second arg is the desired timeout in milliseconds. Pass a -1 to set to wait forever for records.

FUNCDEF("SetMailslotInfo", "32, void, 32u", "kernel32",,"O 0")
IF SetMailslotInfo(handle, -1) == "" THEN
   SAY "ERROR:" UNIXERROR(RXFUNCERRMSG())

Caveats

One caveat is that sometimes your server script will receive two or more duplicate copies of a single 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 script embed within the record the CPU's current time since the script stated (which can be gotten with TIME('U')) and then the User's Name (gotten with USERID()).

Then when your server script receives a record, it can compare the USERID and time with previously received records, and discard the record if these match a previous record.

Here's an example of the client script prepending the time and username before the record data (and separating them with a comma):


IF CHAROUT(mailslotname, TIME('U') USERID() || "," || "This is some text") \== 0 THEN
   SAY "ERROR writing mailslot:" STREAM(mailslotname, 'D')
And here is how the server script can detect a duplicate message:
/* At the start of the script, these must be initialized to defaults */
lastTime = ""
lastUser = ""

/* Here the script would create a mailslot and read records
 * from it. Assume the next record has been read into
 * the variable "buffer".
 */

/* Chop apart the TIME, USERNAME, and record data */
PARSE VAR buffer thisTime thisUser ',' data

IF thisTime = lastTime & thisUser = lastUser THEN SAY "Duplicate message"
ELSE DO
   /* This is not a duplicate message, so save this info */
   lastTime = thisTime
   lastUser = thisUser

   /* Because of the PARSE VAR, we have an extra leading space
    * on our data. You can strip if off with STRIP()
    */

END
掌柜推荐
 
 
 
 
 
 
 
 
 
 
 
 
© Tue 2024-10-8  Guidance Laboratory Inc.
Email:webmaster1g.yi.org Hits:0 Last modified:2013-06-18 23:40:59