1. MultiNet Programming Tutorial

 

This chapter contains short tutorials on various aspects of application programming using MultiNet.

Once you have installed the MultiNet Programmers' Kit, you will find a number of example programs in the appendices in the directory MULTINET_ROOT:[MULTINET.EXAMPLES]. The following tutorials, together with the example programs, are designed to get you started as an application programmer using MultiNet.

 

Sockets

A socket is an endpoint for communication. Two cooperating sockets, one on the local host and one on the remote host, form a connection. Each of the two sockets has a unique address that is described generically by the  sockaddr C programming language structure. The sockaddr structure is defined as follows:

struct sockaddr {
   u_char sa_len;     /* length of data structure */
   u_char sa_family;  /* Address family */
   char sa_data[14];  /* up to 14 bytes of direct address*/
};

The sa_family field specifies the address family for the communications domain to which the socket belongs. For example, AF_INET for the Internet family. The sa_data field contains up to 14 bytes of data, the interpretation of which depends on the value of sa_family.

If the sa_family field is AF_INET, the same sockaddr structure can also be interpreted as a sockaddr_in structure that describes an Internet address. A sockaddr_in structure is defined as follows:

struct sockaddr_in {
    u_char sin_len;
    u_char sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

The sin_family field specifies the address family AF_INET. The sin_port field specifies the TCP (Transmission Control Protocol) or UDP (User Datagram Protocol) port number of the address. Whether the communication uses TCP or UDP is not determined here, but rather by the type of socket created with the socket() call: SOCK_STREAM for TCP or SOCK_DGRAM for UDP. The sin_addr field specifies the Internet address. The sin_zero field must be zero. Both the sin_port field and the sin_addr field are in network byte order. See the htons() and htonl() functions in Chapter 3 for further information about network byte ordering.

The sockaddr and sockaddr_in structures serve as input and output to a number of library routines. For example, they may be used as input, specifying the address to which to make a connection or send a packet, or as output, reporting the address from which a connection was made or a packet transmitted.

Internet addresses are normally manipulated with the gethostbyname()gethostbyaddr(), inet_addr(), and inet_ntoa() functions. gethostbyname() and inet_addr() convert a host name or ASCII representation of an address into the binary representation for the sockaddr_in structure. gethostbyaddr() and inet_ntoa() are used to convert the binary representation into the host name or ASCII representation for display.

Port numbers are normally manipulated with the getservbyname() and getservbyport() functions. getservbyname() converts the ASCII service name to the numeric value, and getservbyport()  converts the numeric value to the ASCII name.

The following example shows a typical program that converts the Internet address and the port into binary representations.

#include "multinet_root:[multinet.include.sys]types.h"
#include "multinet_root:[multinet.include.sys]socket.h"
#include "multinet_root:[multinet.include]netdb.h"
#include "multinet_root:[multinet.include.netinet]in.h"

main(argc,argv)
int argc;
char *argv[];
{

    struct sockaddr_in sin;
    struct hostent *hp;
    struct servent *sp;

    /* Zero the sin structure to initialize it */

    bzero((char *) &sin, sizeof(sin));
    sin.sin_family = AF_INET;

    /* Lookup the host and initialize sin_addr */

    hp = gethostbyname(argv[1]);
    if (!hp) {      /* Perhaps it is an ASCII string */
       sin.sin_addr.s_addr = inet_addr(argv[1]);
   if (sin.sin_addr.s_addr == -1) {
      printf("syntax error in IP address\n");
      exit(1);
   }
} else {        /* Extract the IP address */
    bcopy(hp->h_addr, (char *) &sin.sin_addr,
          hp->h_length);
}

/* Lookup up the name of the SMTP service */

sp = getservbyname("smtp","tcp");|
if (!sp) {
    printf("unable to find smtp service");
    exit(1);
}

sin.sin_port = sp->s_port;

/* Now we are ready to create a socket and
pass the address of this sockaddr_in
structure to the connect() call to
connect to the remote SMTP port */
}

 

TCP Client

A TCP client process establishes a connection to a server and uses the socket_read() and socket_ write() functions to transfer data. Typically, you use the following sequence of functions to set up the connection:

1   Create a TCP socket:

socket(AF_INET, SOCK_STREAM, 0);

2   Set up a sockaddr_in structure with the address you want to connect to by calling gethostbyname() and getservbyname().

3   Make a connection to the server with the connect() function.

4   Once connect() completes, the TCP connection is established and you can use socket_read() and socket_write() to transfer data.

Refer to the sample program TCPECHOCLIENT.C in the MultiNet Programmers' Kit examples directory. This program sends data to a server and displays what the server sends back.

 

TCP Server

A TCP server process binds a socket to a well-known port and listens on that port for connection attempts. When a connection arrives, the server processes it by transferring data using socket_read() and socket_write(). Typically, you use the following sequence of functions to set up a server:

1   Create a TCP socket:

socket(AF_INET, SOCK_STREAM, 0);

2   Use the getservbyname() function to get the port number of the service on which you want to listen for connections.

3   Set up a sockaddr_in structure with the port number and an Internet address of INADDR_ANY, and bind this address to the socket with the bind() function.

4   Use the listen() function to inform the MultiNet kernel that you are listening for connections on this socket. Then wait for a connection and accept it with accept().

5   Once accept() completes, the TCP connection is established and you can use socket_read() and socket_write() to transfer data. When you are done with the connection, you can close the channel returned by accept() and start a new accept() call on the original channel to wait for another connection.

Note!     When writing a TCP server that will run under the control of the MultiNet_Server process, you must assign a channel to SYS$INPUT before calling any of the VAX C I/O routines.

Refer to the sample program TCPECHOSERVER-STANDALONE.C in the MultiNet Programmers' Kit examples directory for an example of a server program that echoes data sent to it.

Another way to write a TCP server is to let the MULTINET_SERVER process do the work for you. The MULTINET_SERVER can perform all of the above steps, and when a connection request arrives, can use the OpenVMS system service $CREPRC to create a process running your program. Refer to the sample program TCPECHOSERVER.C in Appendix B and in the MultiNet Programmers' Kit examples directory for an example of how this is done.

 

UDP

A UDP program sends and receives packets to and from a remote port using the send() or sendto() and recv() or recvfrom() functions. UDP is a connectionless transport protocol. It does not incur the overhead of creating and maintaining a connection between two sockets, but rather merely sends and receives datagrams. It is not a reliable transport, and does not provide guaranteed data delivery, packet ordering, or flow control.

Typically, you use the following sequence of functions in a UDP program:

1   Create a UDP socket:

socket(AF_INET, SOCK_DGRAM, 0);

2   Bind the socket to a local port number with the bind() function. Specify the sin_port field as 0 (zero) if you want MultiNet to choose an unused port number for you automatically (typical of a client), or specify the sin_port field as the UDP port number (typical of a server). The sin_addr field is usually specified as INADDR_ANY, which means that packets addressed to any of the host's Internet addresses are accepted.

3   Optionally, use connect() to specify the remote port and Internet address. If you do not use connect(), you must use sendto() to specify the remote address when you send packets, and recvfrom() to learn the address when you receive them.

4   Read and write packets to transfer data using the send() or sendto() and recv() or recvfrom() functions, respectively.

Note!     When writing a UDP server that will run under the control of the MultiNet_Server process, you must assign a channel to SYS$INPUT before calling any of the VAX C I/O routines.

Another way to write a UDP server is to let the MULTINET_SERVER process handle the work. The MULTINET_SERVER can perform all the above steps, and when a packet arrives on a UDP port, can use the OpenVMS system service $CREPRC to create a process running your program. Refer to the sample programs in the MultiNet Programmers' Kit examples directory for examples of UDP clients and servers.

 

BSD-Specific Tips

The following sections contain information specific to working with BSD code.

BSD Sockets Porting Note

When porting a program written for BSD sockets to MultiNet, observe the following guidelines:

