Computer networks -- 2007-2008 -- info.uvt.ro/Laboratory 2

Important! These pages are somehow outdated and it is recommended to consult the newer version at Computer networks -- 2008-2009 -- info.uvt.ro (by Ciprian Crăciun).

Quick links:


Prerequisites

edit

In order to accomplish this laboratory successfully, and without great difficulties you should be familiar with the following:

  • medium Java programming skills:
    • how to parse command line arguments;
    • how to parse strings into numbers;
    • how to use input and output streams and their wrappers;
    • what is the difference between a buffered stream and an unbuffered stream;
    • how to use threads and synchronization;
    • how to use Swing framework to build simple UI -- without the help of an IDE;
    • and of course exception handling;

In order to get familiar with these topics you could consult the following materials:

It is also mandatory to follow the content of the first laboratory (especially the sections noted bellow):

Socket

edit

A socket is an abstraction of a communication channel end-point (either network communication or local inter-process communication), that allows us to exchange data between the peers.

In order to work with sockets we need an API. This API was initially introduced by Berkeley university in 1983, long before the Internet -- TCP/IP -- was born. The API was initially only available for C programming language, but it was quickly adopted by all languages as libraries, and it is today a de facto standard for network application interfaces.

The most important operations that are available to us are:

  • receiving data;
  • sending data;
  • closing the socket;

These operations can be executed only when the socket is in a certain state; states which are determined by the life cycle of the socket:

  • creation -- the socket is created and is ready to be used;
  • setup -- optionally before any other operation we can execute certain parameter setup;
  • data exchange -- we are allowed to read and write data;
  • termination -- releasing all the resources committed to it, and no other operation is allowed after this;

Sockets can be used with many kinds of network protocols:

  • connection-oriented (like TCP) or connection-less (like UDP);
  • stream (like TCP) or datagram (like UDP);
  • reliable (like TCP) or unreliable (like UDP);

They are not only limited to TCP/IP (the most common usage), there are available also for:

  • Unix native sockets;
  • IPX;
  • X25;
  • ATM;
  • or even raw access to the network layer;

The read and write operations can be:

  • blocking;
  • non-blocking;
  • asynchronous;

References

edit
man 7 socket

TCP sockets

edit

The most common use for sockets is to write TCP applications, which are split in two categories:

  • servers:
    • applications that usually run on dedicated hardware and software -- on a server machine;
    • these applications offer a well defined service to any client that communicates with it;
  • clients:
    • applications that usually run on the users computer -- a workstation, desktop, laptop or even smart-phone and PDAs;
    • these applications connect to a specific server, send requests, and wait for responses;

As a consequence the sockets are also split in two types:

  • server sockets -- used on the server side;
  • client sockets -- used on the client side;

As noted previously a client needs to connect to a server, resulting that the two sockets -- the one from the server and the one from the client -- are related to each other -- they are connected. In order to establish a connection the following main conditions must be met:

  • the server has to listen on a specific IP address and port -- this is the server's socket local address;
  • the client has to connect on to that particular IP address and port -- this is the client's socket remote address;
  • additionally each client socket must also have a local address;

Thus the client must know the address and port in advance, but usually we skip this by:

  • using a DNS name;
  • using a default port;

TCP client sockets

edit

Life-cycle

edit
  1. creation -- a client socket object is created;
  2. binding -- the socket's local address is established;
  3. connection -- the socket's remote address is established and a connection is made;
  4. data exchange;
  5. shutting down the channel -- either one way or both ways:
    • if we shutdown the input -- incoming -- stream we notify the other peer that we are not accepting any other data;
    • if we shutdown the output -- outgoing -- stream we notify the other peer that we shall not send any more data;
    • each stream can be shutdown only once;
  6. receiving the remaining data after shutdown;
  7. closing -- releasing all committed resources;

Java API

edit

Examples

edit
  • Creating the client socket:
Socket socket = new Socket ();
  • Binding to the local socket address:
