Socket-level Programming

Part 1 (Ogg-Vorbis format, 8Mbytes) Part 1 (WAV format, 43Mbytes)
Part 2 (Ogg-Vorbis format, 3Mbytes) Part 2 (WAV format, 14Mbytes)

TCP/IP

The OSI model was devised using a committee process wherein the standard was set up and then implemented. Some parts of the OSI standard are obscure, some parts cannot easily be implemented, some parts have not been implemented.

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.

TCP/IP stack

The TCP/IP stack is shorter than the OSI one:

TCP is a connection-oriented protocol, UDP (User Datagram Protocol) is a connectionless protocol.

IP datagrams

The IP layer provides a connectionless and unreliable delivery system. It considers each datagram independently of the others. Any association between datagrams must be supplied by the higher layers.

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.

UDP

UDP is also connectionless and unreliable. What it adds to IP is a checksum for the contents of the datagram and port numbers. These are used to give a client/server model - see later.

TCP

TCP supplies logic to give a reliable connection-oriented protocol above IP. It provides a virtual circuit that two processes can use to communicate.

Internet adddresses

In order to use a service you must be able to find it. The Internet uses an address scheme for machines so that they can be located.

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.

Network address

Class A use 8 bits for the network address with 24 bits left over for other addressing. Class B uses 16 bit network addressing. Class C uses 24 bit network addressing and class D uses all 32.

Monash University is registered as a Class B network, so we have a 16 bit network address with 16 bits left to identify each machine.

Subnet address

Internally, the Uni network is divided into subnetworks. Computer Science and Software Engineering (CSSE) is on one subnetwork that uses 10-bit addressing, allowing 1024 different hosts. The School of Network Computing is on a subnet with 8-bit addressing, allowing 256 different hosts.

Host address

CSSE uses the last 10 bits for host addresses within the CSSE subnet. SNC uses the last 8 bits for network addressing.

Total address

The 32 bit address is usually written as 4 integers separated by dots. For 8-bit host addresses for class B networks, this can be broken down as

Special IP addresses

Symbolic names

Each host has a name. This can be found from the Unix user level command hostname A symbolic name for the network also exists. For our network it is "monash.edu.au". Each subnet also has a name, such as "netcomp". The the symbolic network name for any host is formed from the two: jan.newmarch.name

Programming interface

All networking code for all languages such as Java, Perl, Python, etc is built on low-level code in C. See e.g. Stevens "Unix Network Programming" or Client Server Computing - sockets

InetAddress

The Java class InetAddress can be used to get/manipulate internet addresses. Methods include

static InetAddress getByName(String host) 
static InetAddress getLocalHost()

String getHostAddress(); // in dotted form
String getHostName();

Example


import java.net.InetAddress;
import java.net.UnknownHostException;

public class GetInetInfo{
    public static void main(String[] args){
        if (args.length != 1) {
            System.err.println("Usage: GetInetInfo address");
            // System.exit(1);
            return;
        }

        InetAddress address = null;
        try {
            address = InetAddress.getByName(args[0]);
        } catch(UnknownHostException e) {
            e.printStackTrace();
            // System.exit(2);
            return;
        }
        System.out.println("Host name: " + address.getHostName());
        System.out.println("Host address: " + address.getHostAddress());
        // System.exit(0);
        return;
    }
} // GetInetInfo

IP Address


Port addresses

A service exists on a host, and is identified by its port. This is a 16 bit number (0-65,000). To send a message to a server you send it to the port for that service of the host that it is running on. This is not location transparency!

Certain of these ports are "well known". On Unix, they are listed in the file /etc/services. For example,

Ports in the region 1-255 are reserved by the IETF (Internet Engineering Task Force). The system may reserve more. User processes (in Unix) may have their own ports above 1023. This restriction does not exist in MSDOS.

There is a C function to lookup service ports from their name, but not a Java one.


Sockets

When you know how to reach a service via its network and port IDs, what then? If you are a client you need an API that will allow you to send messages to that service and read replies from it.

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.

The Socket and ServerSocket are the Java client and server classes to do this.


Connection oriented (TCP)

One process (server) makes its socket known to the system using "bind". This will allow other sockets to find it.

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.

Client

The client typically creates a Socket, gets I/O streams from it, and then does consecutive writes and reads.




TCP time client

Some machines run a TCP server on port 13 that returns in readable form the daytime on that particular machine. All that a client has to do is to create a socket to that machine and then read the time from that machine.

import java.io.*;
import java.net.*;

public class Client{

    public static final int DAYTIMEPORT = 8189;
    
