Distinct ONC RPC/XDR for .NET Toolkit
Programming Guide

1. Introduction
This introduction is addressed to programmers who wish to write their own distributed applications in .NET using the Distinct® ONC RPC/XDR Toolkit for .NET.  The sections in this programming guide provide an overview of the following topics. 

This document assumes that you are already familiar with .NET, the basic concepts of RPC client/server computing and ONC RPC and XDR in particular. It also assumes a familiarity with the basics of the C binding of ONC RPC. If you wish to write .NET ONC RPC applications and are not yet familiar with these concepts you should do further reading before using the toolkit. 

You may need to refer to the reference part of this manual to understand parts of the examples given in this programming guide. 

This toolkit may be used to help you out in several different scenarios. Here are some: 

  • Whenever you wish to write an ONC RPC client in any .NET-language to communicate with existing ONC RPC/XDR servers.

  • Whenever you wish to preserve the existing interface of an application written using the XDR specifications.

  • Whenever you wish to write C# code that can read and write XDR streams. Other (for example C, C++ ) applications often use this platform independent encoding format for serializing data. 

  • Whenever you wish to write simple client/server applications in .NET. 

In all these scenarios Distinct ONC RPC/XDR for .NET will certainly help you to write portable, high-quality C# code in a minimum amount of time that can be used from any .NET-language. 

This guide discusses how to write client server applications using the Distinct ONC RPC/XDR Toolkit for .NET. It first explains how to write an ONC RPC client and then illustrates how a server is built in .NET. It then explains how to pass RPCs through Internet firewalls and then describes some other concepts the understanding of which are fundamental to using this toolkit. 

2. What comes with the Distinct ONC RPC/XDR for .NET Toolkit? 
The Distinct ONC RPC/XDR Toolkit for .NET is a set of tools and libraries that enables you to write pure .NET ONC RPC clients and servers.
It consists of: 

  • The Distinct ONC RPC/XDR for .NET assembly that contains the ONC RPC/XDR run time libraries that conform to RFC 1831 (RPC: Remote Procedure Call Protocol Specification Version 2) and RFC 1832 (XDR: External Data Representation Standard). The API consists of classes that allow you to write .NET clients, for standard RPC servers, that can be embedded in applets and be run by a standard Web browser. It also allows you to develop ONC RPC stand-alone servers. The package allows connections over TCP and UDP. 

  • An RPCBIND application that implements the RPCBIND protocol versions 2 (portmapper), 3 and 4. 

  • The Rpcgen.NET compiler that translates standard RPC/XDR interface definition files into C# classes that implement the client-and server side stubs and the XDR conversion routines for the described interface. This means Rpcgen.NET implements a C# language mapping for .x IDL files.

  • The RPCInfo utility displays a list of all the services registered with the RPCBIND on a system. This utility can be used to query the RPCBIND on the local host (by specifying "localhost" or no host name at all on the command line) or on a remote host (by specifying the name of the host as a command line argument). Source code for the RPCInfo utility is included for reference. 

  • A set of demo applications that consist of an XDR file which describes an interface of a very simple server, the server implementation, and demo applications that invokes the server written in both Visual Basic.NET and C#.

3. Building a Client with Distinct ONC RPC/XDR for .NET
In this section we will illustrate how to use this toolkit to build a .NET client in C#. For our example we will use the scenario of an application programmer who wants to write a .NET-based front end for an existing ONC RPC service. 

When writing an ONC RPC application the first thing to do is to write an XDR interface definition file. This file describes the data types and the signature of your interface. Distinct ONC RPC/XDR for .NET understands the XDR language as described in RFC 1832 (XDR: External Data Representation Standard) and many of the extension that have been added by various vendors over the last decade (like multiple arguments in one procedure and "in"/"out"/"inout" parameters). By common programming convention XDR files have a .x extension. 

3.1. The XDR Interface Definition Language File
For the remainder of this guide we will work with the XDR interface definition language (IDL) file demo.x as shown below. It defines a simple service that returns a sequence of consecutive lines from a text: 

%/*****************************************
% * Distinct ONC RPC/XDR for .NET Example *
% ****************************************/

struct request {
	int from;
	int to;
};

struct result {
	int number;
	string line<>;
	struct result *next;
};

typedef result *res_list;

program DEMO_SERVER {
	version DEMO_VERSION {
		res_list get_line(request) = 1;
	} = 1;
} = 0x20000023;
This IDL file has 5 sections: 
  1. A comment with leading '%' characters.
  2. A struct request type declaration. 
  3. A struct result type declaration. 
  4. A typedef res_list type declaration.
  5. A program definition for the server interface DEMO_SERVER.

The program definition contains only one procedure (function get_line()). It was a limitation of the original ONC RPC that each procedure may have only one input parameter and one return value. But as these parameters may be of arbitrary complex type (e.g. a structure), the restriction is only a syntactical one (with Distinct ONC RPC/XDR for .NET this limitation is gone and you can also specify multiple arguments per procedure like you would do in C). In our example the input type of get_line() (named request) contains two integers specifying a range of lines (named from and to). The output type res_list is a pointer to a linked list of result structures. Each element of this list describes one line of the result (line number and content string). In this example it is important to define the typedef result because with Rpcgen.NET a procedure can have only plain type names in its signature (e.g. result *get_line(request) is not allowed). But since you can use any level of typedefs this is not really a semantic restriction.