InetAddress localIpAddress = InetAddress.getByName ("0.0.0.0");
int localIpPort = 0;
SocketAddress localSocketAddress = new InetSocketAddress (localIpAddress, localIpPort);
socket.bind (localSocketAddress);
  • Connecting to the remote socket address:
InetAddress remoteIpAddress = InetAddress.getByName ("www.info.uvt.ro");
int remoteIpPort = 80;
SocketAddress remoteSocketAddress = new InetSocketAddress (remoteIpAddress, remoteIpPort);
socket.connect (remoteSocketAddress);
  • Receiving and / or sending data through input and output streams:
InputStream input = socket.getInputStream ();
OutputStream output = socket.getOutputStream ();
input.read (buffer, offset, size); ...
output.write (buffer, offset, size); ...
output.flush ();
  • Shutting-down the input and output streams:
socket.shutdownInput ();
socket.shutdownOutput ();
  • Closing the socket:
socket.close ();

TCP server sockets

edit

Life-cycle

edit
  • creation -- a server socket is created;
  • binding -- the socket's local address is established;
  • listening -- the socket is put into a state that accepts incoming connections;
  • accepting -- the application waits for incoming connections; each connection is in the form of a (client) socket; for each connection the application either:
    • handles it, closes it, and continues to accept other connections;
    • creates a new process or thread which will handle the connection, and the current process or thread continues to accept other connections;
  • closing -- the socket stops accepting incoming connections, and all committed resources are released; (closing the server socket, does not close any of the accepted incoming sockets;)

Java API

edit
  • java.net:
    • ServerSocket:
      • bind(SocketAddress) -- this establishes the socket's local address -- the one that the client must be aware of; this also puts the socket in a listening state;
      • accept() -- waits for an incoming connection and returns a (client) socket representing that connection;
      • close() -- stops the acceptance of new connections and releases all the socket resources;
      • getLocalSocketAddress() -- used to retrieve the local socket address;
  • please see also the API presented in the section Java IP generic API;

Examples

edit
  • Creating the server socket:
ServerSocket socket = new ServerSocket ();
  • Binding to the local socket address -- this is the one the clients should be connecting to:
InetAddress localIpAddress = InetAddress.getByName ("0.0.0.0");
int localIpPort = 80;
SocketAddress localSocketAddress = new InetSocketAddress (localIpAddress, localIpPort);
socket.bind (localSocketAddress);
  • For each connection accepting a client socket, and:
Socket client = socket.accept ();
    • Receiving and / or sending data;
    • Shutting-down the inbound and outbound streams;
    • Closing the client socket;
These steps are just like the ones described above for the client socket.
  • Closing the server socket;
socket.close ();

Java IP generic API

edit

In Java we have the following API that allows us to:

  • resolve DNS names to IP addresses;
  • resolve IP address to DNS names -- reverse query;
  • get the IP address of the current computer;

API:

Java IO generic API

edit

Complete example

edit

The following two applications are a simple client and server that allow a user to make the following queries:

  • send hello, and receive hello;
  • send get-time, and receive the server's time in number of milliseconds elapsed from 1970;
  • send get-random, and receive a random number;

The protocol is pretty simple: send one line -- the request -- receive one line -- the response;

The client is started as (where bin is a folder where the byte-compiled classes are found):

java -classpath ./bin Client <host> <port> <request>
java -classpath ./bin Client 127.0.0.1 7654 hello

The server is started as (the port is hard-coded, and it is 7654):

java -classpath ./bin Server

Client

edit

The source code for both the client and the server can be found inside the Subversion repository in the folder examples/example-01.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;


public class Client
{
	
