Short Introduction to Sockets
Network I/O is similar to file I/O, although network I/O requires not only a file descriptor sufficient for identifying a file, but also sufficient information for network communication.
Berkeley sockets support both UNIX domain sockets (on the same system) and Internet domain sockets, also called TCP/IP (transmission control protocol) or UDP/IP (user datagram protocol).
Socket Addresses
The socket address specifies the communication family. UNIX domain sockets are defined as sockaddr_un. Internet domain sockets are defined as sockaddr_in or sockaddr_in6 for IPv6.
struct sockaddr_in {
short          sin_family;    /* AF_INET */
u_short        sin_port;      /* port number */
struct in_addr sin_addr;      /* Internet address */
char           sin_zero[8];   /* unused */
};
Socket() System Call
The socket() system call creates one end of the socket.
int socket(int <family>, int <type>, int <protocol>);
The socket() system call returns the socket descriptor, a small integer that is similar to the file descriptor used in other system calls. For example:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int sockfd;
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
Bind() System Call
The bind() system call associates an address with the socket descriptor.
int bind(int sockfd, struct sockaddr *myaddr, int addrlen);
The third parameter is the length of the sockaddr structure, because it can vary.
In the sockaddr structure for IPv4 sockets, the first field specifies AF_INET. The second field sin_port can be any integer > 5000. Lower port numbers are reserved for specific services. The third field in_addr is the Internet address in dotted-quad notation. For the server, you can use the constant INADDR_ANY to tell the system to accept a connection on any Internet interface for the system. Conversion functions htons() and htonl() are for hardware independence. For example:
#define SERV_PORT 5432
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 listen() system call prepares a connection-oriented server to accept client connections.
int listen(int sockfd, struct <backlog>);
The second parameter specifies the number of requests that the system queues before it executes the accept() system call. Higher and lower values of <backlog> trade off high efficiency for low latency.
For example:
listen(sockfd, 5);
Accept() System Call
The accept() system call initiates communications between a connection-oriented server and the client.
int accept(int sockfd, struct sockaddr *cli_addr, int addrlen);
The second parameter is the client’s sockaddr address, to be filled in.
Generally programs call accept() inside an infinite loop, forking a new process for each accepted connection. After accept() returns with client address, the server is ready to accept data.
For example:
for( ; ; ) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr));
if (fork() = 0) {
close(sockfd);
/*
* read and write data over the network
* (code missing)
*/
exit (0);
}
close(newsockfd);
}
Connect() System Call
On the client, the connect() system call establishes a connection to the server.
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
The second parameter is the server’s sockaddr address, to be filled in.
This is similar to the accept() system call, except that the client does not have to bind a local address to the socket descriptor before calling connect(). The server address pointed to by srv_addr must exist.
For example:
#define SERV_PORT 5432
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));
Socket Read and Write
Sockets use the same read and write system calls as for file I/O.
Unlike file I/O, a read or write system call on a stream socket may result in fewer bytes than requested. It is the programmer's responsibility to account for varying number of bytes read or written on the socket.
For example:
nleft = nbytes;
while (nleft > 0) {
if ((nread = read(sockfd, buf, nleft)) < 0)
return(nread); /* error */
else if (nread == 0)
break; /* EOF */
/* nread > 0. update nleft and buf pointer */
nleft - = nread;
buf += nread;
}