Programmer's Guide BSD Sockets Programming interface

INTRODUCTION

This section describes the NetCon implementation of the Berkeley 4.3BSD sockets mechanism. This implementation has been designed to support NOVELL's NetWare protocols in the UNIX internetworking environment. This sockets programming interface provides a framework for interprocess communications between UNIX, DOS and NetWare.

A client process, such as a user application system, usually needs to communicate with a server process to perform its functions. UNIX internetworking also provides a more flexible and powerful independent subsystem especially designed to support IPC in a distributed environment. This subsystem is called the "sockets interface," or simply "sockets." The UNIX internetworking sockets interface comprises the programming interface and the basis for IPC both within a host and across a network between dissimilar DOS, UNIX and NetWare systems.

The Netcon API software distribution contains the socket subsystem protocol that is necessary to support UNIX, DOS and NetWare interprocess communication, networking system calls and the library subroutines.

The NetCon protocol drivers implement the socket abstraction and NetWare compatible protocols. Programmers can write their own distributed programs using interprocess communication by linking their programs with the facilities in the library libsocket.a. The library routines call the kernel directly. The calling programs need not be written in C; they can be written in any language.

The library, libsocket.a, contains system calls and library routines. The calls are linked to the actual system call primitives in the kernel. The system calls perform basic functions for an application system.

SOCKETS

A socket is a software entity that provides the basic building block for interprocess communications. Sockets allow processes to rendezvous in a UNIX name space through which they exchange data. A socket is an endpoint of communication between processes. For IPX addresses, a fully named pair of sockets uniquely identify a connection between two communicating sides:

<<network.node.port> <network.node.port>>

network is the four-byte network address, node is the six-byte card address, and port is two bytes identifying the program or process. The socket on the left is the local socket and the socket on the right is the remote or foreign socket.

Socket Types

A socket has a type and one or more associated processes. Sockets are typed by the communications properties visible to the programmer. Usually a socket type is associated with the particular protocol which the socket supports. Processes usually communicate between sockets of the same type. Generally three types of sockets are available to the programmer. These are:

stream socket - SPX

datagram socket - IPX

raw socket NOT USED

Datagram Sockets

A datagram socket (SOCK_DGRAM) supports bidirectional flow of data in the datagram model of the network level protocol. Record boundaries are preserved. The receiving process must perform resequencing, elimination of duplicates, and reliability assurance. The datagram socket can be used in applications where reliability of an individual packet is not essential, for example, in broadcasting messages for the purpose of updating a status table.

SYSTEM CALLS

System calls are used to perform interprocess communications primarily by manipulating sockets. The linker editor, Id(CP), searches these functions when the -lsocket option is specified. The calls directly invoke UNIX system primitives in the kernel.

The following table gives information about the other IPX system calls that are available.

UNIX Networking System calls

Call Description

accept(SSC) accept a connection on a socket

bind(SSC) bind a name to a socket

connect(SSC) initiate a connection on a socket

getpeername(SSC) get name of connected peer

getsockname(SSC) get socket name

getsockopt(SSC) get options on sockets

setsockopt(SSC) get options on sockets

listen(SSC) listen for connections on a socket

recv(SSC) receive a message from a socket

recvfrom(SSC) receive a message from a socket

send(SSC) send a message to a socket

sendto(SSC) send a message to a socket

shutdown(SSC) shut down part of a full-duplex connection

socket(SSC) create an endpoint for communication

select(SLIB) await data or event on a socket

Error Returns

All the system calls return -1 in case of error. The error number is put in the variable errno. Error numbers are defined in sys/errno.h.

Model Usage

In a model exchange between a calling process and a serving process, the client is the active process and the server is the passive process. The client and the server issue different types of socket calls that are appropriate to their roles. Some of the system calls that can be used are paired and arranged in logical order as follows:

Networking System Calls

Serving Process Client Process

socket() socket()

bind() bind()

listen()

accept() connect()

read() write()

write() read()

close() close()

Creating a Socket

To create a socket, use the socket(SSC) system call:

s = socket(domain, type, protocol);

Where

domain In the NetCon implementation, the domain is always AF_NS (address Xerox network system). The manifest constants are named AF_whatever because they indicate the "address family" to use in interpreting names.

type In the NetCon implementation, type is always SOCK_DGRAM

The protocol is unspecified (a value of 0).

The system returns a small integer descriptor, or handle, to use in later system calls which operate on sockets. This is equivalent to a file descriptor. See open(S).

