Computer Networks and Operating Systems Craig Duffy
Title: Networks and Operating Systems Practical 16 Author: Craig Duffy 4/11/99
Module: Computer Networks and Operating Systems
Awards: CRTS, BSc Computing, Computer Science, Software Engineering
Prerequisites: Some C programming skills, knowledge of TCP/IP
This worksheet intends to give some background and details on networking programming under UNIX using the TCP/IP protocol with the Berkeley socket implementation. Both UDP and TCP socket connection will be discussed.
Introduction
It may help us understand network connections if we compare them with something that we are familiar with, file handling. File handling under the UNIX operating system has a number of features. Firstly there are a set of primitives that allow us to handle files: open, create, read, write, lseek and close. Secondly the system need to be told a number of things, where the file is, a pathname, and the mode or way in which it is to be opened, read, write, append modes.
With network communications it becomes a lot more complex. The following features need to be considered:
A network operation is often client-server, which is not symmetrical, i.e. who is client and who is server?
The type of network connection is important. Is it connection orientated or connectionless? This will affect the calls used and the nature of the connection.
Naming is far more complicated over a network. File systems on a single machine can use file identifiers without ever knowing the actual name of the file, with pipes for example. With a network connection the use of names and protocols becomes important.
Extra parameters are required to set up a connection. Addresses at both ends must be supplied, plus both local and foreign processes to deal with the data. Finally the protocol type needs to be specified.
Network communication needs to support multiple protocols, for example addresses will need to be represented in a large data type in order to deal with the possible addressing schemes on a network.
As different machines with differing architectures will be used then data will have to be stored in a way that does not lose data between transfers. These features are not directly addressed by sockets and will be looked at when we cover Remote Procedure Calls (RPC) and eXternal Data Representation (XDR).
Client-server architecture
Unlike file transfers, network transfers are non-symmetrical. One machine will take on the role of client and one that of server, and it is possible that these roles will change and co-exist. This means that the code and ordering of events is important in network communication and depends upon the role taken. Similarly the type of communication, connection orientated or connectionless, will have a bearing upon the actual code and ordering of calls.
Sockets
There are a number of APIs, Application Program Interfaces, for TCP/IP programming. The 2 most common are Berkeley sockets and System V Transport Layer Interface (TLI). We will look at the former.
Berkeley sockets were developed to support TCP/IP on the Berkeley UNIX system in the early 1980s. They provide support for a number of protocols – UNIX domain, Internet domain (TCP/IP) and Xerox NS domain. We will only look at the first of these. Below is a diagram of the steps, and in the boxes, the calls, that are required for a connection orientated TCP transfer.
socket()
bind()

listen( )

accept() Client

socket()
blocks
until connection
from client


connection
established connect()

read()
data (request) write()

process request

write()
data(reply) read()
A TCP connection orientated transfer
This diagram shows the ordering of events, first the server starts then the client connects to the server. The client uses a subset of the server’s commands.
The calls for a connectionless transfer are simpler.
socket()
bind()

recvfrom() Client

socket()
blocks
until connection
from
client
bind()
data (request) sendto()

process request

