Communications/Transactions Guide and Reference

Building the server application

Building the server application is a more complex exercise than building the client. First, much of the implementation must be done in scripts, because the VisualAge visual parts are aimed at building client applications.

In the VisualAge Organizer window, create a new application by selecting New from the Applications pull-down menu. Name your new application RPCProcServerApp.

In the VisualAge Organizer window, create a new nonvisual part in this application by selecting New from the Parts pull-down menu. Name your new part RPCProcServer.

An RPC server provides one or more functions and services. Each function is represented by a method in Smalltalk. The VisualAge RPC server requires all functions to be methods of the same class or instance. Any class or method is valid. When VisualAge server receives a request it will send a message to the class or instance you specified. Since only one class or instance is allowed per server, all methods for that server must be class or instance methods.

Providing functions and services

In our example server we will provide four functions or services:

For simplicity we decide to create class methods of RPCProcServer to define these functions. Open the script editor and add the following methods:

addTwo: anInputRecord with: anOutputRecord
    "this method adds two numbers together and
	sets the output record with the results"
    anOutputRecord at: #field1 put:
	((anInputRecord at: #field1) +
	  (anInputRecord at: #field2))
 
 
  subtractTwo: anInputRecord with: anOutputRecord
    "this method subtracts two numbers together and
	sets the output record with the results"
    anOutputRecord at: #field1 put:
	((anInputRecord at: #field1) -
	  (anInputRecord at: #field2))
 
 
  multiplyTwo: anInputRecord with: anOutputRecord
    "this method multiplies two numbers together and
	sets the output record with the results"
    anOutputRecord at: #field1 put:
	((anInputRecord at: #field1) *
	 (anInputRecord at: #field2))
 
 
  concatenateString: anInputRecord with: anOutputRecord
    "this method concatenates a string with its self and
	sets the output record with the results"
    anOutputRecord at: #field1 put:
	(anInputRecord at: #field1)	.
    anOutputRecord at: #field2 put:
	((anInputRecord at: #field1) ,
	 (anInputRecord at: #field1))

Notice that all communcations between a client and a server must be done with records. Also the client and server records must match for the function requested or XDR will not encode/decode the data correctly and unpredictable results will occur.

Sarting the RPC server

Now we will create a script that will start and run an RPC server. We begin by creating a class method called serverTest and then add the following code.

The AbtRPCConnectionSpec object represents the server that clients are going to communicate with. To create an instance of this class you need to provide the TCP name or dotted address of the server, the program and version numbers. For Example:

connspec := AbtRPCConnectionSpec new
    programNumber: 16r30099999;
    programVersion: 1;
    serverName: '9.67.167.75';
    yourself.

Next you need to define your input and output records for each server procedure.

inputRecord1 :=
  (AbtCompoundType new name: 'myinrcd1';
    addField: (AbtCUIntField new name: #field1; yourself);
    addField: (AbtCUIntField new name: #field2; yourself);
    yourself) newRecord.
outputRecord1 :=
  (AbtCompoundType new name: 'myoutrcd1';
    addField: (AbtCUIntField new
                         name: #field1;
                         yourself);
    yourself) newRecord.
inputRecord2 :=
  (AbtCompoundType new name: 'myinrcd2';
    addField: (AbtCCharArrayField new
                   name: #field1;
                   count: 20;
                   yourself);
    yourself) newRecord.
outputRecord2 :=
  (AbtCompoundType new name: 'myoutrcd2';
    addField: (AbtCCharArrayField new
                   name: #field1;
                   count: 20;
                   yourself);
    addField: (AbtCCharArrayField new
                   name: #field2;
                   count: 40;
                   yourself);
    yourself) newRecord.

We need to let the server know the class that will be sent the message when a client request is received. Use the receiver: method to associate the class with the server. Also we need to associate the connection spec we created with the server we are about to create.

(server := AbtRPCServer new)
    connectionSpec: connspec;
    receiver: RPCProcServer.

Next you need to create an instance of the AbtRPCProcedures class for each procedure the server is able to invoke. An AbtRPCProcedures instance contains the method name to be invoked and the input and ouput records it expects. AbtRPCServer expects an ordered collection of the procedure instances. The makeRows: method will create a collection of procedure instances and associate the collection with the server. If you use the makeRows: method, create a collection one larger than your needs. You must set the method name, and input and output record values for all members of the ordered collection except the first one, which gets set by default to the null proc.

result :=  server makeRows: 5.
(server procedures at: 2)
    methodInvoked: 'addTwo:with:' asSymbol;
        inputRecord: inputRecord1;
        outputRecord: outputRecord1.
(server procedures at: 3)
    methodInvoked: 'subtractTwo:with:' asSymbol;
        inputRecord: inputRecord1;
        outputRecord: outputRecord1.
(server procedures at: 4)
    methodInvoked: 'multiplyTwo:with:' asSymbol;
        inputRecord: inputRecord1;
        outputRecord: outputRecord1.
(server procedures at: 5)
    methodInvoked: 'concatenateString:with:' asSymbol;
        inputRecord: inputRecord2;
        outputRecord: outputRecord2.

Next, you need to create a server handle and register with PortMapper.

result :=  server createServerHandle.

Next, start your server. It will loop forever waiting for client transaction requests. When a request is received the server will invoke the procedure specified with both an input and output record. The output record will then be transmitted back to the waiting client.

[server serverRun] fork.
^server

The server application is complete, so save the method and close the Composition Editor window. The completed method should look like the following example.

serverTest
    connspec := AbtRPCConnectionSpec new
		programNumber: 16r30099999;
		programVersion: 1;
		serverName: '9.67.167.75';
		yourself.
    inputRecord1 :=
	(AbtCompoundType new name: 'myinrcd1';
	addField: (AbtCUIntField new name: #field1; yourself);
	addField: (AbtCUIntField new name: #field2; yourself);
	yourself) newRecord.
    outputRecord1 :=
	(AbtCompoundType new name: 'myoutrcd1';
	addField: (AbtCUIntField new name: #field1; yourself);
	yourself) newRecord.
    inputRecord2 :=
	(AbtCompoundType new name: 'myinrcd2';
	addField: (AbtCCharArrayField new
                       name: #field1;
                       count: 20;
                       yourself);
	yourself) newRecord.
    outputRecord2 :=
	(AbtCompoundType new name: 'myoutrcd2';
	addField: (AbtCCharArrayField new
                       name: #field1;
                       count: 20;
                       yourself);
	addField: (AbtCCharArrayField new
                       name: #field2;
                       count: 40;
                       yourself);
	yourself) newRecord.
    (server := AbtRPCServer new)
	connectionSpec: connspec;
	receiver: RPCProcServer.
    result :=  server makeRows: 5.
    (server procedures at: 2)
        methodInvoked: 'addTwo:with:' asSymbol;
            inputRecord: inputRecord1;
            outputRecord: outputRecord1.
    (server procedures at: 3)
        methodInvoked: 'subtractTwo:with:' asSymbol;
            inputRecord: inputRecord1;
            outputRecord: outputRecord1.
    (server procedures at: 4)
        methodInvoked: 'multiplyTwo:with:' asSymbol;
            inputRecord: inputRecord1;
            outputRecord: outputRecord1.
    (server procedures at: 5)
        methodInvoked: 'concatenateString:with:' asSymbol;
            inputRecord: inputRecord2;
            outputRecord: outputRecord2.
    (result :=  server createServerHandle)
        isCommunicationsError
            ifTrue: [self halt].
    [server serverRun] fork.
    ^server


[ Top of Page | Previous Page | Next Page | Table of Contents | Index ]