3.2 The Server Implementation in C 
Although the functionality of the service might be obvious at this point, we show here its implementation in C to clarify any questions you may have. In this simple version the lines of text that can be returned are hard coded and there is no error processing done (e.g. if a client requests a non-existing range of lines). 

#include <stdio.h> 
#include <rpc/rpc.h> 
#include "demo.h" 

char *text[] = { 
	"This is the first line of text", 
	"This is the second line of text", 
	"This is the third line.", 
	"This is the 4th line", 
	"This is the 5th line", 
	"This is the 6th line.", 
	"This is the 7th line.", 
	"This is the 8th line." 
}; 

res_list *get_line_1(request *req) 
{ 
	static res_list rl; 
	result *p, *q; 
	result **n; 
	int c; 

	/* free any dynamically allocated memory from previous requests */ 
	p = rl; 
	while (p != NULL) { 
		q = p; 
		p = p->next; 
		free(q); 
	} 

	/* the handling of the new request starts here*/ 
	rl = NULL; 

	/* n points to the pointer where the next list element is inserted */ 
	n = &rl; 
	for (c = req->from; c < req->to; c++) { 
		/* allocate the next element of the list */ 
		*n = malloc(sizeof(result)); 

		/* assign the line number */ 
		(*n)->number = c; 

		/* assign the line (string) */ 
		(*n)->line = text[c]; 
		n = &((*n)->next); 
		*n = NULL; 
	} 
	return &rl; 
} 

To build the server binary with just the demo.x IDL file and the demo_server.c file above, run the standard ONC RPC rpcgen (for C) on demo.x and compile and link the generated files demo_svc.c, demo_xdr.c, demo.h, and demo_server.c into one native binary. 

Once you have built the above server and have it up and running with the ONC RPC RPCBIND, you can proceed to build a .NET stand-alone client that calls this server. 

3.3 The .NET Client 
The following pages will take you through the steps needed to build the .NET client. The first thing to do is to run the Rpcgen.NET provided with the Distinct ONC RPC/XDR for .NET Toolkit on the XDR file. The Rpcgen.NET application translates XDR files into .NET stubs. For a complete description of the Rpcgen.NET command please refer to the reference portion of this manual. 

You run Rpcgen.NET on demo.x by typing: 

>Rpcgen.NET demo.x 

Rpcgen.NET V5.0, Copyright 1997 - 2002 by Distinct Corporation
demo.x
writing: request.cs
writing: result.cs
writing: res_list.cs
writing: demo.cs


The execution of Rpcgen.NET creates four .NET classes in four files, that implement the client stub for calling the demo service described: There is one file per type definition for request.cs, result.cs, and res_list.cs, and one main stub file called demo.cs. 

3.3.1 The Main Stub
We will now take a look at the demo.cs main stub file that has been generated. 

/**************************************** 
* Distinct ONC RPC/XDR for .NET Example * 
****************************************/
 

using com.distinct.rpc;
namespace demoRPC
{

/// <summary>
///
This class was automatically generated by Rpcgen.NET from the RPC/XDR file "demo.x" .
///
It defines the client interface to a server implementing the "DEMO_SERVER" interface.
/// </summary>

public class demo : NetRPCClient {

/// <summary> Program ID of the interface. </summary>

public const int DEMO_SERVER = 0x20000023;

/// <summary>
///
Creates and connects an RPC client for a server that implements the "demo" interface.
/// Calls the remote Portmapper in order to get the port of the server. </summary>
///
<param name = "host"> The host on which the server lives. </param>
///
<param name = "stream"> true for a TCP connection, false for UDP.
</param>

public demo(System.NET.IPAddress host, bool stream) :
       
base(host, DEMO_SERVER, DEMO_VERSION, stream) { }

/// <summary>
///
Creates and connects an RPC client for a server that implements the "demo" interface.
/// The client is connected it to a server with a known port. (No interaction with a portmapper) </summary>
///
<param name = "host"> The host on which the server lives. </param>
///
<param name = "port"> The port on which the server listens. </param>
///
<param name = "stream"> true for a TCP connection, false for UDP. </param>

public demo(System.NET.IPAddress host, int port, bool stream) :
       
base(host, DEMO_SERVER, DEMO_VERSION, port, stream) { }

/// <summary>
///
Creates an RPC client for a server that implements the "demo" interface.
/// It initializes it with a externally created protocol client object. </summary>
///
<param name = "protocol"> The protocol object that implements the client connection.
/// </param>

public demo(ClientGeneric protocol) :
        base
(protocol) { }

/// <summary> Version ID of the interface. </summary>

public const int DEMO_VERSION = 1;

public const int get_line = 1;

/// <summary>
///
Stub method that invokes the server function "get_line" (version 1). </summary>
///
<param name = "arg1"> input parameter of the RPC. </param>
///
<returns> The return value of the RPC. </returns>

public res_list get_line_1(request arg1) {
  res_list retval =
new res_list();
  GetClient().Call(get_line, arg1, retval);
  return
retval;
}
}
}

An instance of this demo stub class represents a client to the demo server. It maintains the complete necessary connection status. Without going into all details we can easily see that this class consists of a number of constants, three constructors, and a public method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the .NET source generated, without the leading % characters. Also Rpcgen.NET generates XML-style comments that are understood by the C#-compiler auto documentation feature (option /doc). 

