The TCP/IP protocol was devised through a long-running DARPA project. This worked by implementation followed by RFCs (Request For Comment). TCP/IP is the principal Unix networking protocol. TCP/IP = Transmission Control Protocol/Internet Protocol.
The IP layer supplies a checksum that includes its own header. The header includes the source and destination addresses.
The IP layer handles routing through an Internet. It is also responsible for breaking up large datagrams into smaller ones for transmission and reassembling them at the other end.
The address is a 32 bit integer which gives the IP address. This encodes a network ID and more addressing. The network ID falls into various classes according to the size of the network address.
The University of Canberra is registered as a Class B network, so we have a 16 bit network address with 16 bits left to identify each machine.
A symbolic name for the network also exists. For our network it is "canberra.edu.au". The the symbolic network name for any host is formed from the two:hostname
birch.ise.canberra.edu.au
(The structure in_addr has only one field which is the 32 bit IP address.)#include #include #include #include unsigned long inet_addr (char *ptr) char *inet_ntoa (struct in_addr in)
The BSD library provides some functions for finding names.struct in_addr { unsigned long int s_addr; }
finds the ordinary hostname.char *gethostname (char *name, int size)
returns a pointer to a structure with two important fields:"char * h_name" which is the "official" network name of the host and "char **h_addr_list" which is a list of TCP/IP addresses.struct hostent *gethostbyname (char *name)
The following program prints these: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 */ }
This programming interface uses a number of standard files: /etc/hostname to find the name, /etc/rhosts to find the network address (or a name server) if it can't find it there.#include #include #include #include #include #include #include #define SIZE (MAXHOSTNAMELEN+1) int main (void) { char name[SIZE]; struct hostent *entry; if (gethostname (name, SIZE) != 0) { fprintf (stderr, "unknown name\n"); exit (1); } printf ("host name is %s\n", name); if ((entry = gethostbyname (name)) == NULL) { fprintf (stderr, "no host name info\n"); exit (2); } printf ("offic. name: %s\n", entry->h_name); printf ("address: %s\n", inet_ntoa(*(struct in_addr *) (entry->h_addr_list [0]))); exit (0); }
Certain of these ports are "well known". They are listed in the file /etc/services. For example,
The function "getservbyname" can be used to find the port for a service that
is registered in /etc/services.
If you are a server, you need to be able to create a port and listen at it. When a message comes in you need to be able to read and write to it.
Berkeley sockets are the BSD Unix system calls for this. They are part of the BSD Unix kernel. They have also been adopted by the PC world. They form the lowest practical level of doing client/server on both Windows and Unix.
To avoid this, two communicating machines must agree on data representation.5 * 16777216 + 6 * 65536
The Sun RPC uses a format known as XDR, which just happens to be the format that doesn't require any conversions for Suns. However, if two 386s are communicating then each of them will have to keep swapping bytes both on receipt and send.
The OSF DCE uses native format, with the receiving machine swapping bytes if needed. This section describes the Unix BSD networking API for IP as in WR Stevens "Unix Network Programming."
Example: The finger service (port 79) on machine 137.92.11.1 is given by#include struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero [8]; }
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons (79); addr.sin_addr.s_addr = inet_addr ("137.92.11.1");
A socket is created using the call "socket". It returns an integer that is
like a file descriptor:
it is an index into a table and "reads" and "writes"
to the network use this "socket file descriptor".
Here "family" will be AF_INET for IP communications, "protocol" will be
zero, and "type" will depend on whether TCP or UDP is used.
Two processes wishing to communicate over a network create a socket each. These are similar to two ends of a pipe - but the actual pipe does not yet exist.
It then "listens" on this socket to "accept" any incoming messages.
The other process (client) establishes a network connection to it, and then the two exchange messages.
As many messages as needed may be sent along this channel, in either direction.
Example: If the program is compiled to "tcptime", find the time in various places by/* TCP client that finds the time from a server */ #include #include #include #include #define SIZE 1024 char buf [SIZE]; #define TIME_PORT 13 int main (int argc, char *argv []) { int sockfd; int nread; struct sockaddr_in serv_addr; if (argc != 2) { fprintf (stderr,"usage: %s IPaddr\n", argv [0]); exit (1); } /* create endpoint */ if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) { perror (NULL); exit (2); } /* connect to server */ serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr (argv [1]); serv_addr.sin_port = htons (TIME_PORT); if (connect (sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) { perror (NULL); exit (3); } /* transfer data */ nread = read (sockfd, buf, SIZE); write (1, buf, nread); close (sockfd); exit (0); }
#include #include #include #include #define SIZE 1024 char buf [SIZE]; #define TIME_PORT 13 int main (int argc, char *argv []) { int sockfd, client_sockfd; int nread, len; struct sockaddr_in serv_addr, client_addr; time_t t; /* create endpoint */ if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) { perror (NULL); exit (2); } /* bind address */ serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl (INADDR_ANY); serv_addr.sin_port = htons (TIME_PORT); if (bind (sockfd, &serv_addr, sizeof (serv_addr)) < 0) { perror (NULL); exit (3); } /* specify queue */ listen (sockfd, 5); for (;;) { len = sizeof (client_addr); client_sockfd = accept (sockfd, &client_addr, &len); if (client_sockfd == -1) { perror (NULL); continue; } /* transfer data */ time (&t); sprintf (buf, "%s", asctime (localtime (&t))); len = strlen (buf) + 1; write (client_sockfd, buf, len); close (client_sockfd); } }
When bind is called it binds to a new port - it cannot bind to one already in use. If you specify the port as zero the system gives you a currently unused port.
Because of this extra task on each message send,
the processes do not use read/write
but recvfrom/sendto. These functions take as parameters the socket to write
to, and the address of the service on the remote machine.
/* UDP client for time */ #include #include #include #include #include #define SIZE 1400 char buf [SIZE]; #define TIME_PORT 13 int main (int argc, char *argv []) { int sockfd; int nread; struct sockaddr_in serv_addr, client_addr; int len; if (argc != 2) { fprintf (stderr, "usage: %s IPaddr\n", argv [0]); exit (1); } if ((sockfd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { perror (NULL); exit (2); } client_addr.sin_family = AF_INET; client_addr.sin_addr.s_addr = htonl (INADDR_ANY); client_addr.sin_port = htons (0); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr (argv [1]); serv_addr.sin_port = htons (TIME_PORT); if (bind (sockfd, &client_addr, sizeof (client_addr)) < 0) { perror (NULL); close (sockfd); exit (3); } len = sizeof (serv_addr); sendto (sockfd, buf, 1, 0, &serv_addr, len); nread = recvfrom (sockfd, buf, SIZE, 0, &client_addr, &len); write (1, buf, nread); close (sockfd); exit (0); }
In addition, "getsockopt" and "setsockopt" can be used for more specific socket control:
Timeout algorithms should adjust the time according to the curent trip time in some manner. They can be implemented using timer signals.
In Unix this is fairly easy: after the accept() has succedeed,
the server should fork off a new process to handle the client.
Here is the TCP time server, capable of handling many clients
at once:
The select() call lets the kernel do this work. The call takes a number of file descriptors. The process is suspended. When I/O is ready on one of these, a wakeup is done, and the process can continue. This is cheaper than busy polling.
When a request comes through, a server is created to handle it. To make it simpler for the server, instead of having to deal with ports, inetd will remap these onto stdin and stdout so that the server just reads/writes to stdin/stdout.
import java.io.*; impoert java.net.*; public class SocketTest { public static void main (String argv []) { try { Socket t = new Socket ("java.sun.com", 13); DataInputStream is = new DataInputStream (t.getInputStream ()); boolean more = true; while (mnore) { String str = is.readLine (); if (str == null) more = false; else System.out.println (str); } } catch (IOException e) { System.out.println ("Error" + e); } } }
import java.io.*; import java.net.*; public class EchoServer { public static void main (String argv []) { try { ServerSocket s = new ServerSocket (8189); Socket incoming = s.accept (); DataInputStream in = new DataInputStream (incoming.getInputStream ()); PrintStream out = new PrintStream (incoming.getOutputStream ()); out.println ("Hello. Enter BYE to exit"); boolean done = false; while (!done) { String str = in.readLine (); if (str == null) done = true; else { out.println ("Echo: " + str); if (str.trim ().equals ("BYE")) done = true; } incoming.close (); } catch (Exception e) { System.out.println (e); } } }
Perl has a C-like interface to sockets. It also has a higher level one
using the IO::Socket module.
This example is a client to fetch documents from a Web server.
Here is a server that will execute some commands and return a result