sio: SocketIO Library
Author: Mike Gleason

# SocketIO Library Documentation

- Introduction
- Installation
- Function Reference
- Sample Code

The purpose of the SocketIO Library (sio for short) is to provide a safe and consistent wrapper interface to a BSD Sockets implementation. The library also takes steps to protect against a few common pitfalls that plague poorly written BSD Sockets programs. Specifically, it was designed to do the following:

- Take care of reading and writing the full operation.

  For example, a network `write()` for 50 bytes may only write 30 bytes; Sio would continue writing until all 50 bytes have been sent.

- Allow operations that would normally block can be set to timeout after a customizable period of time.

- Catch the SIGPIPE signal which would cause an unexpecting process to exit.

  A frequent source of problems for beginning programmers is the situation where their process is suddenly no longer running because a write to a socket caused a broken pipe. This problem can be difficult to diagnose if the process is running as a daemon process in the background.

- Resume operation when system calls are interrupted (operations errout with `EINTR`).

- Cater to the internet socket interface (i.e. `struct sockaddr_in`).

- Simplified interface to name service.

  Library routines can take a textual address specification instead of you having to prepare a `struct sockaddr_in`.

The library was written by Mike Gleason. The first incarnation dates back to some time in 1992. The library may be used and distributed freely, as long as you give due credit.

### Introduction

The reader is assumed to be familiar with BSD Sockets. An excellent source of information is W. Richard Stevens' UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI, Prentice Hall, 1998, ISBN 0-13-490012-X.

#### Connection modes

A communications exchange between two end-points can be connection-oriented or connectionless. An connection-oriented exchange is characterized by a connection establishment, a series of messages, and a disconnection. A connectionless exchange is characterized by one or more independent messages. An analogy to a connection-oriented exchange would be a telephone call where one party establishes a connection to another, and a conversation ensues. A connectionless exchange analogy would be a letter sent via the postal service -- even if a writer sends two letters to the same recipient, each letter is transported independently.

#### Message modes

For the sio library, all connection-oriented exchanges are data streams using the TCP/IP protocol. After connection establishment, the conversation consists of one long sequence of data bytes, which is known as a stream. Therefore there is no concept of a record boundary associated with a stream connection at the transport level, although often there is a record associated over the stream at the application level. The important thing here is to realize that there is no flow control associated with the stream, so although the data bytes are guaranteed to arrive in order, there is no guarantee that messages will be read in the same size blocks as they were originally written.

For connectionless exchanges, sio uses the UDP/IP protocol's datagram messages. There is an implicit definition of a record boundary, because each message is treated as a record. So, three writes results in three separate messages.

For example, let's say a sender writes three 50-byte messages. If a receiver does a 20-byte read followed by 100-byte read followed by a 50-byte read, the receiver would get the first 20 bytes of the first message, 50 of 50 bytes of the second message, and 50 of 50 bytes of the third message. It's important to understand that in the first read that since UDP datagrams are message oriented that the remaining 30 bytes of the first message are lost, and that although the second read is for 100 bytes, the read immediately returns as finished even though only 50 bytes were actually read.

With UDP datagrams, there are also other important implications that affect message ordering, duplication, and overall reliability.

#### Creating and disposing sockets

A socket is an I/O descriptor that is associated with one side of a communications exchange. A socket must be either a client or a server, although after communications is established the difference is arbitrary. A server socket is one that waits for contact to be initiated by a client on a mutually agreed upon address and port number. A client socket initiates the first communication by sending to an existing server socket. Client and server sockets may of course be on different machines, and different networks altogether.

A stream server socket is created using `SNewStreamServer()`, which returns a socket file descriptor ready to accept new client connections. After a socket descriptor is obtained, your server program can then use `SAccept()` to establish a connection with a client.

A stream client socket is created using `SNewStreamClient()`. After a socket descriptor is obtained, your client program can use `SConnectByName()` or `SConnect()` to initiate a connection to a server.

A datagram server socket is created using `SNewDatagramServer()`. After a socket descriptor is obtained, it is ready to receive messages from clients using `SRecvfrom()` and reply back with `SSendto()`.

A datagram client socket is created using `SNewDatagramClient()`. After a socket descriptor is obtained, it is ready to communicate with servers using `SSendto()` and `SRecvfrom()`.

All socket descriptors are disposed of with `SClose()` when communication is finished.