Like all main client-stub classes in Distinct ONC RPC/XDR for .NET, the demo class is derived from the base class NetRPCClient. This class provides the framework for calling RPC servers using the various possible protocols. Similar to the C binding of ONC RPC, the constants are the program's number and version as well as an ordinal number for each procedure. Of the three constructors the first (public demo(System.NET.IPAddress host, bool stream)) is probably the most commonly used one. We will use it in our application example. Finally, the get_line_1() method is what we have to invoke when we want to interact with the server. The "_1" extension results from the fact that this is the implementation of version 1 of this RPC program. As with the C binding the version number from the XDR file is always appended with an underscore in front of it. 

3.3.2 The Type Definition Files 
We now take a look into the result.cs file, which is one of the type definition files that were generated. 

/**************************************** 
* Distinct ONC RPC/XDR for .NET Example * 
****************************************/
 

using System;
using com.distinct.rpc;
namespace demoRPC
{

/// <summary>
///
This class was automatically generated by Rpcgen.NET from the RPC/XDR file "demo.x".
/// result: was struct
/// </summary>

[Serializable()]
public class result : XDRType {

public int number;
public System.String line;
public result next;

/// <summary>
///
Default constructor for objects of class result. </summary>

public result()

{}

/// <summary>
///
Creates an object of class result. </summary>
///
<param name = "arg_number"> The value of the number component.</param>
///
<param name = "arg_line"> The value of the line component.</param>
///
<param name = "arg_next"> The value of the next component.</param>

public result(int arg_number, System.String arg_line, result arg_next)
{
  number = arg_number;
  line = arg_line;
  next = arg_next;
}

/// <summary>
///
Encodes an object of class result in compliance to RFC 1832 (XDR). </summary>
///
<param name = "xdrs"> The XDR output stream. </param>

public void xdr_encode(XDRStream xdrs)
{
  xdrs.xdr_encode_int(number);
  xdrs.xdr_encode_string(line);
  xdrs.xdr_encode_bool(next !=
null);
  if (next != null)
    next.xdr_encode(xdrs);
}

/// <summary>
///
Decodes an object of class result in compliance to RFC 1832 (XDR). </summary>
///
<param name = "xdrs"> The XDR input stream.</param>


public void xdr_decode(XDRStream xdrs)
{
  number = xdrs.xdr_decode_int();
  line = xdrs.xdr_decode_string();
  next =
null;
  if (xdrs.xdr_decode_bool()) {
    next =
new result();
  next.xdr_decode(xdrs);
}
}
}
}


Once again you can see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the C#-file generated. This happens for all files generated. The data members of the result class mirror the C like definition of the struct result in the XDR file. The linked-list structure of the result type is mapped in an XDR-typical manner to a recursive invocation guided by a boolean value that indicates a NULL pointer value (and thus, the end of the list). The constructor allows to create a result-object and initilize its data members.

Like all classes that are created by Rpcgen.NET from type definitions, result implements the XDRType interface. This interface defines the two methods xdr_encode() and xdr_decode() that are used by the stub implementation for marshalling and unmarshalling the parameter into and from an XDRStream object. Rpcgen.NET generates the necessary code to implement these methods for the given XDR type. 

Typically, if you are using the RPC functionality of the Distinct ONC RPC/XDR for .NET Toolkit, you will not have to bother with these methods. As with the C binding, the stub implementation hides all details of their usage. You just have to construct objects of the defined input parameter class and pass them to the stub method (in our case e.g. of class request to demo.get_line()) and you will receive a newly created reply object from the stub (in our case of class res_list). However, if you use the Distinct ONC RPC/XDR Toolkit just for encoding/decoding XDR data (e.g. from a file) you will have to call these methods directly in conjunction with an appropriate XDRStream object. 

The other two type definition classes, request.cs and res_list.cs, are very similar and have exactly the same structure. 

3.3.3 The Client Application
Now we are ready to write our first Distinct ONC RPC/XDR for .NET application. Below we show the listing of a simple console C# application that uses the generated stubs to make some invocations of the demo server: 

using System;

namespace demoRPC
{

/// <summary>
/// The main program of the client sample.
/// </summary>

public class RPCClient
{

static public void Main (String[] args)
{
  demo client;
// this is the RPC client object 
 
request req = new request(); 

  try {
    client =
new demo(
        System.NET.Dns.GetHostByName(args[0]).AddressList[0],
// the host 
                   
true); // use TCP 
         
for (int i = 0; i < 8; i++) 
           
for (int j = i + 1; j < 8; j++) {
      req.from = i;
      req.to = j;

      System.Console.Out.WriteLine("from " + i + " to " + j);
      res_list rl = client.get_line_1(req);
      result res = rl.
value;

     
while (res != null) {
        System.Console.Out.WriteLine(res.number+":"+res.line);
        res = res.next;

      }
    }

    client.CloseClient();
  } 
    
catch (Exception e) {
    System.Console.Out.WriteLine(e);
  }
}
}
}