    public static void main(String[] args){

	if (args.length != 1) {
	    System.err.println("Usage: Client address");
	    // System.exit(1);
	    return;
	}

	InetAddress address = null;
	try {
	    address = InetAddress.getByName(args[0]);
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    // System.exit(2);
            return;
	}

	Socket sock = null;
	try {
	    sock = new Socket(address, DAYTIMEPORT);
	} catch(IOException e) {
	    e.printStackTrace();
	    // System.exit(3);
            return;
	}

	InputStream in = null;
	try {
	    in = sock.getInputStream();
	} catch(IOException e) {
	    e.printStackTrace();
	    // System.exit(5);
            return;
	}

	BufferedReader reader = new BufferedReader(new InputStreamReader(in));
	String line = null;
	try {
	    line = reader.readLine();
	} catch(IOException e) {
	    e.printStackTrace();
	    // System.exit(6);
            return;
	}

	System.out.println(line);
	// System.exit(0);
        return;
    }
} // Client

Server

A server uses a ServerSocket to bind to a port and listen on it. When a new client request is accepted, it returns a Socket which is connected to the client. The server uses this to talk to the client, typically reading requests and responding to them

Echo Server

The following code is incomplete and won't compile, since it doesn't handle exceptions

import java.io.*;
import java.net.*;

public class EchoServer {
    
    public static int MYECHOPORT = 8189;

    public static void main(String argv[]) {
	ServerSocket s = new ServerSocket(MYECHOPORT);
        while (true) {
            Socket incoming = s.accept();
	    handleSocket(incoming);
	    incoming.close();
	}
    }
    
    public static void handleSocket(Socket incoming) {
	BufferedReader reader =
	    new BufferedReader(new InputStreamReader(
			       incoming.getInputStream()));
	PrintStream out =
	    new PrintStream(incoming.getOutputStream());
	out.println("Hello. Enter BYE to exit");
	
	boolean done = false;
	while ( ! done) {
	    String str = reader.readLine();
	    if (str == null) 
		done = true;
	    else {
		out.println("Echo: " + str);
		if (str.trim().equals("BYE"))
		    done = true;
	    }
	    
	}
    }
}

IO exceptions are thrown by lots of statements in the program above

So, just catch them all and ignore them? e.g.
public class EchoServer {
    
    public static int MYECHOPORT = 8189;

    public static void main(String argv[]) {
        // bad code follows
	try {
            ServerSocket s = new ServerSocket(MYECHOPORT);
            while (true) {
                Socket incoming = s.accept();
	        handleSocket(incoming);
	        incoming.close();
	    }
        } catch(IOException e) {
            // ignore
        }
    }

Each exception must be examined to see what effect it has on the application

A safer server is


import java.io.*;
import java.net.*;

public class EchoServer {
    
    public static int MYECHOPORT = 2000;
    //public static String CR_LF = "\r\n";

    public static void main(String argv[]) {
	ServerSocket s = null;
	try {
	    s = new ServerSocket(MYECHOPORT);
	} catch(IOException e) {
	    System.out.println(e);
	    System.exit(1);
	}
	while (true) {
	    Socket incoming = null;
	    try {
		incoming = s.accept();
		System.out.println("Connected");
	    } catch(IOException e) {
		System.out.println(e);
		continue;
	    }

	    handleSocket(incoming);
	}
    }
    