#### Socket I/O

A stream connection uses `SRead()` to receive data from the stream and `SWrite()` to send data. A datagram socket should use `SRecvfrom()` to read a message and `SSendtoByName()` or `SSendto()` to send a message. Since each datagram communication is independent, these routines use an address specifier along with each call.

### Installation

First, unpack the archive. If the package arrived as a '.tar.gz' or '.tgz' file, you need to run gunzip, and then tar to extract the source files. You can do that in one shot by doing "`gunzip -c sio.tgz | tar xvf -`". If the package you have is a '.tar' file you can use "`tar xvf sio.tar`". If the package you have is a '.zip' file you can use "`unzip sio.zip`" or "`pkunzip sio.zip`".

Now go to the "`sio`" directory you just made. There is a script you must run which will checks your system for certain features, so that the library can be compiled on a variety of UNIX systems. Run this script by typing "`sh ./configure`" in that directory. After that, you can look at the `Makefile` it made if you like, and then you run "`make`" to create the "`libsio.a`" library file.

Finally, install the library and headers. You can manually copy the files, or you can run "`make install`" to copy the files for you. If you choose to "`make install`" you may want to edit the `Makefile` if you do not want to install to the `/usr/local` tree.

### Function Reference

AddrStrToAddr | SConnectByName | SSendtoByName
AddrToAddrStr | SListen | SWrite
DisposeSReadlineInfo | SNewDatagramClient | SelectR
FlushSReadlineInfo | SNewDatagramServer | SelectSetAdd
GetSocketBufSize | SNewStreamClient | SelectSetInit
GetSocketLinger | SNewStreamServer | SelectSetRemove
GetSocketNagleAlgorithm | SRead | SelectW
InitSReadlineInfo | SReadline | Sendto
SAcceptA | SRecv | SendtoByName
SAcceptS | SRecvfrom | SetSocketBufSize
SBind | SRecvmsg | SetSocketLinger
SClose | SSend | SetSocketNagleAlgorithm
SConnect | SSendto

##### AddrStrToAddr

`int` `AddrStrToAddr(const char * const s, struct sockaddr_in * const sa, const int defaultPort);`

This takes a textual internet address specification and converts it to a `struct sockaddr_in`. An address string may be any of the following forms:

- `hostname:port`
- `service://hostname`
- `service://hostname:port`
- `service://hostname[/more/junk/which/is/ignored/]`
- `port@hostname`

Additionally, the hostname may be a name or an IP address string (i.e. `192.168.1.13`).

If the string contains a hostname but a port number does not appear to be present and the defaultPort parameter is greater than zero, then the address structure will use defaultPort as the port number.

The function returns a negative number if the string could not be converted, such as the case where the hostname was not found in the name service. Name service is not used unless there is a name instead of an IP address, so if you don't want to use name service, only use IP addresses.

Example:

    struct sockaddr_in addr;
    int result;

    result = AddrStrToAddr("ftp.probe.net", &addr, 21);
    result = AddrStrToAddr("ftp.probe.net:21", &addr, 21);
    result = AddrStrToAddr("206.28.166.234:21", &addr, 21);
    result = AddrStrToAddr("ftp://ftp.probe.net", &addr, 0);
    result = AddrStrToAddr("21@ftp.probe.net", &addr, 0);

##### AddrToAddrStr

`char *AddrToAddrStr(char *const dst, size_t dsize, struct sockaddr_in * const saddrp, int dns, const char *fmt);`

This function takes a `struct sockaddr_in` and converts into a readable internet textual address.

The dns parameter specifies if name service should be used to lookup the symbolic hostname from the raw address. If zero, it expresses the raw IP address in the standard dotted-decimal format, like 192.168.1.13.

The fmt parameter is a magic cookie string, with the following cookies:

- `%h` = hostname
- `%p` = port number
- `%s` = service name (based off of the port number)

This lets you print the address in just about any way you like. The dst parameter is the string to write the results to, and is always null-terminated. The dst parameter is also returned as the result of the function.

Example:

    char str[128];

    fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%h"), stdout);
    fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%h:%p"), stdout);
    fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%s://%h"), stdout);

##### DisposeSReadlineInfo

`void DisposeSReadlineInfo(SReadlineInfo *srl);`