  Change any #include statements to reference files with the same names in the
MULTINET_ ROOT:[MULTINET.INCLUDE...] directory areas.

  Implement your change in the source code using #ifdef statements to enable the use of MultiNet include files; you can then compile your software in a UNIX environment by selecting the other side of the #ifdef.

BSD 4.4 TCP/IP Future Compatibility Considerations

MultiNet supports both BSD 4.3 and BSD 4.4 format sockaddrs.

The BSD 4.4 format is:

struct sockaddr_in {
        u_char  sin_len;
        u_char  sin_family;
        u_char sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

 

The BSD 4.3  format of the sockaddr_in structure is:

struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

MultiNet will accept either format from customer applications. This affects applications that explicitly check the sin_family field for the value AF_INET. Applications can avoid incompatibilities by avoiding explicit references or checks of the sin_family field, or by assuming that it can be in either format. The INET device uses the IO$M_EXTEND modifier to specify that a BSD 4.4 sockaddr (or current format) is used when IO$M_EXTEND is not used on the function code, the old (BSD 4.3) format is used.  This provides compatibility with prior versions of MultiNet.

Support for the BSD 4.4 style sockaddr data structure is included in the BGDRIVER (UCX interface). If the IO$M_EXTEND modifier is set on any one of the following QIO operations, the sockaddr parameter passed in these operations is assumed to be in BSD 4.4 format.

  IO$_SETMODE/IO$_SETCHAR (socket, bind)

  IO$_ACCESS (connect, listen)

  IO$_SENSEMODE/IO$_SENSECHAR (getsockname, getpeername)

  IO$_READVBLK (recv_from, when P3 is specified for a UDP or raw IP message)

  IO$_WRITEVBLK (send_to, when P3 is specified for a UDP or raw IP message)

When the IO$M_EXTEND modifier is used in the creation of a socket via
IO$_SETMODE/IO$_SETCHAR (socket, bind), the setting is remembered for the lifetime of the socket and all sockaddr structures passed in are assumed to be in BSD 4.4 format. Refer to the HP TCP/IP Services for OpenVMS System Services and C Socket Programming manual for additional information.

Operations that return a sockaddr (READVBLK (recv_from) like accept, getsockname, and getpeername), return that sockaddr in BSD 4.4 format. Operations that accept a sockaddr (WRITEVBLK (send_to) like connect and bind) expect the address family value to be in the position it is in for the BSD 4.4 structure. When a CONNECT/BIND/ACCEPT operation is done for a TCP connection with the IO$V_EXTEND bit set, the setting is remembered for the duration of the connection and all specified sockaddr structures are expected to be in BSD 4.4 format, and operations returning a sockaddr will return it in BSD 4.4 format.

For IO$_ACCESS (connect) and IO$_SETMODE (bind), if the portion of the sockaddr structure that is used to specify the address family in BSD 4.4 format is non-zero, then the sockaddr structure is assumed to be in BSD 4.4 format.

 

TCP/IP Services (UCX) Compatibility

MultiNet supports programs written for HP's TCP/IP Services.  The C run time library will automatically use the compatible entry points in the UCX$IPC_SHR.EXE image included with MultiNet.  MultiNet supports the following IPv6 compatible routines:

getaddrinfo
freeaddrinfo
getnameinfo
       gai_strerror
inet_pton
inet_ntop