example:

s = socket (AF_NS, SOCK_DGRAM, 0);

Socket Creation Errors

EPROTONOSUPPORT The protocol type or the specified protocol is not supported within this domain.

EMFILE The per-process descriptor table is full.

ENFILE The system file table is full.

EACCESS Permission to create a socket of the specified type and/or protocol is denied.

ENOSR Insufficient buffer space is available. The socket cannot be created until sufficient resources are freed.

Binding Socket Names

A socket is created without an IPX address also referred to as the sockets name, but, to be used, it must be given a name. The bind call is used to assign a name to a socket on the local side or a connection, for example:

bind(s, name, namelen);

The argument s in the line above is the socket descriptor returned from the socket(SSC) call. (See "Creating a Socket," above).

The argument name is the IPX address for the socket. the name contains an network address card address and port number, usually formatted in a structure called sockaddr_ns. The argument namelen is the length of the name.

You do not have to specify an address unless you want a certain one. To bind an IPX address, the call is:

#include <sys/types.h>
#include <netinet/in.h>
/* . . . */
struct sockaddr_ns sin;
/* . . . */
sin.sns_addr.x_port = htons(NSPORT_NCP);
bind(s, &sin, sizeof (sin));

If you set sin to 0, the system binds to the server for you and returns the address it used.

Making a Connection

Once a process has bound a local socket, the process can rendezvous with an unrelated foreign process. Usually the rendezvous takes the form of a client server relationship. The client completes the other side of the socket pair when it requests services from the server, initiating a connection by issuing a connect call:

struct sockaddr_ns server;
connect(s, &server, sizeof (server));

If the client process's socket is unbound when it issues the connect call, the system automatically selects a name (address) and binds the socket. If the socket is successfully associated with the server, data transfer can begin. If not, an error is returned. Only the active process uses connect.

The Server

A completed connection is identified by a unique pair of sockets, each socket being an endpoint associated with one of the reciprocating processes. For the server to receive a client's connection, the server must issue two system calls after binding its socket. The first is to indicate a willingness to listen for incoming connection requests. The second is to accept the client's connect. To "listen" is to passively wait to accept a connection from a client process:

listen(s, 5);

The second parameter, 5, indicates the maximum number of outstanding connections which can be queued awaiting the acceptance of the server. This limit prevents processes from hogging system resources. Should a connection be requested while the queue is full, the server does not refuse the connection, but ignores the messages which comprise the request. This forces the client to retry the connection request and gives the server time to make room in its queue.

Had the connection been returned with the ECONNREFUSED error, the client would be unable to tell if the server was up or not. As it is, now it is still possible to get the ETIMEDOUT error back, though this is unlikely. The backlog figure supplied with the listen call is limited by the system to a maximum of five pending connections on any one queue. This avoids the problem of processes hogging system resources by setting an infinite backlog and then ignoring all connection requests.

Accepting a Connection

With the socket marked as listening, the server can now accept a connection:

fromlen = sizeof (from);
snew = accept(s, &from, &fromlen);

The server returns a new descriptor to the client on receipt of a connection (along with a new socket). If the server wishes to find out who its client is, it may supply a buffer for the client socket's name. The fromlen argument is a value-result parameter initialized by the server to indicate how much space is associated with (the client). It is modified on return to reflect the true size of the name. Only a passive process uses accept.

The call to accept will not return until a connection is available or the system call is interrupted by a signal to the process. This is called blocking. Further, there is no way for a process to indicate it will accept connections from only a specific individual, or individuals. It is up to the user process to consider who the connection is from and close down the connection if it does not wish to speak to the process. If the server process wants to accept connections on more than one socket, or not block on the accept call.

Servers often bind multiple sockets. When a server accepts a connection, it usually spins off (forks) a process which is the connected socket. The parent then goes back to listening on the same local socket.

Connection Errors

Of the many errors that can be returned when a connection fails, the following are the most common:

ETIMEDOUT After failing to establish a connection during a period of time, the system decided there was no point in retrying any more. The cause for this error is usually that the remote host is down or that problems in the network resulted in transmission being lost.

ECONNREFUSED The host refused service for some reason. This error is usually caused by a server process not being present at the requested host.

ENETDOWN

EHOSTDOWN Status information received by the client host from the underlying communication services indicates the net or the remote host is down.

ENETUNREACH