This function is used to dispose of a `SReadlineInfo` structure that was created using `InitSReadlineInfo()`. You're required to use this to free dynamically allocated buffers it creates unless you specified that you wanted to use your own buffer, in which case it's optional (but recommended for clarity).

##### FlushSReadlineInfo

`void FlushSReadlineInfo(SReadlineInfo *srl);`

This rarely used function is used to reset and clear the buffer used by `SReadline()`. It acts similarly to a `fflush(stdin)`.

##### GetSocketBufSize

`int GetSocketBufSize(int sockfd, size_t *const rsize, size_t *const ssize);`

This utility routine returns the size of the socket's internal buffers, as maintained by the kernel (or socket library). It does this by calling `getsockopt()` with the `SO_RCVBUF` and `SO_SNDBUF` options, if they are defined. In the event they aren't defined, it returns a negative result code.

Example:

    size_t rsize, ssize;

    if (GetSocketBufSize(sockfd, &rsize, &ssize) == 0) ...

##### GetSocketLinger

`int GetSocketLinger(const int fd, int *const lingertime);`

This utility routine returns whether linger mode has been turned on, and also sets the amount of time in the lingertime parameter.

Example:

    int lingertime;

    if (GetSocketLinger(sockfd, &lingtime) > 0)
        /* linger is on... */ ...;

##### GetSocketNagleAlgorithm

`int GetSocketNagleAlgorithm(const int fd);`

This utility routine returns whether the Nagle Algorithm is in effect (`TCP_NODELAY` mode not on).

##### InitSReadlineInfo

`int InitSReadlineInfo(SReadlineInfo *srl, int sockfd, char *buf, size_t bsize, int tlen);`

This function is used to prepare a `SReadlineInfo` structure for use with the `SReadline()` function. The sockfd parameter specifies the socket that will be used for line buffering. The sio library does not open or close this socket.

The buf parameter is the area of memory to use for buffering; it should be large enough to hold several lines of data. If buf is NULL, the function will `malloc()` one of bsize bytes, otherwise it is assumed that buf is maintained by you and is of size bsize bytes. If you let sio `malloc()` this buffer, you must also use `DisposeSReadlineInfo()` to free it when you're finished with it.

The tlen parameter is the timeout value (in seconds) to use for each call of `SReadline()`.

Example:

    SReadlineInfo sri;
    char localbuf[2048];

    if (InitSReadlineInfo(&sri, sockfd, NULL, 512, 10) < 0)
        perror("malloc 512 bytes failed");
    ...or...
    (void) InitSReadlineInfo(&sri, sockfd, localbuf, sizeof(localbuf), 10);

##### SAccept

`int SAccept(int sfd, struct sockaddr_in *const addr, int tlen);`

This is does an `accept()`, with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If no new connection is accepted, `kTimeoutErr` is returned, otherwise a new socket or (-1) is returned. The socket is still usable if a timeout occurred, so you can call `SAccept()` again.

##### SBind

`int SBind(int sockfd, const int port, const int nTries, const int reuseFlag);`

This calls `bind()`, to the wildcard address with the specified port (not in network byte order). The nTries parameter tells how many attempts it tries before giving up (which is useful if other processes are grabbing the same port number). If the reuseFlag parameter is non-zero, `SBind()` tries to turn on the `SO_REUSEADDR` (and `SO_REUSEPORT`, if available) socket options before binding.

Normally you will not call this function directly, since `SNewStreamServer()` and `SNewDatagramServer()` do this for you.

##### SClose

`int SClose(int sfd, int tlen);`

This is `close()` with a timeout of tlen seconds. Normally you don't need to worry about `close()` blocking, but if you have turned on linger mode, `close()` could block. `SClose()` calls `close()` for up to tlen seconds, and if the timeout expires, it calls `shutdown()` on the socket.

##### SConnect

`int SConnect(int sfd, const struct sockaddr_in *const addr, int tlen);`

This is `connect()` with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If it returns (-1) or `kTimeoutErr`, the socket is no longer usable and must be closed.

##### SConnectByName

`int SConnectByName(int sfd, const char * const addrStr, const int tlen);`

This is `connect()` with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If it returns (-1) or `kTimeoutErr`, the socket is no longer usable and must be closed. The difference between `SConnect()` is that this function takes a textual address string instead of a `struct sockaddr_in`.

Example:

    if (SConnectByName(sockfd, "http://www.probe.net", 15) == 0) ...