sendto() data(reply) recvfrom()
In the connectionless transfer the client does not establish a connection, but just send data and the server does not listen for connections, but just reads data.
As was pointed out earlier, both end of the communication require addresses, so that the data can get to the destination and the replies can be returned. The addresses may vary from protocol to protocol, so the socket system keeps the addresses in a structure in a header file - /usr/inlcude/sys/socket.h, on the UNIX system. This contains the following structure.
struct sockaddr {
u_short sa_family; /* address family AF_xxx value */
char sa_data[14]; /*up to 14 bytes of protocol-
specific address */
};
The 14 bytes are different for each addressing scheme used. For the Internet family (TCP/IP) they are:
struct in_addr{
u_long s_addr; /* 32 bit netid/hoostid */
};
struct sockaddr_in {
short sin_family; /*AF_INET */
u_short sin_port; /16 bit port number */
struct in_addr; /*32 bit netid/hostid */
char sin_zero[8]; /* unused */
};
This definition is in the netinet/in.h file. All of the header files require the sys/types.h file for the data type definitions, u_long u_short etc.
Socket calls
I will now detail the socket system calls and give details of their usage. After this section I will give some simple client-server code which should be tried and then modified later on.
Socket call
In order to do any network communication you must first create a socket which acts as a communication end point.
#include <sys/types.h>
#include <sys/socket.h>
int socket(int family, int type, int protocol);
Where family can be:
AF_UNIX
AF_INET
AF_NS
AF_IMPLINK
For each different protocol family. The socket type can be one of the following:
SOCK_STREAM stream socket
SOCK_DGRAM datagram socket
SOCK_RAW raw socket
SOCK_SEQPACKET sequenced packet socket
SOCK_RDM reliable delivery socket not implemented
For most applications protocol is set to 0 unless a specific protocol is required.
bind call
The bind call assigns a name to an opened but unamed socket.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddrr *myaddr, int addrlen);
The sockfd parameter is the value returned from the socket call. The myaddr parameter is a protocol specific address, as their size can vary from protocol family the size of the address is required.
Bind allows servers to register their addresses. Clients can register their addresses if they wish, connectionless clients must register their address so that the server knows who to reply to.
connect call
The connect call allows a created client socket to communicate with a remote server.
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
sockfd is returned by the socket call, the servaddr and addrlen pointers to a socket address and its length, as detailed above. Connect results in an actual connection being set up and parameters are exchanged by the client and server.
listen call
int listen(int sockfd, int backlog);
listen is normally executed after the socket and bind calls and before the accept calls. The backlog parameter specifies the number of outstanding calls that can be queued.
accept call
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *peer, int addrlen);
The accept system call blocks and waits for an actual connection and when it receives one it returns a new socket descriptor. The 2 values peer and addrlen are filled in from the connecting client, these values are then transferred to the new socket. If the server is a concurrent server, i.e. it will accept multiple clients concurrently, then it is normally the case that after a successful accept call a fork call is executed to service the request. The server will continue to listen for new requests.
send, sento, recv and recvfrom calls
These 4 calls correspond to the file read and write calls, but have a few extra arguments.
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, char *buff, int nbytes, int flags);
int sento(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen);
int recv(int sockfd, char *buff, int nbytes, int flags);
int recfrom(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen);
The first 3 arguments, sockfd, buff and nbytes are the same as in read and write calls.
Stream sockets can use the normal read and write calls and using a socket as their file descriptor. sendto and recvfrom are used for UDP connectionless datagram protocols. The flags argument is either 0 or formed by or’ing one of the following:
MSG_OOB send/receive out-of-band data
MSG_PEEK peek at incoming message
MSG_DONTROUE bypass routing
close call
The standard UNIX close call is used to close a socket:
int close(int fd);
The closed socket may have data still pending so that may have to be queued or to be sent or acknowledged.
Byte ordering routines
You will see in the example code attached some routiens to perform byte ordering for the system calls. These are:
#include <sys/types.h>
#include <netinet/in.h>
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
u_long ntonl(u_long netlong);
u_short ntohs(u_short netshort);
As the client and server code could, and probably will, run on machines with different architectures it is necessary to make sure that any differences in byte ordering are dealt with.
Byte operations
As with the above operations, car needs to be taken when dealing with data structures on various machines. These routines,
bcopy(char *src, char *dest, int nbytes);
bzero(char *dset, int nbytes);
int bcmp(char *ptrl, char *ptr2, int nbytes);
do portable copying, clearing and comparing operations on user defined data structures. In our examples they are used to handle the address structures for Internet addresses.
Example code – what you should do
The attached example code is a part of a very simple client server application to send and receive data. The client sends the data and the server echoes it back.
I would like you to compile and run the code on your Linux machine, if possible, or on a CEMS UNIX machine.
Once you have made it work, alter the code so that rather than echo back data, so that the client sends the name of a directory on the server machine. The server should respond with either an error message (no such directory, permission not allowed etc) or send back the directory listing.
Rework the code to change it into a UDP connectionless datagram program. This will require reworking the client and server start up code as well as the reading and writing routines.
All of the programs come with makefiles.
The code consists of a number of library routines, compiled and stored in libnet.a using the librarian ar. These routines read and write data from the stream sockets.
The main function str_echo is called by the server to do the simple echo command. The function str_cli send the data to the server.
The client and serve code both use the inet.h header file. This calls in all the other headers needed and sets up the addresses. The port numbers just need to be above 1023, preferably above 5000. The address will have to be changed for the address of the machine upon which the server is running.
Page