EHOSTUNREACH These operational errors can occur either because the network or host is unknown (no route to the host or network is present) or because status information to that effect has been delivered to the client host by the underlying communication services.

Transferring Data

When a connection is established, data flow can begin using a number of possible calls. If the peer entity at each end of a connection is anchored (that is, there is a connection), a user can send or receive a message, without specifying the peer, by using read and write:

write(s, buf, sizeof (buf));
read(s, buf, sizeof (buf));

The calls send and recv are virtually identical to read and write, except that a flags argument is added.

send(s, buf, sizeof (buf), flags);
recv(s, buf, sizeof (buf), flags);

One or more of the flags can be specified as a nonzero value as follows:

MSG_OOB Send/receive out of band data. Out of band data is specific to stream sockets.

MSG_PREVIEW Look at data without reading. When specified in a recv call, any data present is returned to the user but treated as though still "unread." The next read or recv call applied to the socket will return the data previously previewed.

MSG_DONTROUTE Send data without routing packets. (Used only by the routing table management process.)

Discarding Sockets

If a socket is no longer of use, the process can discard it by applying a close to the descriptor, as in the following example:

close(s);

If data is associated with a socket which promises reliable delivery (a stream socket), the system will continue to attempt to transfer the data. However, after a fairly long period of time, if the data is still undelivered, it will be discarded. If a user process wishes to abort any pending data, it can apply a shutdown on the socket prior to closing it. The shutdown command causes any data queued to be immediately discarded. The call format is:

shutdown(s,how);

where how is one of the following:

0 if the user no longer wishes to read data

1 if no more data will be sent

2 if no data is to be sent or received.

Introduction

IPX datagram sockets provide only connectionless interactions. When using datagram sockets, the programmer does not have to issue a connect call before sending. To be informed of network level errors, however, it is useful to issue a connect call. A datagram socket provides a symmetric interface to data exchange. Datagram processes are still likely to be client and server, but there is no requirement for connection establishment. Each message includes the destination address.

Using Connect on a Datagram Socket

Datagram sockets can also use connect to associate a socket with a specific address. In this case, any data sent on the socket is automatically addressed to the connected peer, and only data received from that peer will be delivered to the user.

Only one connected address is permitted for each socket (that is, no multicasting). Connect requests return immediately; the system merely records the peer's address, as compared to a stream socket where a connect request initiates establishment of an end to end connection. Other of the less important details of datagram sockets are described in the chapter "IPC Programming Techniques."

If connect is used with a datagram socket, then read, write, send and recv can be used to transfer data.

Sending from Datagram Sockets

Datagram sockets are created and name bound exactly as are stream sockets but to send data from a datagram socket, the process uses the sendto primitive:

sendto(s, buf, buflen, flags, &to, tolen);

The parameters are the same as those described for send, above, except the to and tolen values are used to indicate the intended recipient of the message.

When using an unreliable datagram interface, it is unlikely any errors will be reported to the sender. If information at the sending node indicates that the message cannot be delivered (for example, when a network is unreachable), the call returns -1, and the global value errno will contain an error number.

Receiving on Datagram Sockets

To receive data on an unconnected datagram socket, use recvfrom, as in the following example:

recvfrom(s, buf, buflen, flags, &from, &fromlen);

The parameters are as described above. Note that fromlen is handled in the value-result manner described in the section "Accepting a Connection," in the earlier chapter "Creating a Socket."

Input/Output Multiplexing

An application system can multiplex I/O requests among multiple sockets by using select:

select(nfds, &readfds, &writefds, &exceptfds, &timeout);

The select command takes three bit-masks as arguments, one for each of the following:

the set of file descriptors for which the caller wishes to be able to read data on

those descriptors to which data is to be written

to indicate which exceptional conditions are pending

Bit masks are created by or-ing bits of the form "1 << fd". That is, a descriptor fd is selected if a 1 is present in the fd'th bit of the mask. The parameter nfds specifies the range of file descriptors (that is, one plus the value of the largest descriptor) specified in a mask.

A timeout value may be specified if the selection is not to last more than a predetermined period of time. If timeout is set to 0, the selection takes the form of a poll, returning immediately. If the last parameter is a null pointer, the selection will block indefinitely. (A return takes place only when a descriptor is selectable, or when a signal is received by the caller, interrupting the system call.) The select command normally returns the number of file descriptors selected. If the select call returns due to the timeout expiring, then a value of -1 is returned along with the error number EINTR. The select call provides a synchronous multiplexing scheme.