##### SListen

`int SListen(int sfd, int backlog);`

This isn't too useful at present, since it just does `listen(sfd, backlog)`. And, you will not call this function directly, since `SNewStreamServer()` and `SNewDatagramServer()` do this for you.

##### SNewDatagramClient

`int SNewDatagramClient(void);`

This returns a new datagram socket, which is ready to send (and then receive) datagrams. This function is just `socket(AF_INET, SOCK_DGRAM, 0)`. If successful, it returns a non-negative socket descriptor.

Example:

    int sockfd;

    sockfd = SNewDatagramClient();

##### SNewDatagramServer

`int SNewDatagramServer(const int port, const int nTries, const int reuseFlag);`

This function creates a new socket and binds it to the specified port (not in network byte order) on the wildcard address. The nTries and reuseFlag are used when it calls `SBind()`. If successful, it returns a non-negative socket descriptor.

Example:

    int sockfd;

    sockfd = SNewDatagramServer(13, 3, 0);
    if (sockfd >= 0)
        /* ready to receive requests on the daytime port (13) */

##### SNewStreamClient

`int SNewStreamClient(void);`

This returns a new stream socket, which is ready to `SConnect()` to a server. This function is just `socket(AF_INET, SOCK_STREAM, 0)`. If successful, it returns a non-negative socket descriptor.

Example:

    int sockfd;

    sockfd = SNewStreamClient();

##### SNewStreamServer

`int SNewStreamServer(const int port, const int nTries, const int reuseFlag, int listenQueueSize);`

This function creates a new socket, binds it to the specified port (not in network byte order) on the wildcard address, and turns it on for listening. The nTries and reuseFlag are used when it calls `SBind()`. The listenQueueSize is for the `listen()` call. If successful, it returns a non-negative socket descriptor.

Example:

    int sockfd;

    sockfd = SNewStreamServer(80, 3, 0);
    if (sockfd >= 0)
        /* ready to accept HTTP connections */

##### SRead

`int SRead(int sfd, char *const buf0, size_t size, int tlen, int retry);`

This is `read()` on a socket descriptor, with a timeout of tlen seconds (tlen must be greater than zero). Like `read()`, it can return 0, (-1), or the number of bytes read, but in addition, it can also return `kTimeoutErr`.

If retry is set to `kFullBufferRequired`, `SRead()` does not return until size bytes has been read or EOF is encountered. This is useful if you expect fixed-length records, and it doesn't do much good until a complete record has been read. However, it is still possible that an EOF is encountered after some bytes have been read, and with retry set to `kFullBufferRequired`, you get EOF instead of that partial record. If you set retry to `kFullBufferRequiredExceptLast`, you get the partial record (and EOF on the next `SRead()`). Otherwise, if you should set retry to kFullBufferNotRequired and SRead() will return when there is some data read.

Example:

    int nread;
    char buf[256];

    while (1) {
        nread = SRead(sockfd, buf, sizeof(buf), 15, kFullBufferNotRequired);
        if (nread <= 0) {
            if (nread == 0)
                break;    /* okay, EOF */
            else if (nread == kTimeoutErr) {
                fprintf(stderr, "timed-out\n");
                break;
            } else {
                perror("read");
            }
        }
        (void) write(1, buf, nread);
    }

##### SReadline

`int SReadline(SReadlineInfo *srl, char *const linebuf, size_t linebufsize);`

It is often desirable to process data from sockets line by line, however this is cumbersome to do on a socket descriptor. The `SReadline()` function allows you to do this, so you can do one call of the function and get back a line of input. It accomplishes this much the same way the standard library's I/O routines do this, using a buffer. However, it is usually not possible to determine if the standard library takes the same special I/O measures on socket descriptors that sio does, so using the standard library function `fdopen()` with a socket descriptor may or may not work the way you want.

`SReadline()` needs to maintain state information for each call, so a data structure is required. You must first call `InitSReadlineInfo()` to initialize a `SReadlineInfo` structure. After that, you may call `SReadline()` repeatedly until it returns 0 to indicate EOF. The function returns the number of characters in the input line, including a newline (however, carriage return characters are omitted). You must call `DisposeSReadlineInfo()` if you chose to let `InitSReadlineInfo()` use `malloc()` to allocate your buffer.

