You hear talk of "sockets" all the time, and perhaps you are wondering just what they are exactly. Well, they're this: a way to speak to other programs using standard Unix file descriptors. What?
Ok—you may have heard some Unix hacker state, "Jeez, everything in Unix is a file!" What that person may have been talking about is the fact that when Unix programs do any sort of I/O, they do it by reading or writing to a file descriptor. A file descriptor is simply an integer associated with an open file. But (and here's the catch), that file can be a network connection, a FIFO, a pipe, a terminal, a real on-the-disk file, or just about anything else. Everything in Unix is a file! So when you want to communicate with another program over the Internet you're gonna do it through a file descriptor, you'd better believe it. "Where do I get this file descriptor for network communication, Mr. Smarty-Pants?" is probably the last question on your mind right now, but I'm going to answer it anyway: You make a call to the socket() system routine. It returns the socket descriptor, and you communicate through it using the specialized send() and recv() (man send, man recv) socket calls.
"But, hey!" you might be exclaiming right about now. "If it's a file descriptor, why in the name of Neptune can't I just use the normal read() and write() calls to communicate through the socket?" The short answer is, "You can!" The longer answer is, "You can, but send() and recv() offer much greater control over your data transmission."
What next? How about this: there are all kinds of sockets. There are DARPA Internet addresses (Internet Sockets), path names on a local node (Unix Sockets), CCITT X.25 addresses (X.25 Sockets that you can safely ignore), and probably many others depending on which Unix flavor you run. This document deals only with the first: Internet Sockets.
Introduction
Sockets are a protocol independent method of creating a connection between processes. Sockets can be either
- connection based or connectionless: Is a connection established before communication or does each packet describe the destination?
- packet based or streams based: Are there message boundaries or is it one stream?
- reliable or unreliable. Can messages be lost, duplicated, reordered, or corrupted?
Socket characteristics
Socket are characterized by their domain, type and transport protocol. Common domains are:
- AF UNIX: address format is UNIX pathname.
- AF INET: address format is host and port number
Common types are:
virtual circuit: received in order transmitted and reliably
datagram: arbitrary order, unreliable
datagram: arbitrary order, unreliable
Each socket type has one or more protocols. Ex:
- TCP/IP (virtual circuits)
- UDP (datagram)
Use of sockets:
- Connection–based sockets communicate client-server: the server waits for a connection from the client
- Connectionless sockets are peer-to-peer: each process is symmetric.
Socket APIs
- socket: creates a socket of a given domain, type, protocol (buy a phone)
- bind: assigns a name to the socket (get a telephone number)
- listen: specifies the number of pending connections that can be queued for a server socket. (call waiting allowance)
- accept: server accepts a connection request from a client (answer phone)
- connect: client requests a connection request to a server (call)
- send, sendto: write to connection (speak)
- recv, recvfrom: read from connection (listen)
- shutdown: end the call
Connection-based communication
Server performs the following actions
- socket: create the socket
- bind: give the address of the socket on the server
- listen: specifies the maximum number of connection requests that can be pending for this process
- accept: establish the connection with a specific client
- send, recv: stream-based equivalents of read and write (repeated)
- shutdown: end reading or writing
- close: release kernel data structures
TCP client
Client performs the following actions
- socket: create the socket
- connect: connect to a server
- send, recv: (repeated)
- shutdown
- close
socket
#include <sys/types.h>
#include <sys/socket.h>
int socket( int domain ,int type ,int protocol );
Returns a file descriptor (called a socket ID) if successful, -1 otherwise. Note that the socket returns a socket descriptor which is the same as a file descriptor.
The domain is AF INET.
This function accepts three input arguments:
- The domain of the socket (the protocol family to use)
- The type of the socket required
- The specific protocol to use within the protocol family
Choosing a Socket Type
You already know that choosing a domain value for the socket(2) or socketpair(2) function chooses a protocol family to be used. For example, you know that
- PF_LOCAL (which is the same as PF_UNIX) indicates that a local UNIX socket protocol family is being specified.
- PF_INET indicates that the Internet family of protocols is used.
Consequently, you now have to learn about only two more input
arguments.
The type argument can be:
- SOCK STREAM: Establishes a virtual circuit for stream
- SOCK DGRAM: Establishes a datagram for communication
- SOCK SEQPACKET: Establishes a reliable, connection based,
- SOCK_RAW
two way communication with maximum message size. (This is not available on most machines.) protocol is usually zero, so that type defines the connection within domain.
Understanding the SOCK_STREAM Socket Type
The SOCK_STREAM socket type is used when you want to perform stream I/O with a remote socket. A stream in the socket sense is the same concept that applies to a UNIX pipe. Bytes written to one end of the pipe (or socket) are received at the other end as one continuous stream of bytes. There are no dividing lines or boundaries. There is no record length, block size, or concept of a packet at the receiving end. Whatever data is currently available at the receiving end is returned in the caller's buffer.
Example
An example to review might help illustrate the stream I/O concept. In this example, there is a local process on your host that has connected to a remote process on a remote host. The local host is going to send data to the remote host in two separate write(2) calls as follows:
- The local process writes 25 bytes of data to be sent to the remote process, by socket. The Linux kernel might or might not choose to buffer this data. Buffering helps improve the performance of the kernel and the network facilities.
- Another 30 bytes are written by the local process to be sent to the remote process.
- The remote process executes a function designed to receive data from the socket. The receiving buffer in this example allows up to 256 bytes to be read. The remote process receives the 55 bytes that were written in steps 1 and 2.
Note what has happened. The local process has performed two separate writes to the socket. These could be two different messages or two different data structures. Yet, the remote process received all of the written data as one combined unit of 55 bytes.
Another way to look at this example is that the local process might have had to create one message in two partial writes. The receiving end received the message as one combined unit.
At other times, depending on timing and buffer availability, the remote process might first get the original piece of 25 bytes (or perhaps even less). Then, on a successive receive function call, obtain the remaining 30 bytes. In short, a stream socket does not preserve any message boundary. It simply returns the data it has to the receiving application.
The receiving end cannot tell what the original message boundaries were. In our example, it cannot tell that the first write(2) was for 25 bytes and the second was for 30. All it can know is the data bytes that it received and that the total bytes sent was 55.
A stream socket has one other important property. Like a UNIX pipe, the bytes written to a stream socket are guaranteed to arrive at the other end in the exact same order in which they were written.
With protocols such as IP, in which packets can take different routes to their destination, it frequently happens that later packets arrive ahead of their earlier cousins. The SOCK_STREAM
socket ensures that your receiving application accepts data bytes in precisely the same sequence in which they were originally written.
Let's recap the properties of a SOCK_STREAM socket:
- No message boundaries are preserved. The receiving end cannot determine how many write(2)calls were used to send the received data. Nor can it determine where the write(2) calls began or ended in the stream of bytes received.
- The data bytes received are guaranteed to be in precisely the same order in which they were written.
- All data written is guaranteed to be received by the remote end without error. If a failure occurs, an error is reported after all reasonable attempts at recovery have been made. Any recovery attempts are automatic and are not directed by your application program.
The last point presented is a new one to this discussion. A stream socket implies that every reasonable effort will be made to deliver data written to one socket, to the socket at the other end. If this cannot be done, the error will be made known to the receiving end as well as the writing end. In this respect, SOCK_STREAM socket is a reliable data transport. This feature makes it a very popular socket type.
There is one more property of the SOCK_STREAM type of socket. It is
- The data is transported over a pair of connected sockets.In order to guarantee delivery of data, and to enforce byte ordering, the underlying protocols use a connected pair of sockets. For the moment, simply know that the SOCK_STREAM type implies that a connection must be established before communications can proceed.
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind( int sid, struct sockaddr *addrPtr ,int len );
Where
- sid: is the socket id
- addrPtr: is a pointer to the address family dependent address structure
- len: is the size of *addrPtr
Associates a socket id with an address to which other processes
can connect. In internet protocol the address is [ipNumber,
portNumber]
sockaddr
For the internet family:
struct sockaddr_in {
sa_family_t sin_family; /* Address Family */
uint16_t sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Pad bytes */
};
struct in_addr {
uint32_ t s_ addr; /* Internet address */
};
where
- The sin_family member occupies the same storage area that sa_family does in the generic socket definition. The value of sin_family is initialized to the value of AF_INET.
- The sin_port member defines the TCP/IP port number for the socket address. This value must be in network byte order (this will be elaborated upon later).
- The sin_addr member is defined as the structure in_addr, which holds the IP number in network byte order. If you
- Finally, the remainder of the structure is padded to 16 bytes by the me
listen
#include <sys/types.h>
#include <sys/socket.h>
int listen ( int sid, int size ) ;
Where size it the number of pending connection requests allowed
(typically limited by Unix kernels to 5). Returns the 0 on success, or -1 if failure.
accept
#include <sys/types.h>
#include <sys/socket.h>
int accept( int sid, struct sockaddr *addrPtr ,int *lenPtr );
Returns the socketId and address of client connecting to socket.
if lenPtr or addrPtr equal zero, no address structure is returned. lenPtr is the maximum size of address structure that can be called, returns the actual value. Waits for an incoming request, and when received creates a socket for it.
accept styles
There are basically three styles of using accept:
Iterating server: Only one socket is opened at a time. When the
processing on that connection is completed, the socket is closed, and next connection can be accepted.
Forking server: After an accept, a child process is forked off to
handle the connection. Variation: the child processes
are preforked and are passed the socketId.
Concurrent single server: use select to simultaneously wait on all open socketIds, and waking up the process only when new data arrives.
Pro and Con of Accept styles
- Iterating server is basically a low performance technique since only one connection is open at a time.
- Forking servers enable using multiple processors. But they make sharing state difficult, unless performed with threads. Threads, however present a very fragile programming environment.
- Concurrent single server: reduces context switches relative to forking processes and complexity relative to threads. But does not benefit from multiprocessors.
send
#include <sys/types.h>
#include <sys/socket.h>
int send( int sid, char *bufferPtr ,int len, int flags );
Send a message. Returns the number of bytes sent or -1 if failure. (Must be a bound socket). flag is either
- 0: default
- MSG OOB: Out-of-band high priority communication
recv
#include <sys/types.h>
#include <sys/socket.h>
int recv( int sid, char *bufferPtr ,int len, int flags );
Receive up to len bytes in bufferPtr. Returns the number of
bytes received or -1 on failure. flags can be either
- 0: default
- MSG OOB: out-of-bound message
- MSG PEEK: look at message without removing
shutdown
#include <sys/types.h>
#include <sys/socket.h>
int shutdown( int sid, int how );
Disables sending (how=1 or how=2) or receiving (how=0 or how=2). Returns -1 on failure. acts as a partial close.
connect
this is the first of the client calls
#include <sys/types.h>
#include <sys/socket.h>
int connect( int sid, struct sockaddr *addrPtr, int len );
Specifies the destination to form a connection with (addrPtr), and returns a 0 if successful, -1 otherwise.
Denoting Connections
Note that a connection is denoted by a 5-tuple:
- from IP
- from port
- protocol
- to IP
- to port
So that multiple connections can share the same IP and port.
Port usage
Note that the initiator of communications needs a fixed port to
target communications. This means that some ports must be reserved for these “well known” ports.
Port usage:
- 0-1023: These ports can only be binded to by root
- 1024-5000: well known ports
- 5001-64K-1: ephemeral ports
APIs for managing names and IP addresses
We next consider a number of auxiliary APIs:
- The hostent structure: describes IP, hostname pairs
- gethostbyname: hostent of a specified machine
- htons, htonl, ntohs, ntohl: byte ordering
- inet pton, inet ntop: conversion of IP numbers between presentation and strings
gethostname
#include <unistd.h>
int gethostname ( char *hostname , size_t nameLength )
Returns the hostname of the machine on which this command
executes (What host am i?). Returns -1 on failure, 0 on success.
MAXHOSTNAMELEN is defined in <sys/param.h>.
hostent structure
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
};
Error is return through h error which can be:
- HOST NOT FOUND
- TRY AGAIN
- NO RECOVERY
- NO DATA
Gethostbyname
Auxiliary functions
extern int h_errno;
struct hostent *gethostbyname(const char *name);
Translates a DNS name into a hostent.
Example:
struct hostent *hostEntity =
gethostbyname ("it.blr.ind.com");
memcpy ( socketAddr−>sin_addr,
hostEntity−>h_addr_list [0],
hostEntity−>h_length );
Network byte ordering
to understand the network byte order click here
Manipulating the IP Numbers
to understand the complete manipulation click here
IP Number translation
IP address strings to 32 bit number
In what follows, ’p’ stands for presentation.
Hence, these routines translate between the address as a string and the address as the number.
Hence, we have 4 representations:
- IP number in host order
- IP number in network order
- Presentation (eg. dotted decimal)
- Fully qualified domain name
Only the last needs an outside lookup to convert to one of the
other formats.
Connectionless communication
UDP variations
It is not necessary for both sockets to bind
- The receiver gets the address of the sender
It is possible for a UDP socket to connect
- In this case, send/recv (or write/read) must be used instead of sendto/recvfrom.
- Asynchronous errors can be returned (using ICMP)
sendto
The sendto(2) function allows you to write a datagram and specify the destination address of the recipient at the same time. The function synopsis is as follows:
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int s,
const void *msg,
int len,
unsigned flags,
const struct sockaddr *to,
int tolen);
Don't let the number of arguments intimidate you about this function. They are quite easy to understand, after they are described:
1. The first argument s is the socket number. You received this value from the socket(2)function.
2. Argument msg is a pointer to the buffer holding the datagram message that you wish to send.
3. Argument len is the length, in bytes, of the datagram that starts at the pointer given by msg.
4. The flags argument allows you to specify some option bits. In many cases, you will simply supply a value of zero.
5. The argument to is a pointer to a generic socket address that you have established. This is the address of the recipient of the datagram.
6. Argument tolen is the length of the address argument to.
The value returned by the function sendto(2), when successful, is the number of bytes sent (note that this is no guarantee that they were received at the remote end.) When an error occurs, the
function returns a value of -1 and the value errno can be consulted to find out why.
recvfrom
The companion to the sendto(2) function is the recvfrom(2) function. This function differs from the read(2) function in that it allows you to receive the sender's address at the same time
you receive your datagram. The function synopsis is as follows:
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom (int s,
void *buf,
int len,
unsigned flags,
struct sockaddr *from,
int *fromlen);
The list of arguments is very similar to those used in the sendto(2) function. The recvfrom (2) arguments are
1. The socket s to receive the datagram from.
2. The buffer pointer buf to start receiving the datagram into.
3. The maximum length (len) in bytes of the receiving buffer buf.
4. Option flag bits flags.
5. The pointer to the receiving socket address buffer, which will receive the sender's address (pointer argument from).
6. The pointer to the maximum length (fromlen) in bytes of the receiving socket address buffer from. Note that the integer that this pointer points to must be initialized to the maximum size of the receiving address structure from, prior to calling the function.
Like any normal read(2) operation, the receiving buffer buf must be large enough to receive the incoming datagram. The maximum length is indicated to the function by the argument len.
The function returns the value -1 if there was an error, and you should consult the value of errno for the cause of the error. Otherwise, the function returns the number of bytes that were received into your receiving buffer buf. This will be the size of your datagram received.
Note especially, however, that the last argument is a pointer to the length of the receiving address structure. Prior to calling the function recvfrom(2), the int value that this pointer points to must contain the maximum byte size of the receiving address structure from. Upon return from the function, the actual size of the address returned is placed into this int variable. In effect, the value pointed to by fromlen acts as both an input value and a returned value.
TIP
If you are using the function recvfrom(2) to receive datagrams for varying protocols, make certain that you allow sufficient socket address space to receive all address families that you might encounter. For example, the socket address size differs for address families AF_INET and AF_LOCAL (AF_UNIX). Often a C
union data type can allow for the maximum size needed.
Note :
I will post the separate example for each TCP/UDP client-server very soon, as soon I will finish them and have a one round of unit testing on that.
thanks for reading.
Reference Books:
No comments:
Post a Comment