Here we take a step by step look at this application: 

  1. In this example we assume that  the generated demo stub class is in the same namespace (in this case demoRPC). So we have no special using directive here.
  2. We then define a very simple class RPCClient with just the static Main() method. Main() interprets the first string parameter from the command line as the name of the host where the demo server is running. 
    Now we declare an object of class demo. This is the client object we use every time we call the demo server. 
  3. Next we open an exception context. Most of the Distinct ONC RPC/XDR for .NET methods throw either a RPCError or an System.Net.Sockets.SocketException. Please note that every time this occurs, the status of the used client object is undefined. If this happens, you should reestablish the connection with a new client object if you want to call the server again. In our sample program we just catch every exception and print the message on the screen. 
  4. Now, we establish the connection to the server by calling the simplest constructor of the demo client class. Parameters are the hostname of the server (from the command line) and a boolean that indicates whether we want to set up a TCP or a UDP connection. A value of true means TCP. 
  5. If this call does not throw an exception we are connected. Depending on whether the addressed host really has a ONC RPC RPCBIND and a demo server running, it may take several seconds before the success or the failure of this call are reported. 
  6. Once we are connected we call the server several times in a loop. We initialize the input object req, invoke the server with res_list rl = client.get_line_1(req); and process the output object rl. Please note the handling of the value member of rl that represents the original type (* result) of the typedef res_list. The invocation of client.get_line_1() may again result in an asynchronous exception of type RPCError, System.Net.Sockets.SocketException, or System.IO.IOException if any problem occurs in the RPC runtime (typically, when the server becomes unreachable). 
  7. Finally, we close the connection to the server and free all resources with client.CloseClient();. 

Here we are. This is our first RPC client written in C#. Now we only have to compile the stubs and our RPCClient.cs with the C#-compiler (Don't forget to add a reference to the assembly RPC_NET.dll that contains the ONC RPC runtime library). 

If you have compiled the files and you run the program, the output will look like this: 

>RPCClient localhost 

from 0 to 1 
0:This is the first line of text 
from 0 to 2 
0:This is the first line of text 
1:This is the second line of text 
from 0 to 3 
0:This is the first line of text 
1:This is the second line of text 
2:This is the third line. 
from 0 to 4 
0:This is the first line of text 
1:This is the second line of text 
2:This is the third line. 
3:This is the 4th line 
from 0 to 5 
... 

If the output looks like this: 

>RPCClient localhost 

System.NET.Sockets.SocketException: No connection could be made because the target machine actively refused it at System.NET.Sockets.Socket.Connect(EndPoint remoteEP)
...


This means that our program has caught an exception because we have most probably forgotten to start the demo server (in this case on localhost, our local machine). 

4. Building a Server with Distinct ONC RPC/XDR for .NET 

4.1 The .NET Server 
This section illustrates how to build a .NET server. To build a server, again we have to run Rpcgen.NET from the Distinct ONC RPC/XDR Toolkit for .NET on the XDR file. This time you run Rpcgen.NET on demo.x with the -S option, telling it that it has to create the server stub as well. Thus, you type: 

>Rpcgen.NET -S demo.x 

Rpcgen.NET V5.0, Copyright 1997 - 2002 by Distinct Corporation
demo.x
writing: request.cs
writing: result.cs
writing: res_list.cs
writing: demo.cs
writing: demoServer.cs

The execution of Rpcgen.NET creates the same four C#-classes as before (even if we do not need the client part demo.cs this time) plus the additional server stub class named demoServer.cs. 

4.2 The Main Stub
We will now take a look into the demoServer.cs main stub file that was generated. 

/**************************************** 
* Distinct ONC RPC/XDR for .NET Example * 
****************************************/
 

using com.distinct.rpc;

namespace demoRPC {

/// <summary>
///
DEMO_SERVER Server Stub
/// This class was automatically generated by Rpcgen.NET from the RPC/XDR file "demo.x".
/// DEMO_SERVER: was interface
/// </summary>

public abstract class demoServer : NetRPCServer {

/// <summary> Program ID of the interface. </summary>

public const int DEMO_SERVER = 0x20000023;

/// <summary> Version ID of the interface. </summary>

public const int DEMO_VERSION = 1;

public const int get_line = 1;

/// <summary>
///
Constructor that creates the RPC server that implements the "demo" interface.
/// </summary>

public demoServer() :
        base(DEMO_SERVER, DEMO_VERSION, true) {
  UnregisterServer();
  RegisterServer(StartUDP(0),
false);
  RegisterServer(StartTCP(0),
true);
}

/// <summary>
///
Constructor that creates the RPC server that implements the "demo" interface. </summary>
///
<param name = "port"> The port on which the server listens (if 0 a random port will be
///
chosen). </param>

public demoServer(int port) :
        base(DEMO_SERVER, DEMO_VERSION, true) {
  UnregisterServer();
  RegisterServer(StartUDP(port),
false);
  RegisterServer(StartTCP(port),
true);
}

/// <summary>
///
Constructor that creates the RPC server that implements the "demo" interface. </summary>
///
<param name = "port"> The port on which the server listens (if 0 a random port will be
///
chosen). </param>
///
<param name = "do_tcp"> True, if a TCP server should be started. </param>
///
<param name = "do_udp"> True, if a UDP server should be started. </param>
///
<param name = "do_rpcb"> True, if a RPCBind nameserver should be started
/// (only if there isn't already one running). </param>

public demoServer(int port, bool do_tcp, bool do_udp, bool do_rpcb) :
        base(DEMO_SERVER, DEMO_VERSION, do_rpcb) {
  UnregisterServer();
  if (do_udp) RegisterServer(StartUDP(port), false);
  if (do_tcp) RegisterServer(StartTCP(port), true);
}

/// <summary>
///
Dispatcher Routine that interpretes the call requests. </summary>
///
<param name = "proc" The index of the requested function. </param>
///
<param name = "xin" read the input-parameter from this XDR stream. </param>
///
<param name = "xout" write the return-parameter to this XDR stream. </param>
///
<returns> true, if the function with the index proc can be served, false otherwise. </returns>

override public bool DoCall(int proc, XDRStream xin, XDRStream xout) {
try {
  switch (proc) {

  case get_line: {
    request arg =
new request();
    arg.xdr_decode(xin);
    res_list ret = get_line_1(arg);
    ret.xdr_encode(xout);
    return true;
  }

  default:
    break;
  }

  return (proc==0)?true:false;
  }

  catch (System.Exception) {
    return false;
  }
}

// Overwrite these abstract server methods for implementing the server's functionality.

public abstract res_list get_line_1(request arg1);
}
}

