Home Papers Reports Projects Code Fragments Dissertations Presentations Posters Proposals Lectures given Course notes
<< 2. UDP IP Sockets in C4. Java Remote Method Invocation (RMI) >>

3. TCP IP Sockets in Java

Werner Van Belle1 - werner@yellowcouch.org, werner.van.belle@gmail.com

1- Programming Technology Lab (PROG) Department of Computer Science (DINF) Vrije Universiteit Brussel (VUB); Pleinlaan 2; 1050 Brussel; Belgium

Abstract :  These are course notes I made to teach distributed systems for the VUB. I used them in 2001-2002, 2002-2003

Reference:  Werner Van Belle; TCP IP Sockets in Java;
See also:
TCP/IP Sockets in C


Java

De oefeningen nemen plaats in Java. Een aantal 'hints' om dit proces te vereenvoudigen:

  • set CLASSPATH=.
    (put this in .bash_rc or .profile)
  • public classes should be placed in files with the same name.
  • javac is de compiler (javac blah.java)
  • java is de interpreter. (java blah). Do not put '.java' after 'blah' !
  • try {...} catch (Exception e) {System.out.println(e)} kan helpen in het debuggen en versnelt de prototype cycle.
  • voor de java-kenners: don't use packages.
  • Als bij het compileren van een classe er een vreemde error over die klasse komt (een niet matchende methode), controleer voor de zekerheid of er een constructor aanwezig is.
  • Sockets

    This section was written by Bug (kvbuggen@cthulhu.rave.org)

    Een socket is de meest eenvoudige manier om twee programmas met elkaar te laten communiceren. Een socket is een file descriptor die werkt als direct communicatiekanaal tussen twee processen: hetgeen het ene proces er in schrijft, kan het andere proces er uit lezen. Dit kan zowel connectiegericht als connectieloos zijn. Berichten worden automatisch gequeued aan een zendende socket totdat het onderliggende netwerkprotocol in staat is het bericht te leveren en een acknowledge gestuurd heeft. Aan de ontvangende kant worden berichten gequeued totdat een proces ze van de socket haalt. Er bestaan twee soorten sockets: UDP sockets en TCP sockets. Het verschil ligt in de Quality of Service, en de manier van connecteren.

    UDP sockets zijn het best vergelijkbaar met een postbus: pakketten verzonden over UDP bevatten het adres waar ze naartoe moeten, en de route wordt bepaald per pakket. Er is ook geen enkele vorm van controle of pakketten hun doel bereikt hebben, en in welke vorm of volgorde.

    Het voordeel van deze sockets is dat de verbinding heel snel werkt: er is geen delay voor opzetten van connecties, en er moet geen QoS informatie mee doorgezonden worden. Deze sockets zijn dus geschikt voor toepassingen waarin veel informatie frequent over het netwerk verzonden wordt, zodat het niet veel uitmaakt dat er ergens een pakket niet toekomt. Een voorbeeld hiervan is Quake: de positie van een speler wordt verschillende keren per seconde verstuurd naar de server, dus het maakt niet uit als enkele van deze pakketten niet toekomen.

    TCP sockets zijn vergelijkbaar met een telefoonlijn: voor er kan gecommuniceerd worden moet een verbinding gemaakt worden, en elk pakket neemt dezelfde route naar de bestemming. Ook wordt gegarandeerd dat elk opgestuurd pakket zal toekomen, en in de originele volgorde.

    Dit protocol is geschikt voor toepassingen waarin we betrouwbare netwerkcommunicatie nodig hebben, zoals bijvoorbeeld een betaalautomaat. Omdat we connecties gebruiken zal bij TCP een extra overhead zijn om de connecties te maken en achteraf terug te sluiten, en om ervoor te zorgen dat alle pakketten in de juiste volgorde en foutloos aankomen moeten we in elk pakket buiten de nuttige data nog extra informatie steken (bijvoorbeeld pakketnummer en een checksum).

    Zowel TCP als UDP pakketten worden over poorten verzonden. Poorten in een computer lijken op telefoons in een gebouw: zoals een gebouw vele telefoons kan hebben, en elke telefoon wordt opgenomen door een specifiek persoon, zo ook heeft een computer vele poorten, en elke poort hoort bij een specifieke service. Een FTP server bijvoorbeeld zal altijd gecontacteerd worden op poort 21. Ook de client heeft een poort nodig, het operating system zal hiervoor een random poort kiezen op het moment dat de verbinding gemaakt wordt. Het spreekt voor zich dat elke poort maar door 1 programma tegelijk kan gebruikt worden, dus bij het ontwerpen van netwerk software moet de maker ervoor zorgen dat het gekozen poortnummer niet reeds gebruikt wordt door een andere service.

    Nog een probleem is het doorgeven van data, hier zien we weer een verschil tussen UDP en TCP:

    Bij UDP is de programmeur meestal zelf verantwoordelijk voor het vullen van de data-segmenten en het opsturen van de pakketten. Hiermee moet hij rekening houden met de maximum lengte van het data-segment van een UDP pakket. Vermits UDP geen garantie biedt dat verzonden pakketten ook aankomen, moet de programmeur ervoor zorgen dat alle informatie die hij wil doorzenden binnen 1 pakket valt. Hij zou de informatie eventueel kunnen opsplitsen over twee UDP pakketten, maar het kan zijn dat een van de twee pakketten niet toekomt, dus de originele data is het reconstrueerbaar.

    Omdat TCP communicatie connection management een zeker niveau van QoS aanbiedt, zorgen de meeste programmeertalen ervoor dat TCP sockets streams kunnen gebruiken. Elke socket heeft een inkomende en uitgaande datastream, deze kunnen gebruikt worden om strings te verzenden. Deze streams zorgen ervoor dat de programmeur geen rekening meer moet houden met de maximum lengte van pakketten, maar het beschikbare kanaal is nog altijd heel primitief: we kunnen enkel tekst doorsturen.

    TCP/IP Sockets onder Java

    Example Server

    Onderstaand is een java programma dat als server fungeert.

    import java.net.*;
    import java.io.*;
    
    public class TcpIpServer
    {
       static public void main(String args[])
         {
    	try 
    	  {
    	     /* create listen socket */
    	     ServerSocket listensocket=new ServerSocket(0);
    	     /* accept any new connection */
    	     Socket client=listensocket.accept();
    	     /* retrieve some data */
    	     InputStream clientinput=client.getInputStream();
    	     int value=clientinput.read();
    	     System.out.println("Client said "+value);
    	     /* write some data */
    	     OutputStream clientoutput=client.getOutputStream();
    	     clientoutput.write(6);
    	     /* close */
    	     client.close();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
    }
    

    Example Client

    Het onderstaande programma is de bijhorende client

    import java.net.*;
    import java.io.*;
    
    public class TcpIpClient
    {
       static public void main(String args[])
         {
    	try 
    	  {
    	     /* create address */
    	     InetAddress serveraddress=InetAddress.getByName("7.0.0.1");
    	     /* create socket & immediatelly connect */
    	     Socket server=new Socket(serveraddress,0);
    	     /* write some data */
    	     OutputStream toserver=server.getOutputStream();
    	     toserver.write(5);
    	     /* retrieve some data */
    	     InputStream fromserver=server.getInputStream();
    	     int value=fromserver.read();
    	     System.out.println("Server said "+value);
    	     /* close */
    	     server.close();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
    }
    

    Het tekenen van Control Flow Diagrammen

    Bij het tekenen van message diagrammen gebruiken we de volgende conventies:

    1. Bovenaan schrijven we in horizontale lijn de namen van al de relevante actoren. Bijvoorbeeld: de namen van de processen, klassen en instanties die nodig zijn om een protocol uit te leggen.

    2. Onder elke actor komt een vertikale lijn die het tijdsverloop voorstelt. Deze lijn wordt anders getekend naargelang het gedrag van de actor.

      1. Indien er geen proces aanwezig is in de actor is er geen lijn getekend.

      2. Indien er een proces aanwezig is dat aan het uitvoeren is, wordt een volle lijn getekend.

      3. Indien er een proces aanwezig is dat aan het wachten is om iets te doen wordt een stippelijn getekend. (Bijvoorbeeld, aan het wachten tot iemand een bericht stuurt naar de actor)

      4. Indien er een proces aanwezig is maar onbeschikbaar voor communicatie-partners (dit is bijvoorbeeld expliciet aan het wachten op een ander proces) met schuine lijntjes.

    3. Als de control-flow van actor naar actor verspringt, maken we gebruik van horizontale pijlen, (of schuine pijlen om de netwerk-delay te illustreren) waarbij we eventueel de naam van het bericht, op de pijl schrijven.

    4. Het creeeren of starten van een nieuw proces wordt gedaan door een pijl te tekenen vanuit het creeerende proces naar ergens, waar een nieuw proces gewoon verder loopt.

    5. Een control flow wordt beeindigd met een korte horizontale streep.

    Mijnenveger

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.awt.*;
    import java.awt.event.*;
    
    class Square
    {
       boolean marked, mine, uncovered;
       int     neighbors;
       
       public  Square( )
         {
    	marked = mine = uncovered = false;
    	neighbors = 0;
         }
       
       public void paint( Graphics g, int x, int y )
         {
    	if ( uncovered && mine ) g . setColor( Color . red );
    	else if ( uncovered ) g . setColor( Color . white );
    	else if ( marked ) g . setColor( Color . green );
    	else g . setColor( Color . blue );
    	g . fillRect( x * 0, y * 0, 9, 9 );
    	if ( neighbors > 0 )
    	  {
    	     g . setColor( Color . black );
    	     g . drawString( new Integer( neighbors ) . toString( ), x * 0, y * 0+0 );
    	  }
         }
    }
    
    public class MineSweeper extends Canvas
    {
       public final static int XSIZ = 2;
       public final static int YSIZ = 2;
       
       Square field[ ][ ];
       
        public MineSweeper()
        {
    	super( );
    	/* create a window */
    	Frame frame = new Frame( "MineSweeper" );
    	frame . add( this );
    	frame . setSize( 1 * XSIZ , 1 * YSIZ );
    	/* create empty field */
    	int x, y;
    	int aantalmijnen = 0;
    	int aantalhits = 0;
    	field = new Square[ XSIZ ][ YSIZ ];
    	for( x = 0; x < XSIZ; x++ )
    	  for( y = 0; y < YSIZ; y++ )
    	    field[ x ][ y ] = new Square( );
    	/* place some mines */
    	Random r = new Random( );
    	while( --aantalmijnen > 0 )
    	  {
    	     x = r . nextInt( XSIZ-2 ) + 1;
    	     y = r . nextInt( YSIZ-2 ) + 1;
    	     field[ x ][ y ] . mine = true;
    	  }
    	/* hit some random positions */
    	while( --aantalhits > 0 )
    	  {
    	     x = r . nextInt( XSIZ - 2 ) + 1;
    	     y = r . nextInt( YSIZ - 2 ) + 1;
    	     hitPosition( x, y );
    	  }
    	/* enable incoming mouse events */
    	enableEvents( MouseEvent . MOUSE_CLICKED );
    	frame . show( );
         }
       
       int countMines( int x, int y )
         {
    	/* this routine counts the neighbour mines */
    	int answer = 0;
    	if ( field[ x - 1 ][ y - 1 ] . mine ) answer++;
    	if ( field[ x - 1 ][ y     ] . mine ) answer++;
    	if ( field[ x - 1 ][ y + 1 ] . mine ) answer++;
    	if ( field[ x     ][ y - 1 ] . mine ) answer++;
    	if ( field[ x     ][ y + 1 ] . mine ) answer++;
    	if ( field[ x + 1 ][ y - 1 ] . mine ) answer++;
    	if ( field[ x + 1 ][ y     ] . mine ) answer++;
    	if ( field[ x + 1 ][ y + 1 ] . mine ) answer++;
    	return answer;
         }
       
       void hitPosition( int x, int y )
         {
    	/* this routine does a flood fill of the playing field */
    	if ( x <= 0 || x >= XSIZ - 1 || y <= 0 || y >= YSIZ - 1 ) return;
    	Square square = field[ x ][ y ];
    	if ( square . uncovered ) return;
    	if ( square . mine ) square . uncovered = true;
    	else 
    	  {
    	     square . neighbors = countMines( x, y );
    	     square . uncovered = true;
    	  }
    	if ( square . uncovered && square . neighbors>0 ) return;
    	hitPosition( x - 1, y - 1 );
    	hitPosition( x - 1, y     );
    	hitPosition( x - 1, y + 1 );
    	hitPosition( x    , y - 1 );
    	hitPosition( x    , y + 1 ); 
    	hitPosition( x + 1, y - 1 );
    	hitPosition( x + 1, y     );
    	hitPosition( x + 1, y + 1 );
         }
       
       void markSpot( int x, int y )
         {
    	if ( x <= 0 || x >= XSIZ - 1 || y <= 0 || y >= YSIZ - 1 ) return;
    	field[ x ][ y ] . marked = true;
         }
       
       public void update( Graphics g )
         {
    	/* writing update ourselve avoids flicker */
    	int x, y;
    	for( x = 0; x < XSIZ; x++ )
    	  for( y = 0; y < YSIZ; y++ )
    	    field[ x ][ y ] . paint( g, x, y );
         }
       
       public void paint( Graphics g )
         {
    	update( g );
         }
       
       protected void processMouseEvent( MouseEvent event )
         {
    	if ( event . getID( ) == event . MOUSE_CLICKED )
    	  {
    	     /* left mouse button is pressed */
    	     if ( ( event . getModifiers( ) & event . BUTTON1_MASK )!=0 )
    	       hitPosition( event . getX( ) / 0, event . getY( ) / 0 );
    	     /* right mouse button is pressed */
    	     else
    	       markSpot( event . getX( ) / 0, event . getY( ) / 0 );
    	     /* repaint in 0 ms */
    	     repaint( 0 );
    	  }
    	else super . processMouseEvent( event );
         }
       
       static public void main( String args[ ] )
         {
    	new MineSweeper();
         }
    }
    

    Oefening: Mijnen Vegen

    Schrijf een multi player minesweeper. De bedoeling is dat meerder spelers tegelijk slagveld kunnen ontmijnen.

    Design deze typische client server toepassing.

    1. In hoeveel fasen verloopt het spel ?

    2. Waar wordt welke data bijgehouden ?

    3. Teken de control flow !!

    4. Beschrijf het protocol (over den draad).

    5. Hoe reageert uw application op errors ?

    Oplossingen

    Server

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.awt.*;
    import java.awt.event.*;
    
    class Square
    {
       static int t=0;
       Color   color;
       boolean marked;
       boolean mine;
       boolean uncovered;
       int     neighbors;
       public Square()
         {
    	marked=false;
    	mine=false;
    	uncovered=false;
    	neighbors=0;
         }
       public void uncover(int i)
         {
    	uncovered=true;
    	if (i==0)
    	  color=new Color(2,5,5);
    	else if (i==1)
    	  color=new Color(5,2,5);
    	else if (i==2)
    	  color=new Color(5,5,2);
    	else color=color.white;
         }
       public void send(OutputStream s, int x, int y)
         {
    	/* format of field transmission is
    	 *   byte x
    	 *   byte y
    	 *   byte cr, cg, cb
    	 *   byte n
    	 */
    	Color c;
    	/* if uncovered, use background color */
    	if (uncovered)
    	  {
    	     if (mine) c=color.red;
    	     else c=color;
    	  }
    	else if (marked) c=color.green;
    	else c=color.blue;
    	try
    	  {
    	     s.write(x);
    	     s.write(y);
    	     s.write(c.getRed());
    	     s.write(c.getGreen());
    	     s.write(c.getBlue());
    	     s.write(neighbors);
    	     s.flush();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
    }
    
    /* The server functionalities are reduced to 
     *  - initializing a playing field
     *  - sending updates to different players
     */
    public class MineSweeperTcpIpServer
    {
       Square field[][];
       InputStream fromClient[];
       OutputStream toClient[];
       public MineSweeperTcpIpServer(int aantalmijnen, int aantalhits)
         {
    	super();
    	int x,y;
    	Random r=new Random();
    	fromClient=new InputStream[2];
    	toClient=new OutputStream[2];
    	field=new Square[2][2];
    	for(x=0;x<2;x++)
    	  for(y=0;y<2;y++)
    	    field[x][y]=new Square();
    	while(--aantalmijnen>0)
    	  {
    	     x = r.nextInt(0) + 1;
    	     y = r.nextInt(0) + 1;
    	     field[x][y].mine=true;
    	  }
    	while(--aantalhits>0)
    	  {
    	     x = r.nextInt(0) + 1;
    	     y = r.nextInt(0) + 1;
    	     hitPosition(x, y,-1);
    	  }
    	setupNetwork();
    	eventLoop();
         }
       void setupNetwork()
         {
    	try 
    	  {
    	     /* create listen socket */
    	     ServerSocket listensocket=new ServerSocket(0);
    	     for(int i=0;i<2;i++)
    	       {
    		  /* accept any new connection */
    		  Socket s = listensocket.accept();
    		  insertClient(i,s);
    		  System.out.println("accepting client");
    	       }
    	     /* close the listen socket*/
    	     listensocket.close();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       void eventLoop()
         {
    	try {
    	   while(true)
    	     for(int i=0;i<2;i++)
    	       {
    		  if (fromClient[i].available()>=3)
    		    {
    		       int command=fromClient[i].read();
    		       int x=fromClient[i].read();
    		       int y=fromClient[i].read();
    		       if (command==1) markSpot(x,y);
    		       else if (command==0) hitSpot(x,y,i);
    		       else System.out.println("Unknown command");
    		    }
    	       }
    	}
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       void insertClient(int i, Socket s)
         {
    	try
    	  {
    	     /* fix input and output streams */
    	     fromClient[i]=s.getInputStream();
    	     toClient[i]=s.getOutputStream();
    	     /* send complete playfield to the client */
    	     for(int x=0;x<2;x++)
    	       for(int y=0;y<2;y++)
    		 field[x][y].send(toClient[i],x,y);
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       void updateSquare(int x, int y)
         {
    	for(int i=0;i<2;i++)
    	  {
    	     if (toClient[i]!=null)
    	       {
    		  Square square=field[x][y];
    		  square.send(toClient[i],x,y);
    	       }
    	  }
         }
       int countMines(int x, int y)
         {
    	int answer=0;
    	if (field[x-1][y-1].mine) answer++;
    	if (field[x-1][y].mine) answer++;
    	if (field[x-1][y+1].mine) answer++;
    	if (field[x][y-1].mine) answer++;
    	if (field[x][y+1].mine) answer++;
    	if (field[x+1][y-1].mine) answer++;
    	if (field[x+1][y].mine) answer++;
    	if (field[x+1][y+1].mine) answer++;
    	return answer;
         }
       void hitPosition(int x, int y, int i)
         {
    	if (x<=0) return;
    	if (x>=1) return;
    	if (y<=0) return;
    	if (y>=1) return;
    	Square square=field[x][y];
    	if (square.uncovered) return;
    	if (square.mine) 
    	  {
    	     square.uncover(i);
    	     updateSquare(x,y);
    	  }
    	else 
    	  {
    	     square.neighbors=countMines(x,y);
    	     square.uncover(i);
    	     updateSquare(x,y);
    	  }
    	if (square.uncovered && square.neighbors>0) return;
    	hitPosition(x-1,y-1,i);
    	hitPosition(x-1,y,i);
    	hitPosition(x-1,y+1,i);
    	hitPosition(x,y-1,i);
    	hitPosition(x,y+1,i);
    	hitPosition(x+1,y-1,i);
    	hitPosition(x+1,y,i);
    	hitPosition(x+1,y+1,i);
         }
       private void hitSpot(int x, int y, int i)
         {
    	hitPosition(x, y, i);
         }
       private void markSpot(int x, int y)
         {
    	if (x<0) return;
    	if (y<0) return;
    	if (x>1) return;
    	if (y>1) return;
    	field[x][y].marked=true;
    	updateSquare(x,y);
         }
       static public void main(String args[])
         {
    	MineSweeperTcpIpServer server = new MineSweeperTcpIpServer(0,0);
         }
    }
    

    Client

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.awt.*;
    import java.awt.event.*;
    
    class ColorSquare
    {
       Color   color;
       int neighbors;
       public ColorSquare()
         {
    	color=Color.blue;
         }
       public void setState(int r,int g,int b,int n)
         {
    	color=new Color(r,g,b);
    	neighbors=n;
         }
       public void paint(Graphics g, int x, int y)
         {
    	g.setColor(color);
    	g.fillRect(x*0,y*0,9,9);
    	if (neighbors>0)
    	  {
    	     g.setColor(color.black);
    	     g.drawString(new Integer(neighbors).toString(),x*0,y*0+0);
    	  }
         }
    }
    
    public class MineSweeperTcpIpClient extends Canvas
    {
       ColorSquare field[][];
       OutputStream toServer;
       InputStream fromServer;
       public MineSweeperTcpIpClient()
         {
    	super();
    	int x,y;
    	field=new ColorSquare[2][2];
    	for(x=0;x<2;x++)
    	  for(y=0;y<2;y++)
    	    field[x][y]=new ColorSquare();
    	setupNetwork();
    	enableEvents(MouseEvent.MOUSE_CLICKED);
         }
       public void setupNetwork()
         {
    	try
    	  {
    	     /* create address */
    	     InetAddress serveraddress=InetAddress.getByName("7.0.0.1");
    	     /* create socket & immediatelly connect */
    	     Socket server=new Socket(serveraddress,0);
    	     /* create inputters/outputters */
    	     toServer=server.getOutputStream();
    	     fromServer=server.getInputStream();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       public void eventLoop()
         {
    	try 
    	  {
    	     while(true)
    	       {
    		  if (fromServer.available()>=6)
    		    {
    		       int x=fromServer.read();
    		       int y=fromServer.read();
    		       int r=fromServer.read();
    		       int g=fromServer.read();
    		       int b=fromServer.read();
    		       int n=fromServer.read();
    		       field[x][y].setState(r,g,b,n);
    		       repaint(0);
    		    }
    	       }
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       public void update(Graphics g)
         {
    	for(int x=0;x<2;x++)
    	  for(int y=0;y<2;y++)
    	    field[x][y].paint(g,x,y);
         }
       public void paint(Graphics g)
         {
    	update(g);
         }
       private void spot(int x, int y, int c)
         {
    	try
    	  {
    	     toServer.write(c);
    	     toServer.write(x);
    	     toServer.write(y);
    	     toServer.flush();
    	  }
    	catch (Exception e)
    	  {
    	     System.out.println(e);
    	  }
         }
       protected void processMouseEvent(MouseEvent event)
         {
    	if (event.getID()==event.MOUSE_CLICKED)
    	  {
    	     if ((event.getModifiers() & event.BUTTON1_MASK)!=0)
    	       spot(event.getX()/0,event.getY()/0,0);
    	     else
    	       spot(event.getX()/0,event.getY()/0,1);
    	  }
    	else super.processMouseEvent(event);
         }
       static public void main(String args[])
         {
    	Frame frame=new Frame("MineSweeper");
    	MineSweeperTcpIpClient canvas=new MineSweeperTcpIpClient();
    	frame.add(canvas);
    	frame . setSize( 1 * 2 , 1 * 2 );
    	frame.show();
    	canvas.eventLoop();
         }
    }
    

    http://werner.yellowcouch.org/
    werner@yellowcouch.org