    public static void handleSocket(Socket incoming) {

	byte[] data = new byte[1024];
	InputStream in;
	OutputStream out;
	try {
	    in = incoming.getInputStream();
	    out = incoming.getOutputStream();
	}  catch(IOException e) {
	    System.err.println(e.toString());
	    return;
	}
	int numRead = 0;

	try {
	    while (true) {
		try {
		    numRead = in.read(data);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
		if (numRead == -1) {
		    System.out.println("Disconnecting");
		    return;
		}
		try {
		    out.write(data, 0, numRead);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
	    }		
	} finally {
	    System.out.println("Closing all");
	    try {
		in.close();
		out.close();
		incoming.close();
	    } catch(IOException e) {
		// nothing to do really
	    }
	}
    }
}

Multithreaded Echo Server

The server above will only handle one client at a time. To handle many clients at once, the server should use threads


import java.io.*;
import java.net.*;

public class MultiEchoServer {
    
    public static int MYECHOPORT = 2000;

    public static void main(String argv[]) {
	ServerSocket s = null;
	try {
	    s = new ServerSocket(MYECHOPORT);
	} catch(IOException e) {
	    System.out.println(e);
	    System.exit(1);
	}
	System.out.println("Listening");

	while (true) {
	    Socket incoming = null;
	    try {
		incoming = s.accept();
		System.out.println("Connected");
	    } catch(IOException e) {
		System.out.println(e);
		continue;
	    }

	    new SocketHandler(incoming).start();

	}  
    }
}

class SocketHandler extends Thread {

    Socket incoming;

    SocketHandler(Socket incoming) {
	this.incoming = incoming;
    }

    public void run() {
	byte[] data = new byte[1024];
	InputStream in;
	OutputStream out;
	try {
	    in = incoming.getInputStream();
	    out = incoming.getOutputStream();
	}  catch(IOException e) {
	    System.err.println(e.toString());
	    return;
	}
	int numRead = 0;

	try {
	    while (true) {
		try {
		    numRead = in.read(data);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
		if (numRead == -1) {
		    System.out.println("Disconnecting");
		    return;
		}
		try {
		    out.write(data, 0, numRead);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
	    }		
	} finally {
	    System.out.println("Closing all");
	    try {
		in.close();
		out.close();
		incoming.close();
	    } catch(IOException e) {
		// nothing to do really
	    }
	}
    }
}

Timeout

The server may wish to timeout a client if it does not respond quickly enough i.e. does not write a request to the server in time. This should be a long period (several minutes), because the user may be taking their time. Conversely, the client may want to timeout the server (after a much shorter time). Both do this by

try {
    incoming.setSoTimeout(10000); // 10 seconds
} catch(SocketException e) {
    e.printStackTrace();
}
before any reads on the socket. A timeout on a read() will then throw a InterruptedIOException (a subclass of IOException.

Staying alive

A client may wish to stay connected to a server even if it has nothing to send. It can setKeepAlive(true) on the socket.


Connectionless (UDP)

In a connectionless protocol each message contains information about its origin or destination. There is no "session" established using a long-lived socket. UDP clients and servers make use of DatagramSocket. There is no state maintained by these messages, unless the client or server does so. The messages are not guaranteed to arrive, or may arrive out of order.

Time client (UDP)

The UDP time server requires a datagram to be sent to it. It ignores the contents of the message but uses the return address to send back a datagram containing the time.


/**
 * UDPTimeClient.java
 *
 *
 * Created: Sun Jul 22 19:21:13 2001
 *
 * @author <a href="mailto: "Jan Newmarch</a>
 * @version
 */

import java.io.*;
import java.net.*;

public class UDPTimeClient{

    public static final int DAYTIMEPORT = 9013;
    public static final int DGRAM_BUF_LEN = 512;                                
    public static void main(String[] args){
	
	if (args.length != 1) {
	    System.err.println("Usage: Client address");
	    // System.exit(1);
            return;
	}

	InetAddress address = null;
	try {
	    address = InetAddress.getByName(args[0]);
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    // System.exit(2);
            return;
	}

	DatagramSocket socket = null;
	try {
	    socket = new DatagramSocket();
	} catch (SocketException e) {
	    e.printStackTrace();
	    // System.exit(3);
            return;
	}

	try {
	    socket.connect(address, DAYTIMEPORT);
	    
	    byte[] buf = new byte[DGRAM_BUF_LEN];
	    DatagramPacket packet = new DatagramPacket(buf, buf.length);
	    socket.send(packet);
	    
	    socket.receive(packet);
	    byte[] data = packet.getData();
	    String date = new String(data);
	    System.out.println(date);
	    
	} catch(IOException e) {
	    e.printStackTrace();
	    // System.exit(4);
            return;
	}
	// System.exit(0);
        return;
    }
} // UDPTimeClient



UDP Time Server



/**
 * UDPTimeClient.java
 *
 *
 * Created: Sun Jul 22 19:21:13 2001
 *
 * @author <a href="mailto: "Jan Newmarch</a>
 * @version
 */

import java.io.*;
import java.net.*;
import java.util.Date;

public class UDPTimeServer {

    public static final int DAYTIMEPORT = 9013;
    public static final int DGRAM_BUF_LEN = 512;

    public static void main(String[] args){
	
	DatagramSocket socket = null;
	try {
	    socket = new DatagramSocket(DAYTIMEPORT);
	} catch (SocketException e) {
	    e.printStackTrace();
	    System.exit(3);
	}
	while (true) {
	    try {
		byte[] buf = new byte[DGRAM_BUF_LEN];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		socket.receive(packet);

		String date = new Date().toString();
		buf = date.getBytes();

		// get client info
		InetAddress clientAddr = packet.getAddress();
		int port = packet.getPort();
		// prepare packet for return to client
		packet = new DatagramPacket(buf, buf.length, clientAddr, port);
		socket.send(packet);
	    } catch(IOException e) {
		e.printStackTrace();
	    }
	}
    }
} // UDPTimeServer



Server listening on multiple sockets

A server may be attempting to listen to multiple clients not just on one port, but on many. In this case it has to use some sort of polling mechanism between the ports.

In C, 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. In Java, accomplish the same by using a different Thread for each port. A thread will become runnable when the lower-level select() discovers that I/O is ready for this thread.

Multicast

Overview

A multicast packet is broadcast to all possible clients, and may be received by any/all of them. A multicast packet is not restricted to just one host destination. This is useful for e.g. multi-player games, or networks where you know a service is available but don't know its address.

IP addresses in the range 224.0.0.0 to 239.255.255.255 (inclusive) are multicast addresses. The address 224.0.0.0 is reserved and should not be used. If you ping 224.0.0.1, all multicast-enabled hosts should answer. Each of these IP addresses has the full range of 65k ports.

"Ordinary" TCP and UDP addresses use a field TTL (time to live) to control the number of "hops" they are allowed to make. For example, a hop count of one will restrict them to the local network. Multicast packets have the potential to flood the network. Most network administrators will restrict the range of multicast packets. The TTL is used for this as a hack measure. There is no standard, but typically a hopcount of less than 60 will not be allowed past any gateway, and the default hopcount is usually set at 15.

Java Multicast sockets

Java uses MulticastSocket to encapsulate multicast behaviour. This is a subclass of DatagramSocket

Java Multicast client



/**
 * MulticastClient.java
 *
 *
 * Created: Sun Jul 22 19:21:13 2001
 *
 * @author <a href="mailto: "Jan Newmarch</a>
 * @version
 */

import java.io.*;
import java.net.*;

public class MulticastClient{

    public static final String MCAST_ADDR = "230.0.0.1";
    public static final int MCAST_PORT = 9013;
    public static final int DGRAM_BUF_LEN = 512;                                
    public static void main(String[] args){

	String msg = "Hello";
	InetAddress group = null;
	try {
	    group = InetAddress.getByName(MCAST_ADDR);
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	try {
	    MulticastSocket socket = new MulticastSocket(MCAST_PORT);
	    socket.joinGroup(group);
	    DatagramPacket hi = new DatagramPacket(msg.getBytes(), msg.length(),
						   group, MCAST_PORT);
	    System.out.println("Sending: " + msg);
	    socket.send(hi);
	    // get their responses!
	    while (true) {
		byte[] buf = new byte[DGRAM_BUF_LEN];
		DatagramPacket recv = new DatagramPacket(buf, buf.length);
		socket.receive(recv);
		byte[] data = recv.getData();
		System.out.println("Received: " + new String(data));
	    }
	} catch(IOException e) {
	    e.printStackTrace();
	    System.exit(2);
	}
	// OK, I'm done talking - leave the group...
	// s.leaveGroup(group);
 	// System.exit(0);
    }
} // MulticastClient



Java Multicast server



/**
 * UDPTimeClient.java
 *
 *
 * Created: Sun Jul 22 19:21:13 2001
 *
 * @author <a href="mailto: "Jan Newmarch</a>
 * @version
 */

import java.io.*;
import java.net.*;
import java.util.Date;

public class MulticastServer {

    public static final String MCAST_ADDR = "230.0.0.1";
    public static final int MCAST_PORT = 9013;
    public static final int DGRAM_BUF_LEN = 512;

    public static void main(String[] args){
	
	InetAddress group = null;
	try {
	    group = InetAddress.getByName(MCAST_ADDR);
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	MulticastSocket socket = null;
	try {
	    socket = new MulticastSocket(MCAST_PORT);
	    socket.joinGroup(group);
	} catch (IOException e) {
	    e.printStackTrace();
	    System.exit(3);
	}

	while (true) {
	    try {
		byte[] buf = new byte[DGRAM_BUF_LEN];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		socket.receive(packet);
		System.out.println("Received: " + new String(buf));

		String date = new Date().toString();
		buf = date.getBytes();

		// get client info
		InetAddress clientAddr = packet.getAddress();
		int port = packet.getPort();
		// prepare packet for return to client
		packet = new DatagramPacket(buf, buf.length, clientAddr, port);
		System.out.println("Sending: " + new String(buf));
		socket.send(packet);
	    } catch(IOException e) {
		e.printStackTrace();
	    }
	}
    }
} // UDPTimeServer




Jan Newmarch <jan@newmarch.name>
Last modified: Mon Aug 15 12:14:19 EST 2005
Copyright © Jan Newmarch, Monash University, 2007
Creative Commons License This work is licensed under a Creative Commons License
The moral right of Jan Newmarch to be identified as the author of this page has been asserted.