This stub class demoServer represents the frame for a server that implements the DEMO_SERVER interface described in demo.x. Similar to the client stub we see, this class consists of a number of constants, two constructors, and an abstract method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the generated .NET source without the leading % characters. 

Like all server stub classes in Distinct ONC RPC/XDR for .NET, the class demoServer is derived from the base class NetRPCServer. This class provides the framework for implementing RPC servers using the various possible protocols. Again, the constants are the program's number and version and ordinal numbers for the remote procedure. The method DoCall() is the core of the server. It implements dispatching of the remotely called procedures depending on the given ordinal number and it calls marshalling and unmarshalling of the parameters. Finally, the abstract get_line_1() method is the procedure we have to implement for our demo server by overriding it in a derived class. 

4.3 Synchronization
In Distinct ONC RPC/XDR for .NET DoCall() and, consequently, all implemented remote procedures can be called concurrently by different server threads. In the Distinct ONC RPC/XDR for .NET implementation of an ONC RPC server, each TCP client is served by its own thread. AN additional server thread pool handles the UDP requests. 

4.4 The Server Application
Now we are ready to write our first RPC server in .NET. Below is the listing of a simple .NET application that uses the generated stub to implement the demo server: 

using System;
using com.distinct.rpc;

namespace demoRPC
{

/// <summary>
/// The main program of the server sample.
/// </summary>

public class RPCServer : demoServer
{
 
 static String[] text = { 

    "This is the first line of text", 
    "This is the second line of text", 
    "This is the third line.", 
    "This is the 4th line", 
    "This is the 5th line", 
    "This is the 6th line.", 
    "This is the 7th line.", 
    "This is the 8th line."
  }; 

static public void Main (String[] args)
{
  try
  {
    new RPCServer ();
  }
  catch (RPCError e) {
    System.Console.Out.WriteLine(e);
  }
}

public RPCServer() : base()
{}

// here we override and implement the real remote procedure 

public override res_list get_line_1(request arg)

  res_list rl =
new res_list(); 
  result n =
new result(); 

  for (int c = arg.from; c < arg.to; c++) {

  // allocate the next element in the list 

  n.next = new result(); 
  n = n.next; 
  if (c == arg.from) 
    rl.
value = n;


 
// assign the line number
 
n.number = c;
  // assign the line (string)
 
n.line = text[c];
  } 

  return rl;
}
}
}


This C# code implements the same simple server functionality as the C version shown earlier. It defines a new class named RPCServer that is derived from the demoServer class and that finally implements the remote procedure get_line_1() in a straightforward manner. Compared to the implementation in C there are two differences worth noting: 

  1. The Main() method is not defined by the automatically generated stub but by the implementing class. The only thing Main() does, is create a RPCServer server object and handle the possible RPCError exception. 
  2. Compared to the C version all the ugly memory management has gone from get_line_1(). 

If you now compile this program and run it as a stand alone program together with the RPC client as shown above you will see that our first C# ONC RPC server behaves exactly like the initial C version. 

4.5 RPCBIND
If you are not using fixed server ports (see below) you will need an instance of the RPCBIND running on each machine that hosts ONC RPC servers. It is required to bind RPC clients to servers. Basically, RPCBIND is a name server. Server ports of ONC RPC servers are chosen randomly. It is the responsibility of RPCBIND to tell the clients the port number of a server with a given program/version number. RPCBIND itself is an RPC server that listens on a well-known port (111) for these requests. Typically,  RPCBIND is running as a demon in the background and it is usually provided by the standard ONC RPC infrastructure that is part of e.g. any network connected UNIX host (e.g. NFS relies on ONC RPC). However, if you are starting your RPC server on a machine that does not provide this infrastructure by default (e.g. all flavors of MS Windows) you have to make sure that RPCBIND is present to enable clients to connect to your new server. 

The Distinct ONC RPC/XDR for .NET Toolkit provides its own implementation of the RPCBIND. This native implementation of the RPCBIND implements the RPCBIND protocol versions 3 and 4 as well as the portmap interface version 2. As RPCBIND uses a fixed port number there can be only one RPCBIND instance per node. Therefore, any server that uses the Rpcgen.NET generated stub will first check, whether the system already provides RPCBIND, and if not, it will start its own RPCBIND. Please note, that all ONC RPC servers that are subsequently started on that node will now also use this instance of RPCBIND. This means, if you shut down the Distinct ONC RPC/XDR for .NET server that started RPCBIND, you will also loose the ability to bind new clients to the other, still running servers, as the RPCBIND that stores the binding information is gone. Connections that were established before RPCBIND went down are not affected, as the binding is established only once during the connection setup. You can avoid this by starting the RPCBIND manually before starting the server. 

