Computer Graphics Workshop '96 Lab Notes

1/25/96

Today's topics
Networking

This lab will discuss some of the issues relating to creating a networked application. We will be using the SocketMan class library from within Scheme to support easy client-server and peer-peer networking.

Documentation for SocketMan is located in

/mit/thingworld/Ivy/SocketMan/Documentation/SocketMan.doc

Example code for some multicasting programs is located in

/mit/thingworld/Ivy/Networking/

These programs' listings are also shown below.

SocketMan

SocketMan (short for Socket Manager) is a C++ class library designed to make implementing a networked application simple. By supporting several design abstractions, SocketMan can nearly transparently implement several types of networking models, such as client-server, peer-peer, and group networking (via IP multicasting).

The SocketMan classes (SocketMan and SocketClient) work by sending data structures known as SocketBufs amongst themselves. A SocketBuf is a very simple C++ class, of which we will be using only two parts.

Methods from class SocketBuf:

void setBufFromString(char *new_value)

Fields from class SocketBuf:

char *buf

The first part is the setBufFromString method. This method takes a Scheme string as argument and sets the internal buffer buf of the SocketBuf to be this string. For example, to create a new SocketBuf which contains the string "Hello world", we could do the following:

> (define my-sbuf (new-SocketBuf))
> (-> my-sbuf 'setBufFromString "Hello world")
The next part is the buf field. Note that this is not an Inventor-style field; when you send the buf message to a SocketBuf, it returns the string which the object contains. For example, to extract the string from the above object, we could do:
> (define my-string (-> my-sbuf 'buf))
> my-string
"Hello world"
By using some built-in Scheme functions, and some provided by the Scheme Library, we can send Scheme objects over the network in the form of strings. To enable this functionality, put the following two lines in any Scheme code which needs network support:
(require 'object->string)
(require 'string-port)
The object->string procedure takes a Scheme object and prints it into a string, returning this string as its value. This function will not have much meaning for Inventor objects, since they print their memory locations as their values; in order to send information between Scheme interpreters, you must use object->string on a native Scheme data type such as a list, vector, or number.

As an example, to put the string representing the Scheme vector of #(1.0 4.0 9.0) into a SocketBuf, we would do the following:

(define my-vec '#(1.0 4.0 9.0))
(define my-string (object->string my-vec))
(define my-sbuf (new-SocketBuf))
(-> my-sbuf 'setBufFromString my-string)
We can then send this SocketBuf using the methods described below.

On the receiving end, we would like to convert the data in a SocketBuf back into a Scheme object. We can do this using the standard Scheme read command, combined with the Scheme Library's string-port extension:

(define the-new-string (-> my-sbuf 'buf))
(define the-new-object (call-with-input-string the-new-string read))
the-new-object would, in our above example, now be the vector from the other end. We can now access its values in the standard way:
> (vector-ref the-new-object 1)
4.0
The two primary networking functions which we will be using (and which are present in both SocketMan and SocketClient) are readWithPollAndTimeout and writeWithTimeout. The former takes no arguments, and attempts to read in a SocketBuf from the network; the latter takes a SocketBuf as argument, and attempts to write it to the network. It returns a status code, which, at least for now, can be ignored.

Client-server

The client-server networking model involves one machine, the server, to which one or more clients connect. In the client-server networking model, clients do not connect to each other.

The SocketMan class handles connections to multiple clients transparently. The acceptConnectionWithPoll method takes no arguments and receives a connection from a client, if one is attempting to connect. Connections are automatically closed when the client disconnects. The rewindSockets and nextSocket methods allow iteration over all connected clients. See the code in

/mit/thingworld/Ivy/Networking/server.scm

for an example of iterating over sockets. client.scm and server.scm in this directory implement a client-server pair. The connect-and-send function connects a SocketClient to a server and sends a message. Both the server and message are strings; for example:

(connect-and-send "m4-034-18" "Hi there!")
Peer-to-peer

Peer-to-peer networking is accomplished simply by creating both a SocketMan (which can only accept connections) and a SocketClient (which can only make connections) within the same application. The SocketMan object can be used to satisfy information requests from another host; the SocketClient object can be used to generate these requests.

Group

Group networking, wherein multiple machines can all send messages to one another, is implemented in the SocketMan library via the use of the SocketClient class. When a SocketClient is in multicasting mode, it can connect to a special range of multicast addresses. All messages sent to the appropriate port on this address will then be automatically "multicast" to all clients connected to that address. The valid range of multicast IP addresses are

224.0.0.0 - 239.255.255.255

Examples of multicasting code are in

/mit/thingworld/Ivy/Networking/

The cone-sender program opens an examiner viewer with a cone inside a handle box manipulator. Dragging the cone around the scene causes the cone in any cone-receiver's window to follow the motion of the sender's cone. This program demonstrates sending Scheme vectors over the network.

The mclooper and mcsender programs demonstrate the ability to send differently typed data over the network, encapsulated within strings. The send-string function inside mcsender sends a string over the network; the send-SbVec3f function sends the contents of an SbVec3f. The mclooper program defines a loop-infinite procedure which, when run, simply waits for data sent by the mcsender functions and displays any data read in. Note that this is implemented poorly; the "right" way of performing this task is inside an idle callback, as is shown in the cone programs.

Source code for examples

client.scm

(define client (new-SocketClient 10732))

(define sbuf (new-SocketBuf))

(define (connect-and-send server message)
  (-> client 'closeConnection) ;; if necessary,
                               ;; close current connection
  (-> client 'connectToServer server) ;; open new connection
  (-> sbuf 'setBufFromString message) ;; set message
  (-> client 'writeWithTimeout sbuf) ;; send it
  )
server.scm

(define server (new-SocketMan 10732))

(define (sub-loop)
  (if (= 1 (-> server 'nextSocket))
      (begin
	(let ((sbuf (-> server 'readWithPollAndTimeout)))
	  (if (not (equal? sbuf (SocketBuf-cast (void-null))))
	      (begin
		(display (-> sbuf 'buf))
		(newline))))
	(sub-loop))))

(define (loop-once)
  (-> server 'acceptConnectionWithPoll)
  (-> server 'rewindSockets)
  (sub-loop))

(define (loop-semiinfinite times)
  (if (> times 0)
      (begin
	(loop-once)
	(loop-semiinfinite (- times 1)))))

(define (loop-infinite)
  (loop-once)
  (loop-infinite))
mclooper.scm

(require 'string-port)

(define sc (new-SocketClient 10666))
(-> sc 'setUsingMulticast 1)
(-> sc 'connectToServer "224.6.6.6")

(define (loop-infinite)
  (let ((sbuf (-> sc 'readWithPollAndTimeout)))
    (if (not (equal? sbuf (SocketBuf-cast (void-null))))
	(let ((obj (call-with-input-string (-> sbuf 'buf) read)))
	  (if (vector? obj)
	      (begin
		(display "Vector values: ")
		(for-each
		 (lambda (i)
		   (display i)
		   (display " "))
		 (vector->list obj))
		(newline))
	      (begin
		(display (-> sbuf 'buf))
		(newline))))))
  (loop-infinite))
mcsender.scm

(require 'string-port)
(require 'object->string)

(define sc (new-SocketClient 10666))
(-> sc 'setUsingMulticast 1)
(-> sc 'connectToServer "224.6.6.6")

(define send-sbvec3f
  (let ((sbuf (new-SocketBuf)))
    (lambda (the-sbvec)
      (let* ((the-vec (-> the-sbvec 'getValue))
	     (the-string (object->string the-vec)))
	(-> sbuf 'setBufFromString the-string)
	(-> sc 'writeWithTimeout sbuf)))))

(define send-string
  (let ((sbuf (new-SocketBuf)))
    (lambda (the-string)
      (-> sbuf 'setBufFromString the-string)
      (-> sc 'writeWithTimeout sbuf))))
cone-sender.scm

(require 'object->string)

(load "cone-network")

(define viewer (new-SoXtExaminerViewer))
(-> viewer 'show)

(define root (new-SoSeparator))
(-> root 'ref)

(define mat (new-SoMaterial))
(-> (-> mat 'diffuseColor) 'setValue 0.2 0.8 0.2)
(-> root 'addChild mat)

(define manip (new-SoHandleBoxManip))
(-> root 'addChild manip)

(-> root 'addChild (new-SoCone))

(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue "field-changed-cb")

(define field-sensor (new-SoFieldSensor (get-scheme-sensor-cb)
					(void-cast cb-info)))
(-> field-sensor 'attach (-> manip 'translation))

(define field-changed-cb
  (let ((sbuf (new-SocketBuf)))
    (lambda (user-data sensor)
      (let* ((vec (-> (-> (-> manip 'translation) 'getValue)
		      'getValue))
	     (the-string (object->string vec)))
	(-> sbuf 'setBufFromString the-string)
	(-> sc 'writeWithTimeout sbuf)))))

(-> viewer 'setSceneGraph root)
cone-receiver.scm

(require 'object->string)
(require 'string-port)

(load "cone-network")

(define viewer (new-SoXtExaminerViewer))
(-> viewer 'show)

(define root (new-SoSeparator))
(-> root 'ref)

(define mat (new-SoMaterial))
(-> (-> mat 'diffuseColor) 'setValue 0.2 0.8 0.2)
(-> root 'addChild mat)

(define xform (new-SoTransform))
(-> root 'addChild xform)

(-> root 'addChild (new-SoCone))

(define cb-info (new-SchemeSoCBInfo))
(-> cb-info 'ref)
(-> (-> cb-info 'callbackName) 'setValue "idle-cb")

(define idle-sensor (new-SoIdleSensor (get-scheme-sensor-cb) 
				      (void-cast cb-info)))
(-> idle-sensor 'schedule)

(define (idle-cb user-data sensor)
  (-> sensor 'schedule)
  (let ((sbuf (-> sc 'readWithPollAndTimeout)))
    (if (not (equal? sbuf (SocketBuf-cast (void-null))))
	(let ((the-vec (call-with-input-string (-> sbuf 'buf)
					       read)))
	  (-> (-> xform 'translation) 'setValue the-vec)))))

(-> viewer 'setSceneGraph root)
cone-network.scm

(define *cone-sender-port* 13742)
(define *cone-sender-address* "224.4.8.16")

(define sc (new-SocketClient *cone-sender-port*))
(-> sc 'setUsingMulticast 1)
(-> sc 'connectToServer *cone-sender-address*)

Back to the CGW '96 home page

$Id: index.html,v 1.4 1996/01/29 00:03:31 kbrussel Exp $