Example:

    SReadlineInfo sri;
    char line[80];
    int nread;

    if (InitSReadlineInfo(&sri, sockfd, NULL, 512, 10) < 0) {
        perror("malloc");
        exit(1);
    }
    while (1) {
        nread = SReadline(&sri, line, sizeof(line));
        if (nread <= 0) {
            if (nread == kTimeoutErr)
                fprintf(stderr, "readline timed-out.\n");
            else if (nread < 0)
                perror("readline");
            break;
        }
        line[nread] = '\0';    /* omit newline */
        fprintf(stdout, "read [%s]\n", line);
    }
    DisposeSReadlineInfo(&sri);
    (void) SClose(sockfd, 3);

##### SRecv

`int SRecv(int sfd, char *const buf0, size_t size, int fl, int tlen, int retry);`

This is the corresponding wrapper for `recv()`, as `SRead()` is for `read()`. You will never need this function, unless you need the special receiving flags that `recv()` gives you.

##### SRecvfrom

`int SRecvfrom(int sfd, char *const buf, size_t size, int fl, struct sockaddr_in *const fromAddr, int tlen);`

This is `recvfrom()` with a timeout of tlen seconds (tlen must be greater than zero). Like `recvfrom()`, it can return 0, (-1), or the number of bytes read, but in addition, it can also return `kTimeoutErr`. Upon a timeout, the socket is still valid for additional I/O.

Example:

    int nread;
    char buf[80];
    struct sockaddr_in remoteClientAddr;

    nread = SRecvfrom(sockfd, buf, sizeof(buf), 0, &remoteClientAddr, 15);
    if (nread <= 0) {
        if (nread == kTimeoutErr)
            fprintf(stderr, "recvfrom timed-out.\n");
        else if (nread < 0)
            perror("recvfrom");
    }

##### SRecvmsg

`int SRecvmsg(int sfd, void *const msg, int fl, int tlen);`

This is the corresponding wrapper for `recvmsg()`, as `SRead()` is for `read()`.

##### SSend

`int SSend(int sfd, char *buf0, size_t size, int fl, int tlen);`

This is the corresponding wrapper for `send()`, as `SWrite()` is for `write()`. You will never need this function, unless you need the special receiving flags that `send()` gives you.

##### SSendto

`int SSendto(int sfd, const char *const buf, size_t size, int fl, const struct sockaddr_in *const toAddr, int tlen);`

This is `sendto()` with a timeout of tlen seconds (tlen must be greater than zero). Like `sendto()`, it can return 0, (-1), or the number of bytes sent, but in addition, it can also return `kTimeoutErr`. Upon a timeout, the socket is still valid for additional I/O.

Since `sendto()` rarely blocks (only if the outgoing queue is full), you probably do not want to use a timeout or bother with its associated overhead; therefore `Sendto()` would be a better choice.

##### SSendtoByName

`int SSendtoByName(int sfd, const char *const buf, size_t size, int fl, const char *const toAddrStr, int tlen);`

This is `SSendto()`, only you can use a textual internet address string instead of a `struct sockaddr_in`.

##### SWrite

`int SWrite(int sfd, const char *const buf0, size_t size, int tlen);`

This is `write()` on a network socket with a timeout of tlen seconds (tlen must be greater than zero). Like `write()`, it can return 0, (-1), or the number of bytes sent, but in addition, it can also return `kTimeoutErr`.

##### SelectR

`int SelectR(SelectSetPtr ssp, SelectSetPtr resultssp);`

This does a `select()` for reading with the file descriptors in the specified `SelectSet`. Using a `SelectSet` ensures that the first argument to select is always correct (the smallest correct value, for speed) and `SelectR()` does not destroy the original `fd_set` and `timeval` (it copies it to resultssp before using `select()`).

Example:

    SelectSet ss, selected;

    SelectSetInit(&ss, 10.0);
    SelectSetAdd(&ss, sockfd1);
    SelectSetAdd(&ss, sockfd2);
    rc = SelectR(&ss, &selected);
    if ((rc > 0) && (FD_ISSET(sockfd2, &selected.fds))) ...

##### SelectW

`int SelectW(SelectSetPtr ssp, SelectSetPtr resultssp);`

This does a `select()` for writing with the file descriptors in the specified `SelectSet`. Using a `SelectSet` ensures that the first argument to select is always correct (the smallest correct value, for speed) and `SelectW()` does not destroy the original `fd_set` and `timeval` (it copies it to resultssp before using `select()`).