To check whether RPCBIND is running you can use the standard ONC RPC rpcinfo tool with the -p <hostname> option. This should enumerate the servers registered with RPCBIND of this host. If this tool reports an error or does not show the servers you were expecting, there is obviously a problem with RPCBIND. 

If you have to disable the RPCBIND autostart feature of Rpcgen.NET generated server stubs for some reason, you currently have to edit the xxxServer class. Simply change the third parameter of the invocation super(DEMO_SERVER, DEMO_VERSION, true); in the constructors to false and the server will not try to start RPCBIND. 

5. RPC Programs in Visual Basic .NET

One of the main features of .NET is the Common Language Runtime (CLR). Among various other features it allows for interoperability of code modules written in different .NET-languages. Especially it allows to use libraries (assemblies) written in C# from Visual Basic .NET and vice-versa. This means, with the Distinct ONC RPC/XDR for .NET you can create the stub code for accessing ONC RPC server in C# and use these stubs from Visual Basic .NET. Of course this also applies for implementing servers in Visual Basic .NET and for using the methods from the toolkit directly in your code (e.g. for XDR-Encoding/Decoding).  

As an example here is the Visual Basic code of a client accessing the demo sever. This code basically does the same as C# client above (To keep it short, it uses directly the loopback address to access a local server and it makes just one call instead of some kind of loop):

Imports demoRPC

Module RPCClient

  Sub Main()

    Dim cl As demo
    cl =
New demo(System.NET.IPAddress.Loopback, True)

    Dim req As request
 
  Dim res As res_list

    req = New request()
    req.from = 1
    req.to = 5

    res = cl.get_line_1(req)

    Dim r As result
    r = res.value

    While (Not r Is Nothing)
      System.Console.Write(r.number)
      System.Console.WriteLine(". " + r.line)
      r = r.next
   
End While

  End Sub

End Module

As C# and Visual Basic cannot be mixed directly at code level, the following project structure is required to get the program running:

The C# stub code generated by Rpcgen.NET is put into a class library - in this example the library named "demoRPCLib". This code is then compiled to a DLL (a .NET assembly) and used as a reference in the Visual Basic project. Both projects, the library and the Visual Basic client also need a reference to the assembly RPC_NET.dll that contains the ONC RPC runtime library.

6. XDR to .NET Type Mapping 
In most cases the mapping of XDR types to .NET types and classes is quite obvious. For an overview of the complete mapping please have a look at the following table: 

XDR Type .NET Type
int int
unsigned int uint
long int
unsigned long uint
short short
unsigned short ushort
hyper long
unsigned hyper ulong
char sbyte
unsigned char byte
float float
double double
bool boolean
string String
opaque byte array
fixed length array .NET array
variable length array .NET array
optional data (pointer like *x) Reference to an object of class x. If x is a basic type a special wrapper class XDRx that implements XDRType is used instead.
enum x Class x implements XDRType with member variable int value and one constant int per enum constant.
struct x Class x implements XDRType with member variables for each struct member.
union x Class x implements XDRType with member variables for each union member including discriminant. No overlaying of members is supported (neither for type conversion nor for saving space).
typedef x y Class x implements XDRType with member variable value of the redefined type y.


The most notable difficulty in the .NET mapping is the handling of typedefs. As .NET has no notion of type name aliasing, the only viable solution that preserves the introduced names is the definition of a new class. In the .NET mapping of typedef this new class has always only one data member, called value, that is a variable of the redefined type (for an illustration please refer to the generated file res_list.cs). 

7. How To 
This section discusses how to do certain things in the Distinct ONC RPC/XDR for .NET environment. It includes information on how to authenticate, how to broadcast RPCs, how to run RPCs in batch mode and discusses server data flow, using XDR streams and error and timeout handling. 

7.1 AUTHENTICATION 
By default, Distinct ONC RPC/XDR for .NET uses NULL authentication. However, this default can be changed at the client object by using setCredential() and setVerifier(). In the other direction the returned verifier from the server can be obtained after each call with getReturnedVerifier(). Please note that Distinct ONC RPC/XDR for .NET (like the C binding) provides the data structures for transferring authentication data and the methods for setting, retrieving and checking this data. It does not provide protection against forged authentication data. Any security and access control features have to be implemented by your application. Below is a code sample that creates a Unix authentication (sometimes also called System authentication) and sets it as the new credential of this client. From now on each request will be sent with this authentication. 

using System;
using com.distinct.rpc;

namespace demoRPC
{

   ...

