(If you do not see the frame version
of
this page with a table of contents in the left frame, click here.)
The farVIEW Communications Library
Introduction
The multi-threaded Comm library included in the farVIEW engine provides
a flexible TCP/IP interface for a farSlang module. You can use the
library
to access web pages on the internet. You can also use it to implement a
web server, a Remote-Procedure-Call (RPC) server, or any other kind of
server you want. The Comm library supports messages using the HTTP
protocol.
I'll provide a short description of that later in this document, and
provide
links to the primary document site. The thread management aspects of
the
library are transparent to the farSlang module.
The Comm Library
The library contains four main classes:
- A user interface class, called CComm, which provides
communication
between the user code and the communication threads.
- A session handler class derived from the CThread class, called CSession,
which communicates with a remote correspondent, whether as a server or
as a client. The CSession class is designed to allow to be overridden
with
a derived class to provide specific server-side and client-side
behaviors
- A socket listener class derived from the CThread class, called CListen,
which listens for client messages and spawns server-side CSession
handlers.
- A socket handler class, providing a “low-level” interface to the
WinSock
software, called CSocket.
Of these classes, you use only four functions in the CComm interface
class
to control the whole library.
There are several support classes, mostly acting as wrappers to
simplify
the communication between the farSlang module thread and the session
threads.
One special support class is CCommResult. Objects of this class
are passed by the farSlang module with a communication request, and are
used by the CSession thread to synchronize and communicate results back
to the farSlang caller. I will discuss this further later in this
document.
The other support classes are not used by farSlang.
The Comm Interface
You do not interact directly with CSession, CListen, or CSocket.
Instead,
you use the CComm class. The CComm class provides six simple interface
functions to control the Comm library:
- OpenComm – engage the communication package. If a
listener
is specified,
create a CListen thread object and start the thread. (This function is
used only by farVIEW.)
- CloseComm – disengage the communication package.
Disconnect
all
sessions and the disconnect the listener from WinSock. (This function
is
used only by farVIEW.)
- OpenSession – create a CSession thread object
associating it
with
the specified identifier, URL, and port.
- CloseSession – disconnect the specified CSession thread
object and
destroy it.
- SendMessage – send the specified message via the
specified
CSession
thread object. If the message is to be encrypted (and authenticated),
that
is done automatically and transparently by the CSession thread before
the
message is sent.
- ReceiveMessage – receive a message, authenticate and
decrypt
it.
If there is no message available, wait for it. Return the plaintext
message
to the farSlang module.
To properly control the Comm package, you open a session, send and
receive
messages as needed, then you close the session. When you open a
session,
you associate a unique identifier with that session. (Use the uniqueID
library function to obtain a unique identifier.) Use the session
identifier
in all subsequent commands to that CSession object to identify it to
the
CComm interface.
OpenComm?(result: CCommResult)
Since this command should never be used by a farSlang module, I will
comment
no further.
CloseComm?(result: CCommResult)
As above.
OpenSession?(sessionID$, hostIP$, hostPort#, userName$, result:
CCommResult)
TBD
System.Comm.OpenSession("mySession",
"www.mozilla.org", 80,
"", result)
CloseSession?(sessionID$, result: CCommResult)
TBD
System.Comm.CloseSession("mySession", result)
SendMessage?(sessionID$, mssgType$, command$, argument$, bodyType$,
body:
CAtom, life: disposition, result: CCommResult)
TBD
System.Comm.SendMessage("mySession", "HTTP", "GET",
"", "",
nil, keepAlive, result)
ReceiveMessage?(sessionID$, result: CCommResult)
TBD
System.Comm.ReceiveMessage("mySession", result)
Basic Operation
The basic actions taken when you call a CComm function are as follows:
- Your code creates a CCommResult object, specifying synchronous or
asynchronous
behavior, and passes it, along with specific request parameters to the
appropriate CComm function.
- The CComm function wraps the request parameters within a
container
object
and enqueues that object into the specified CSession object’s activity
request port. The CComm function then waits on the CCommResult
object.
When it regains control, it will return to your code. If you secified
asynchronous
behavior, the wait step will not wait.
- The specified CSession object dequeues the request from its
activity
request
port, processes the request, then releases the CCommResult
object.
If you specify synchronous behavior, the CComm function will wait until
the requested action is completed before returning control to you. This
is recommended. If you specify asynchronous behavior, the CComm
function
will return control to you immediately, where it becomes your
responsibility
to determine when the action has completed. This is not recommended
because
you are tying up the machine while you are in the test loop. When
you
create a CCommResult object, the default is for synchronous behavior.
Let's examine a simple example. For this example, we will request a
web page from www.wdj.com (Windows Developers Journal). That is
about
the simplest thing to do. The result will be an HTML document that
includes
links to obtain the images. We won't resolve the links, since we would
have to discuss HTML parsing, which is well beyond the scope of this
article.
We will connect to www.wdj.com on port 80, which is the normal HTTP
server
port, send a GET request to the server, receive the server's response,
and disconnect from the server.
Connecting to a Host
Obtain a unique identifier, then open the session. Retain the sessionID
for subsequent calls.
-- The CComm object is in the System global
glb System: CSystem
-- Get a session identifier
var sessionID := UniqueID
-- Create a CCommResult object
var result := CCommResult -- defaults to wait
-- Open a session
System.Comm.OpenSession(sessionID,
"http://www.wdj.com", 80, "", result)
|
Note that error checking is omitted from the examples for clarity.
In
normal coding, you should include error checking, and we will discuss
it
later in this document.
Sending a Message to a Host
Use the sessionID created above along with the content of the message
to send in this call.
-- Send a request to an HTTP server
var body: CAtom -- no body for a "GET"
System.Comm.SendMessage(sessionID,
"HTTP",
"GET",
"",
"",
body,
keepAlive,
result) |
The SendMessage command has the most parameters.
Receiving a Message from a Host
Continue to use the same sessionID to request to receive a message
-- Receive the server's response
System.Comm.ReceiveMessage(sessionID, result)
-- The content of the server's response
-- is in the result object.
-- Obtain the header list and
-- write it to a file
result.GetHead.WriteFile("head.txt")
-- Obtain the body (content) and
-- write it to a file
result.GetBody:CStrList.WriteFile("body.txt")
|
Disconnecting from a Host
Disconnect the session from the host
-- Disconnect
from the server
System.Comm.CloseSession(sessionID, result) |
Acknowledging a Guest
Of course, there is more to acknowledging the guest's request than just
the ACK-message, but let's just send the text of the guest's message
back
to her for now.
-- Send the body-part of the guest's
-- message back to the guest.
glb System: CSystem
System.Comm.SendMessage(System.SessionID,
"ACK",
"",
"200 OK",
"",
System.CommBody,
keepAlive,
result); |
Notes
When implementing a connection between two farVIEW computers, you need
to know either the URL of the host machine or its IP address. You can
find its IP address by opening a Windows Command Window (MSDOS), and
typing
ipconfig
at the prompt. You will see something like the following.
C:\WINDOWS>ipconfig
Windows 98 IP Configuration
0 Ethernet adapter :
IP Address. . .
. . .
. . . : 192.168.0.1
Subnet Mask . . .
.
. . . . : 255.255.255.0
Default Gateway .
.
. . . . :
1 Ethernet adapter :
IP Address. . .
. . .
. . . : 204.255.212.233
Subnet Mask . . .
.
. . . . : 255.255.255.0
Default Gateway .
.
. . . . : 204.255.212.233
2 Ethernet adapter :
IP Address. . .
. . .
. . . : 0.0.0.0
Subnet Mask . . .
.
. . . . : 0.0.0.0
Default Gateway .
.
. . . . :
C:\WINDOWS>
|
192.x.x.x is a local IP address, which is what you should use if you
are working on a LAN. If you are setting up for Internet access,
however,
that one won't work. In my case, the Internet IP address is
204.255.212.233.
However, because, my ISP assigns a new IP address to my machine
everytime
I log on, that one won't work generally either. In fact, unless you
have
a DSL or Cable connection, you may not have a permanent IP address
assignment
for your machine. That pretty much eliminates that machine as a farVIEW
host, unfortunately, except for just the immediate session.
Building an HTTP Server
TBD
About the HTTP Protocol
TBD
Implementing Remote Procedure Calls
The farVIEW engine can be used as a portal for implementing remote
procedure
calls fairly easily. For this discussion, I will show how to write a
simple
farSlang server and a farSlang client to do something trivial.
Let's ask a server to double a value provided by a client. The
client,
for its part, will show a dialog to obtain the value, send the value to
the server, then display the result when it is returned.
The Server
-- Sample RPC server module in farSlang to double the
value in
the body
-- of the message and return it.
--
-- Note that the server code must reside within a proc, in
this
case,
-- proc double. Also note that the SendMessage and
CloseSession
-- commands used in this code are not immediately carried out
but
are
-- enqueued into the inPort of the same thread in which this
module
-- executes, and cannot execute until this module completes.
Therefore,
-- the module code must not attempt to wait until the
commands
are
-- completed, since if it does, they will never even be
started.
module RPCServer
glb System: CSystem
var result :=
CCommResult(false)
-- don't wait for completion
proc double
-- Get the value, double it,
and
replace the old value
System.CommBody:CStrList.GotoHead
var val# :=
integer(System.CommBody:CStrList.StrItem(true))
System.CommBody:CStrList.StrReplace(string(2
* val, 10))
-- Return the result to the
RPC client
and shut down
System.Comm.SendMessage(System.SessionID,
"ACK",
"200 OK",
"",
"text/plain",
System.CommBody,
closeConnect,
result)
System.Comm.CloseSession(System.SessionID, result)
endProc double
endModule RPCServer
|
The Client
For the purposes of this discussion, you can test on a single machine
by
using the standard 127.0.0.1 IP address. That way, your farVIEW will
act
as both the client and the server.
-- Sample RPC client module in farSlang to get a value
from the
user,
-- send it to the sample server to double the value then
display
the
-- value to the user.
module RPCClient
proc getValueFromServer$(hostURL$, server$,
value$)
glb System: CSystem
var body := CStrList
body.StrInsert(value, atEnd)
var
result :=
CCommResult
var sessionID := UniqueID
-- Open a session with the
specified
host
System.Comm.OpenSession(sessionID, hostURL,
8000, "", result)
-- Send the request
System.Comm.SendMessage(sessionID,
"FAR",
"FP",
server,
"text/plain",
body,
keepAlive,
result)
-- Receive the result
System.Comm.ReceiveMessage(sessionID, result)
-- Shut the session down
System.Comm.CloseSession(sessionID, result)
-- Extract the result and
return
it
body := result.GetBody:CStrList
body.GotoHead
return body.StrItem(true)
endProc getValueFromServer
-- Display a Accept dialog to get a number
var accept := CAcceptDialog(nil)
accept.SetEntry("Enter Number:", "", "A", 64)
accept.Accept("RPC Sample Client")
var value := accept.GetAcceptValue("Enter", true)
-- Send it to the server and get the result
value := getValueFromServer("127.0.0.1",
"RPCServer.double",
value)
-- Display the result to the user
CMessageBox("The host said...", value).Run
endModule RPCClient
|
Notes
The sample code does not check for errors.
Note that the server parameter value in the SendMessage
command
in the client identifies the farSlang module (RPCServer.double) to
execute
as the server.
You can easily execute a Windows program remotely. For example, to
execute
Word remotely, add the following lines to the server module.
var launch := CLaunch
launch.LaunchChild("Word")
repeat until launch.IsChildDone
You will find this code in RPCLaunch.far.
The modules shown in this document are in the farSlang library in
your
release. Look for RPC*.far.
Accessing a Remote farBook
TBD
Handling Errors
TBD