Network I/O is more complicated than the normal file I/O. The file I/O operations are on the same system, the file descriptor is sufficient for identifying a file. The network I/O operations require both the host and the foreign process information. Berkeley sockets supports the following communication protocols:
Socket Addresses
The socket address specifies the family of the communication facility
and the communication media. The Unix domain socket address structure
sockaddr_un
is defined in <sys/un.h>.
struct sockaddr_un{
short
sun_family;
/*AF_UNIX*/
char
sun_PATH[108]; /*path name */
};
The Internet socket address structure sockaddr_in is defined in <netinet/in.h>.
struct in_addr {
u_long
s_addr;
/*32-bit net id */
};
struct sockaddr_in {
short
sin_family;
/* AF_INET */
u_short
sin_port;
/* 16-bit port number */
struct in_addr sin_addr;
char
sin_zero[8];
/* unused */
};
socket System Call
The system call socket creates one end of the socket.
#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol) ;
The first parameter family specifies the communication protocol
used. It can be one of:
AF_UNIX Unix
AF_INET Internet
The second parameter type specifies the type of socket.
We use stream socket:
SOCK_STREAM
The third parameter protocol is usually set to zero. The
socket system call returns a small integer called socket descriptor which
is similar to file descriptor which can be used in other system calls.
For example,
int
sockfd;
sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
bind System Call
The system call bind associates an address to a socket descriptor created
by socket.
#include <sys/types.h>
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr *myaddr, int addrlen);
The second parameter myaddr specifies a pointer to a predefined
address of the socket. Its structure is a general address structure
so that the bind system call can be used by both Unix domain and Internet
domain sockets. The structure sockaddr is defined in <sys/socket.h>.
struct sockaddr {
u_short
sa_family;
/* address family; AF_xxx */
char
sa_data[14]; /* protocol specific
address */
};
Since Unix domain and Internet domain have different address structures
as shown earlier, type casting is needed.
#define
SERV_PATH "./serv.path"
struct sockaddr_un serv_addr;
int servlen;
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SERV_PATH);
servlen = strlen(serv_addr.sun_path) + sizeof(serv)_addr.sun_family);
bind(sockfd, (struct sockaddr *) &serv_addr, servlen);
Note that the BSD network system calls do not assume that the Unix
pathname in sun_path is terminated with a null type. We can initialize
the address by:
bzero((char *) &serv_addr, sizeof(serv_addr));
After the call bind, the system knows the name of the host socket.
In the Unix domain case, it is a path name.
In the Internet domain, the address structure has more fields than the
structure in the unix domain. The first field sin_family is
AF_INET.
The second field sin_port can be any integer greater than 5000.
Lower port numbers are reserved for specific services. For example,
21 is for FTP (see <netinet/in.h>).This port number should be
agreed by both server and clients. The third field in_addr
is
the 32-bit Internet address. For the server, the constant
INADDR_ANY
(defined
in <netinet/in.h>) can be used to tell the system that we will
accept a connection on any Internet interface for the system. Since
different architectures use different byte ordering (big endian and little
endian), the following four functions are used for conversions between
the network (Internet)and the host: htonl
(host to network long),
htons,
ntohl, ntohs. Here is a header file for both server and clients:
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#define SERV_PORT
5432
#define SERV_HOST_ADDR
"130.113.68.1"
In the server program, we have
struct sockaddr_in
serv_addr;
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons (SERV_PORT);
serv_addr.sin_addr.s_addr = htonl (INADDR_ANY);
bind (sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr));
listen System Call
The system call listen is used by a connection-oriented server
to get ready for accepting connection requests from a client:
int listen (int sockfd, int backlog);
The second parameter backlog specifies the number of requests
that can be queued by the system before the server executes the accept
system
call. This system call is usually used after bind and before
accept. For example,
listen ( sockfd, 5);
accept System Call
The system call accept is used by connection-oriented server
to set up an actual connection with a client process.
#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cli_addr, int *addrlen)
;
This system call returns a new socket descriptor. In a concurrent
server, after accept and fork, the child process closes the
original socket descriptor and uses the new socket descriptor so that the
parent process can accept more connections using the original socket descriptor.
for( ; ;) {
newsocketfd = accept (sockfd, ...);
if (fork() = 0) {
close (sockfd);
<do whatever using newsockfd>;
exit (0) ;
}
close (newsockfd);
}
This system call also returns the address of the client process through
its second parameter cli_addr and the length of the address through
its third parameter addrlen. In the Unix domain, since the address
length depends on the length of the path name, the server usually sets
addrlen
to the size of the address structure. When accept
returns,
addrlen has the actual length of the path name. For example,
struct sockaddr_un cli_addr;
int clilen;
clilen = sizeof (cli_addr);
newsockfd = accept (sockfd, (struct sockaddr * ) &cli_addr, &clilen);
After accept, the system know the client process.
connect System Call
The system call connect is used by a client to establish a connection
with the server.
#include <sys/types.h>
#include <sys/socket.h>
int connect (int sockfd, struct sockaddr *servaddr, int addrlen);
This system call is similar to accept. The server address
pointed to by servaddr and its length addrlen should be known.
A client does not have to bind a local address to the socket descriptor
before connect.
#define
SERV_PATH
"./serv.path"
struct sockaddr_un serv_addr;
int servlen;
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SERV_PATH);
servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
connect(sockfd, (struct sockaddr *) &serv_addr, servlen);
In the Internet domain, the client includes the header file shown in
the bind System Call section and uses
unsigned long inet_addr(char *ptr);
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT):
serv_addr.sin_addr.s_addr = inet_addr (SERV_HOST_ADDR);
connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
Where function inet_addr converts a string in dotted-decimal
notation (d.g. 130.113.68.1) to a 32-bit Internet address.
Read and Write on a Stream Socket
A read or write system call on a stream socket, different from file
I/O, might input or output fewer bytes than requested. It is the
programmer's responsibility to ensure the actual number of bytes read or
written on the socket.
/* read n bytes from a socket descriptor */
int readsock(sockfd, buf, nbytes)
register int
sockfd;
register char
*buf;
register int
nbytes;
{
int nleft, nread;
nleft = nbytes;
while (nleft > 0) {
if ((nread = read(sockfd, buf, nleft)) < 0)
return(nread); /* error, nread < 0 */
else if (nread == 0)
break; /* EOF */
/* nread > 0. update nleft and buf pointer */
nleft - = nread;
but + = nread;
} /* while */
return(nbytes - nleft);
} /* readsock() */