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:



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.









Server


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.






Server


socket()



bind()



recvfrom() Client


socket()

blocks until connection

from client

bind()

data (request) sendto()



process request



sendto() data(reply) recvfrom()


A UDP connectionless transfer


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.


Addresses


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.


  1. I would like you to compile and run the code on your Linux machine, if possible, or on a CEMS UNIX machine.


  1. 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.


  1. 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 8 of 8 27/01/03