  try {
    client =
new demo(
        System.NET.Dns.GetHostByName(args[0]).AddressList[0],
// the host 
                   
true); // use TCP 

        // a new Unix authentication is created (UID = GID = 0
        client.GetClient().setCredential(new AuthUnix(
             (int)(System.DateTime.Now.Ticks/10000000),
             "myhostname", 0, 0,
new int[0]));


        res_list rl = client.get_line_1(req);
        ...

	// Here it is assumed that the server returned an authentication
	// (typically of type short) that is now used for any subsequent calls
	client.GetClient().setCredential(client.GetClient().getReturnedVerifier());

    }
    ...

The class com.distinct.rpc.NETRPCServer provides the DoAuth() method for handling authentication at the server side. Override this method if you want your server to check authentication (the default is no check). DoAuth() receives the credential and verifier provided by the client as well as the call identifier and returns the verifier that should be sent back with the reply to the calling client (simply return null for NULL authentication). It throws a com.distinct.rpc.RPCAuthError if it wants to signal an unsuccessful authentication. The following code fragment illustrates how DoAuth() can be used in the server code. 

... 
public override Auth DoAuth(int proc, Auth cred, Auth verf)
{
 
if (cred.Flavor != NetRPC.kAUTH_UNIX)
   
throw new RPCAuthError(NetRPC.kAUTH_TOOWEAK);

  // convert it to the correct type
  AuthUnix unix_cred = new AuthUnix(cred);

  if (unix_cred.Uid != 0)
   
throw new RPCAuthError(NetRPC.kAUTH_BADCRED);

  // uses the same authenticator as returned verifier
  // usually you would use some shortcut here
 
return unix_cred; 


... 


This example implementation of DoAuth() checks for each call to see if it has a credential of type AuthUnix and if so if the user id is 0. If either condition is not fulfilled it throws a com.distinct.rpc.RPCAuthError exception in order to terminate any further processing of this call. The remote procedure itself is not called at all in this case. 

If you need to use other types of authentication (e.g Kerberos style) you can implement it easily by deriving a new authentication class from the com.distinct.rpc.Auth base type. 

7.3 RPC BROADCAST 
RPC broadcast allows your application to broadcast a message to all computers on a network. Typically you would use it if you were trying to identify which servers are available. RPC broadcast runs over UDP only and always uses NULL authentication. RPC broadcast can handle many responses from all responding servers. It filters out all unsuccessful responses, therefore, if a version mismatch exists between the broadcaster and a remote service, the user of RPC broadcast will never know this. When using RPC broadcast keep in mind that all broadcast messages are sent to RPCBIND. Thus, only services that register themselves with their RPCBIND are accessible via RPC broadcast. 

In Distinct ONC RPC/XDR for .NET broadcast calls are implemented by the static member function NetRPCClient.BroadcastCall(). Syntax and semantics are very similar to the C binding. Here is an example of how this function may be used calling the demo server we used earlier. 

using System;
using com.distinct.rpc;

namespace demoRPC
{

/// <summary>
/// The main program of the client sample.
/// </summary>

public class RPCClient : BroadcastHandler
{

  // "BroadcastHandler.onReply()" is the handler called for each reply packet.

  public bool onReply(XDRType retval, System.Net.IPAddress addr)
  {

    result res = ((res_list)retval).value;
   
while (res != null) {
      System.Console.Out.WriteLine(res.number+":"+res.line);
      res = res.next;
    }

    return true; // return with the first answer
}

static public void Main (String[] args)
{
  request req =
new request(); 
  res_list rl =
new res_list();
  RPCClient app =
new RPCClient();

  try {

    for (int i = 0; i < 8; i++)
     
for (int j = i + 1; j < 8; j++) {

        req.from = i; req.to = j;
        System.Console.Out.WriteLine("from " + i + " to " + j);

        demo.BroadcastCall( demo.DEMO_SERVER,
                            demo.DEMO_VERSION,
                            demo.get_line,
                            req, rl,
                            app);
      }
    }
   
catch (Exception e) {
      System.Console.Out.WriteLine(e);
    }
  }
}
}

The changes to the original client program are:

  • The com.distinct.rpc.BroadcastHandler and com.distinct.rpc.XDRType have to be imported.

  • One class has to implement the com.distinct.rpc.BroadcastHandler interface. This interface has only one method onReply() that is called by Distinct ONC RPC/XDR for .NET for each reply packet (as long as onReply() returns true). In our example onReply() just prints the reply and returns true in order to stop the handling of further reply messages.

  • There is no longer a client object. It is not required as the BroadcastCall method is static (a class-method) and not dependent on a special connection between a client and a server.

  • The return object is created as an empty object before the call is done and it is then passed to the call as an input parameter. 
    The request is made via the BroadcastCall() method. This method is not implemented in the client class but directly in com.distinct.rpc.NETRPCClient. This means the call is generic and takes the server number, the server version, and the call identifier (all taken from the .x file and defined in the client class) as well as references to the argument and the return class as parameters.

  • The last parameter is the object that implements the reply handler onReply(). If this parameter is null all possible replies are discarded. 

7.4 HOW TO BATCH RPCs
Usually RPC is synchronous, meaning that clients send a call message and wait for the server to reply by indicating that the call succeeded. But this implies that clients may be sitting idle while servers process calls. Therefore, in cases where the client does not require an acknowledgement for every message sent, RPC messages can be placed in a pipeline of calls to the desired server. This process is known as batching. When operating in batch mode, RPC is simply acting as a message passing system. It is possible to batch RPC calls when: 

No RPC call in the pipeline requires a response from the server, and the server does not send a response message. 
The pipeline of calls is transported via the reliable stream transport protocol TCP. 
Because the server does not respond to every call, the client can generate new calls in parallel with the server executing previous calls. This overlapped execution greatly decreases the total elapsed time of a series of calls. Because the batched calls may be buffered, the client must eventually do a nonbatched call to flush the pipeline. 

Distinct ONC RPC/XDR for .NET provides two additional methods for the use of batched calls, one for the client and one for the server side. A client simply has to set the timeout value of the client object to the value of -1 in order to force all RPC calls to return immediately after sending the request message (by throwing a RPCTimeoutError exception). Use the com.distinct.rpc.setTimeout() method to modify the timeout value.

The com.distinct.rpc.NETRPCServer class provides the IsBatched() method for handling batched calls at the server. Override this method if you want your server not to send any reply to a request. IsBatched() receives the call identifier and returns a bool value. Depending on the call identifier it decides whether this call is one that uses the batched mode and if so it simply returns true. This tells Distinct ONC RPC/XDR for .NET that there is no need to send any reply to the client. 

7.5 CONTROL AND DATA FLOW ON THE SERVER 
As we have seen so far, there are three methods that can be overloaded to implement an ONC RPC server's functionality: NetRPCServer.DoAuth(), NetRPCServer.DoCall(), and NetRPCServer.IsBatched(). The server calls these functions in exactly this order to serve each call, meaning first it calls DoAuth() to check authentication, then it calls DoCall() to perform the remote procedure call itself, and finally, it checks with IsBatch() whether there is a need to send back a reply message. Simple RPC servers only override DoCall() and use defaults for DoAuth() and IsBatched(). You only have to override the latter two methods if you want to change this default. 

In some cases you might want to pass data between these three server-specific methods, e.g. if you need authentication data when processing the remote procedure call itself. Distinct ONC RPC/XDR allows for this data flow by providing the NetRPCServer.SetClientData() and NetRPCServer.SetClientData() methods. With SetClientData() you can register any .NET object and retrieve it later by calling getClientData(). The data is stored per-thread. This means one server thread (the entity that serves one client request) can store any data that has to be transferred between the three methods (NetRPCServer.DoAuth(),NetRPCServer.DoCall(),NetRPCServer.IsBatched()) here. The data will no longer be available after the call to IsBatched(). 

7.6 USING XDR STREAMS
XDRStream implements all the encoding/decoding methods that are required for encoding/decoding the .NET basic types according to RFC 1832 (XDR). It also provides a simple memory management for the streamed data. XDRStream implements a dynamically growing buffer of bytes (in fact a queue). The constructors allow for the creation of an empty XDRStream (with default allocation size 1024), an empty XDRStream with user defined allocation size (just a possible optimization), or an XDRStream initialized with the content of an existing byte array (usually the start of the decoding procedure). The put_byte(), put_bytes(), and all xdr_encode_xxx() methods add bytes to the head of the queue, while the get_byte(), get_bytes(), and all xdr_decode_xxx() methods consume bytes from the tail. With get_length() and get_data() you can request the current length and content of the queue without changing its status. The reset() methods reset the XDRStream to an empty queue. dump() just prints the content of the queue in hex and ASCII to System.out (typically used for debugging). 

When you want to use Distinct ONC RPC/XDR for .NET just for reading or writing XDR encoded data you might want to use an XDRStream object and just manipulate the contents of its buffer. Alternatively you can derive your own XDRStream class that handles I/O automatically. 

7.7 TIMEOUT HANDLING
Whenever asynchronous network protocols are designed, timeouts play an important role. Since setting a timeout for a response is the only way to determine whether your server is still alive, it is crucial to choose the right timeout value. If it is too short, you might think your server has crashed when it may just be working slowly under a heavy load. If the timeout is too long, you might waste important time waiting for something that will never happen. 

RPC maintains timeout values for both types of communication protocols (UDP and TCP). Typically, it is more important to have a timeout in UDP sessions, as the protocol itself does not provide any error detection. In addition, as UDP does not guarantee message delivery at all, a retransmission feature is desirable for UDP clients. RPC implements both. TCP provides reliable streams and usually it is a good idea to rely on the TCP connection management to determine whether your server is still alive. But, as this does not protect your client from endlessly looping (or totally overloaded) servers, RPC also has a time-out mechanism for TCP connections. 

By default, the RPC time out is set to 25 seconds (UDP retransmission time out is 5 seconds). When a client does not receive any byte of the reply message for more than 25 seconds it assumes the server has crashed and throws a timeout RPCError exception. You can change this time-out value by calling NetRPCClient.SetTimeout() on your client object, specifying the new time-out value in milliseconds (a value of 0 means no timeout at all). For clients running over the UDP protocol, the retransmission time can be changed by calling NetRPCClient.SetResend.

7.8 ERROR HANDLING
Whenever the Distinct ONC RPC/XDR for .NET runtime detects a non-recoverable error in a client call it will throw an exception. Usually this will be an System.Net.Sockets.SocketException when the problem is directly related to low-level socket I/O or an RPCError when the problem has a close relation to the RPC protocol. For details about the problem please look into the message string of the particular exception. Once you have received an exception, the status of the client object is undefined. Therefore, it is important to reestablish the connection by creating a new client object before trying to call the server again. 

Exception Reason
RPCAuthError An authentication error occurred
RPCDecodeError A problem such as an unexpected EOF has occurred during XDR decoding
RPCServerError An RPC server is not available or not registered with RPCBIND
RPCTimeoutError An RPC blocked longer than the timeout value of the client
RPCError For all other errors

7.9 Connecting Through Firewalls 
Most RPC applications are designed for Intranets, and although RPC can be made to run over the Internet, the original specifi