Example:

    SelectSet ss, selected;

    SelectSetInit(&ss, 10.0);
    SelectSetAdd(&ss, sockfd1);
    SelectSetAdd(&ss, sockfd2);
    rc = SelectW(&ss, &selected);
    if ((rc > 0) && (FD_ISSET(sockfd2, &selected.fds))) ...

##### SelectSetAdd

`void SelectSetAdd(SelectSetPtr const ssp, const int fd);`

This adds a descriptor to the set to select on.

Example:

    SelectSet ss;

    SelectSetInit(&ss, 10.0);
    SelectSetAdd(&ss, sockfd);

##### SelectSetInit

`void SelectSetInit(SelectSetPtr const ssp, const double timeout);`

Before adding members to the `SelectSet` structure, it must be initialized with this function. The timeout parameter initializes the timeout to use for future `Selects`.

##### SelectSetRemove

`void SelectSetRemove(SelectSetPtr const ssp, const int fd);`

This removes a descriptor from the set to select on. You will need to do this just before you close a descriptor that was in the set.

##### Sendto

`int Sendto(int sfd, const char *const buf, size_t size, const struct sockaddr_in *const toAddr);`

This is a simple wrapper for `sendto()`, which only handles `EINTR` for you. It does not worry about `SIGPIPEs`.

##### SendtoByName

`int SendtoByName(int sfd, const char *const buf, size_t size, const char *const toAddrStr);`

This is a simple wrapper for `sendto()`, which only handles `EINTR` for you. In addition, you can use a textual internet address string instead of a `struct sockaddr_in`.

Example:

    if (SendtoByName(sockfd, msg, sizeof(msg), "elwood.probe.net:13") > 0) ...

##### SetSocketBufSize

`int SetSocketBufSize(int sockfd, size_t rsize, size_t ssize);`

This utility routine changes the size of the socket's internal buffers, as maintained by the kernel (or socket library). It does this by calling `setsockopt()` with the `SO_RCVBUF` and `SO_SNDBUF` options, if they are defined. In the event they aren't defined, it returns a negative result code. The operation is only performed if the size is greater than zero, so if you only wanted to change the receive buffer you could set rsize to greater than zero and ssize to 0.

##### SetSocketLinger

`int SetSocketLinger(const int fd, const int l_onoff, const int l_linger);`

This is an interface to the `SO_LINGER` socket option. The l_onoff parameter is a boolean specifying whether linger is on, and the l_linger parameter specifies the length of the linger time if enabled.

Example:

    if (SetSocketLinger(sockfd, 1, 90) == 0) ...

##### SetSocketNagleAlgorithm

`int SetSocketNagleAlgorithm(const int fd, const int onoff);`

This utility routine enables or disables the Nagle Algorithm (`TCP_NODELAY` mode is off or on). Generally you won't care about this, unless you're writing an interactive application like telnet, talk, or rlogin, where response time is more important than throughput.

Example:

    if (SetSocketNagleAlgorithm(sockfd, 0) == 0) ...

### Sample Code

Here is an example client and server that uses the sio library routines. The server simply takes data and uppercases any lower case data, and returns the result to the client. To try it on port number 5123, you could run "`ucase_s 5123`" in one window, and "`ucase_c localhost:5123`" in another.