	public static void main (String[] arguments) throws Exception
	{
		// Checking the argument count
		if (arguments.length != 3) {
			System.err.println ("Bad usage.");
			return;
		}
		
		// Selecting the arguments
		String hostName = arguments[0];
		String portName = arguments[1];
		String request = arguments[2];
		
		
		// Creating the client socket
		Socket socket = new Socket ();
		
		// Binding the socket
		socket.bind (new InetSocketAddress (InetAddress.getByName ("0.0.0.0"), 0));
		
		// Resolving the server address and port
		InetAddress address = InetAddress.getByName (hostName);
		int port = Integer.parseInt (portName);
		
		// Connecting the socket
		socket.connect (new InetSocketAddress (address, port));
		
		// Creating a reader and writer from the socket streams
		BufferedReader reader = new BufferedReader (new InputStreamReader (socket.getInputStream ()));
		BufferedWriter writer = new BufferedWriter (new OutputStreamWriter (socket.getOutputStream ()));
		
		// Writing the request
		writer.write (request);
		writer.newLine ();
		
		// Flushing the writer
		writer.flush ();
		
		// Reading the response
		String response = reader.readLine ();
		
		// Closing the socket
		socket.close ();
		
		// Printing the response
		System.out.println (response);
	}
}

Server

edit

As noted previously the source code for both the client and the server can be found inside the Subversion repository in the folder examples/example-01.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;


public class Server
{
	public static void main (String[] arguments) throws Exception
	{
		// Creating the server socket
		ServerSocket server = new ServerSocket ();
		
		// Binding the socket
		server.bind (new InetSocketAddress (InetAddress.getByName ("0.0.0.0"), 7654));
		
		// Looping
		while (true) {
			
			// Accepting a new connection => a new client socket
			final Socket client = server.accept ();
			
			// Starting a new Thread for each client
			new Thread () {
				
				public void run () {
					
					// Catching any exceptions
					try {
						
						// Creating a reader and a writer from the socket streams
						BufferedReader reader = new BufferedReader (new InputStreamReader (client.getInputStream ()));
						BufferedWriter writer = new BufferedWriter (new OutputStreamWriter (client.getOutputStream ()));
						
						// Reading the first request
						String request = reader.readLine ();
						
						// While the connection is still open
						while (request != null) {
							
							// Creating a response based on the request
							String response;
							if (request.equals ("hello"))
								response = "hello back";
							else if (request.equals ("get-time"))
								response = "" + System.currentTimeMillis ();
							else if (request.equals ("get-random"))
								response = "" + Math.rint (Math.random () * 1000);
							else if (request.equals ("quit")) {
								break;
							} else
								response = "please?";
						
							// Writing the response
							writer.write (response);
							writer.newLine ();
							
							// Flushing the writer
							writer.flush ();
							
							// Reading the next request
							request = reader.readLine ();
						}
						
						// Closing the client socket
						client.close ();
						
					} catch (Throwable error) {
						
						// Handling the possible errors
						error.printStackTrace ();
					}
				}
			} .start ();
		}
	}
}

Examples of simple TCP based protocols

edit

Assignment

edit

This is the first assignment, so please commit it to the folder assignment-01.

Write a TCP client application that:

  • takes zero or two arguments:
    • the first argument is the DNS name of the server;
    • the second argument is the port of the server;
    • in case there are no arguments the name server is assumed to be hephaistos.info.uvt.ro and the port 7391;
  • it connects by using a TCP socket to the given server;
  • it waits for commands from the server and -- when applicable -- will give an answer;
  • it simulates a simple arithmetic calculator that has a single register to which will operate according to the received commands -- initially the register is 0;
  • it handles all exception cases, and for each exception will show an error message -- using System.err -- and will exit gracefully;
  • in case of errors encountered when decoding the protocol an error message -- using System.err -- will be shown and will exit gracefully;