#### Server:

    /* ucase_s.c */

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>

    #include "sio.h"

    static void
    ServeOneClient(int sockfd, struct sockaddr_in *cliAddr)
    {
    	char buf[32], cliAddrStr[64];
    	int nread, nwrote, i;

    	printf("subserver[%d]: started, connected to %s.\n", (int) getpid(),
    		AddrToAddrStr(cliAddrStr, sizeof(cliAddrStr), cliAddr, 1, "<%h:%p>")
    	);
    	for (;;) {
    		nread = SRead(sockfd, buf, sizeof(buf), 15, kFullBufferNotRequired);
    		if (nread == 0) {
    			break;
    		} else if (nread < 0) {
    			fprintf(stderr, "subserver[%d]: read error: %s\n",
    				(int) getpid(), strerror(errno));
    			break;
    		}
    		for (i=0; i<nread; i++)
    			if (islower(buf[i]))
    				buf[i] = toupper(buf[i]);
    		nwrote = SWrite(sockfd, buf, nread, 15);
    		if (nwrote < 0) {
    			fprintf(stderr, "subserver[%d]: write error: %s\n",
    				(int) getpid(), strerror(errno));
    			break;
    		}
    	}
    	(void) SClose(sockfd, 10);
    	printf("subserver[%d]: done.\n", (int) getpid());
    	exit(0);
    }	/* ServeOneClient */

    static void
    Server(int port)
    {
    	int sockfd, newsockfd;
    	struct sockaddr_in cliAddr;
    	int pid;

    	sockfd = SNewStreamServer(port, 3, kReUseAddrYes, 3);
    	if (sockfd < 0) {
    		perror("Server setup failed");
    		exit(1);
    	}

    	printf("server[%d]: started.\n", (int) getpid());
    	for(;;) {
    		while (waitpid(-1, NULL, WNOHANG) > 0) ;
    		newsockfd = SAccept(sockfd, &cliAddr, 5);
    		if (newsockfd < 0) {
    			if (newsockfd == kTimeoutErr)
    				printf("server[%d]: idle\n", (int) getpid());
    			else
    				fprintf(stderr, "server[%d]: accept error: %s\n",
    					(int) getpid(), strerror(errno));
    		} else if ((pid = fork()) < 0) {
    			fprintf(stderr, "server[%d]: fork error: %s\n",
    				(int) getpid(), strerror(errno));
    			exit(1);
    		} else if (pid == 0) {
    			ServeOneClient(newsockfd, &cliAddr);
    			exit(0);
    		} else {
    			/* Parent doesn't need it now. */
    			(void) close(newsockfd);
    		}
    	}
    }	/* Server */

    void
    main(int argc, char **argv)
    {
    	int port;

    	if (argc < 2) {
    		fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    		exit(2);
    	}
    	port = atoi(argv[1]);
    	Server(port);
    	exit(0);
    }	/* main */

#### Client:

    /* ucase_c.c */

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>

    #include "sio.h"

    static void
    Client(char *serverAddrStr)
    {
    	char buf[256];
    	int nread, nwrote, sockfd;

    	sockfd = SNewStreamClient();
    	if (sockfd < 0) {
    		fprintf(stderr, "client[%d]: socket error: %s\n",
    			(int) getpid(), strerror(errno));
    		exit(1);
    	}

    	if (SConnectByName(sockfd, serverAddrStr, 15) < 0) {
    		fprintf(stderr, "client[%d]: could not connect to <%s>: %s\n",
    			(int) getpid(), serverAddrStr, strerror(errno));
    		exit(1);
    	}

    	printf("client[%d]: connected to <%s>.\n", (int) getpid(), serverAddrStr);
    	for (buf[sizeof(buf) - 1] = '\0';;) {
    		printf("client[%d]: Enter message to send -> ", (int) getpid());
    		if (fgets(buf, sizeof(buf) - 1, stdin) == NULL)
    			break;
    		buf[strlen(buf) - 1] = '\0';	/* Delete newline. */
    		if (buf[0] == '\0')
    			continue;		/* Blank line. */

    		/* Send the request line to the server. */
    		nwrote = SWrite(sockfd, buf, strlen(buf), 15);
    		if (nwrote < 0) {
    			fprintf(stderr, "client[%d]: write error: %s\n",
    				(int) getpid(), strerror(errno));
    			break;
    		}

    		/* Wait for complete reply line */
    		nread = SRead(sockfd, buf, nwrote, 15, kFullBufferRequired);
    		if (nread == 0) {
    			fprintf(stderr, "client[%d]: no reply received (EOF).\n",
    				(int) getpid());
    			break;
    		} else if (nread < 0) {
    			fprintf(stderr, "client[%d]: read error: %s\n",
    				(int) getpid(), strerror(errno));
    			break;
    		}
    		buf[nread] = '\0';
    		fprintf(stdout, "client[%d]: received: %s\n",
    			(int) getpid(), buf);
    	}
    	(void) SClose(sockfd, 10);
    	printf("\nclient[%d]: done.\n", (int) getpid());
    	exit(0);
    }	/* Client */

    void
    main(int argc, char **argv)
    {
    	int port;

    	if (argc < 2) {
    		fprintf(stderr, "Usage: %s <host:port>\n", argv[0]);
    		exit(2);
    	}
    	Client(argv[1]);
    	exit(0);
    }	/* main */