The protocol is as follows:

  • each command, command argument and response are lines -- strings ended by LF, '\n';
  • the commands are sent by the server, and the client only sends back an answer when it should;
  • the commands are:
    • addition command: a line containing only the character '+', followed by a line containing a number; no answer is sent by the client; the number is added to the register;
    • substraction command: the same as addition command, but the first line contains just the character '-'; the number is substracted from the register;
    • multiplication command: '*', see above;
    • division command: '/', see above;
    • result command: a line containing only the string '='; the client will send as answer a line containing the current value of the register; and will wait for a line from the server which validates the answer (either a line containing ok or nok depending on the fact that the client executed the operation correctly); after this it resumes normal operation; please note that the register is not reseted to 0, it retains its value; (please see the example);
    • exit command: a line containing only the string 'exit'; the client will close the connection and exit;

Observations:

  • the error messages should be informative, giving the possible cause;

Example dialog:

  • s->c this is something the server sends to the client;
  • s<-c this is something the client sends to the server;
  • <LF> is the '\n' character -- the line terminator;
  • neither s->c and s<-c are part of the protocol, they are used here only to specify the direction in which the messages (the lines in our case) travel;
s->c +<LF>
s->c 3<LF>
s->c *<LF>
s->c 4<LF>
s->c =<LF>
s<-c 12<LF>
s->c ok<LF>
s->c -<LF>
s->c 10<LF>
s->c =<LF>
s<-c 2<LF>
s->c ok<LF>
s->c exit<LF>
s->c =<LF>
s<-c 0<LF>
s->c ok<LF>
s->c exit<LF>
s->c exit<LF>

Server source code

edit

The source code of the server can also be found inside the Subversion repository in the folder examples/example-02.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;


public class Server
{
	public static void main (String[] arguments) throws Exception
	{
		// Creating the server socket
		ServerSocket server = new ServerSocket ();
		
		// Binding the socket
		server.bind (new InetSocketAddress (InetAddress.getByName ("0.0.0.0"), 7391));
		
		// Looping
		while (true) {
			
			// Accepting a new connection => a new client socket
			final Socket client = server.accept ();
			
			// Starting a new Thread for each client
			new Thread () {
				
				public void run () {
					
					// Catching any exceptions
					try {
						
						// Creating a reader and a writer from the socket streams
						BufferedReader reader = new BufferedReader (new InputStreamReader (client.getInputStream ()));
						BufferedWriter writer = new BufferedWriter (new OutputStreamWriter (client.getOutputStream ()));
						
						// Creating a random source
						Random random = new Random ();
						
						// Initializing the register used to check the validity of the client
						int register = 0;
						
						command_loop :
						while (true) {
							
							// Selecting one of the five possible commands
							int command = random.nextInt (6);
							
							// Selecting a new argument
							int argument = random.nextInt (Integer.MAX_VALUE);
							
							switch (command) {
								
								// In case the command is +
								case 0 : {
									register += argument;
									writer.write ("+\n" + argument + "\n");
									writer.flush ();
									break;
								}
								
								// In case the command is -
								case 1 : {
									register -= argument;
									writer.write ("-\n" + argument + "\n");
									writer.flush ();
									break;
								}
								
								// In case the command is *
								case 2 : {
									register *= argument;
									writer.write ("*\n" + argument + "\n");
									writer.flush ();
									break;
								}
								
								// In case the command is /
								case 3 : {
									if (argument == 0)
										break;
									register /= argument;
									writer.write ("/\n" + argument + "\n");
									writer.flush ();
									break;
								}
								
								case 4 : {
									writer.write ("=\n");
									writer.flush ();
									String response = reader.readLine ();
									if (response == null) {
										System.err.println ("Protocol error!");
										break command_loop;
									}
									String status = (Integer.toString (register) .equals (response)) ? "ok" : "nok";
									writer.write (status + "\n");
									writer.flush ();
									break;
								}
								
								case 5 : {
									writer.write ("exit\n");
									writer.flush ();
									String response = reader.readLine ();
									if (response != null)
										System.err.println ("Protocol error!");
									break command_loop;
								}
								
								default :
									throw new Error ();
							}
						}
						
						// Closing the client socket
						client.close ();
						
					} catch (Throwable error) {
						
						// Handling the possible errors
						error.printStackTrace ();
					}
				}
			} .start ();
		}
	}
}