Network Sockets

De la WikiLabs
Jump to navigationJump to search

The Java language, through the API library, provides native support for network communication. The easiest way to communicate between two machines is by using netowrk sockets. Sockets are network communication mechanisms (developed at The Berkeley University of California) which can use any communication protocol, although usually they're using the ones defined in the TCP/IP stack.

Any computer connected to the Internet needs to have a address by which it is identified, called an IP (internet protocol) address. A IPv4 address (version 4) is made up of 4 bytes and the external representation is done by writing the four bytes separated by dots (E.g.: 192.168.0.1). Besides an IP address, a computer can also have symbolic names, grouped in domains (E.g.: dcae.pub.ro), and the correspondence between IP addresses and symbolic names are stored in a distributed database, accessed through the domain name service (DNS).

In Java, classes and interfaces related to network programming are stored in package java.net. An IP address is represented through class InetAddress, which contains both the IP address (in numerical format) as well as the symbolic corresponding name. This class does not have a public constructor, so an object of this type can be created by calling some of the class's static methods: getByName(), getLocalHost() or getAllByName() which return the reference to the newly created object. For example, the prototype for method getByName() is:

public InetAddress getByName (String host) throws UnknownHostException;

The argument of the method (host), is a String representing the name of the machine as a symbolic name (E.g.: www.upb.ro) or as a textual representation of the IP address ("141.85.94.1").

Sockets can be used in one of the two possible ways:

  • unconnected sockets (UDP protocol);
  • connected sockets (TCP protocol).

Unconnected sockets send packets using the UDP protocol (User Datagram Protocol) between processes which don't have a permanent link.

Connected sockets first establish a connection between sockets belonging to different processes. Connected sockets use the TCP protocol (Transmission Control Protocol) which is a reliable protocol (with error checking and re-transmission), unlike UDP which is not reliable.

In order for multiple applications to access the network at the same time, packages for each application need to be separate. This is achieved by using communication ports. A port is a 16-bit number and every application uses a unique one to communicate. A connection has two ports, one on each side of connection. The client port is allocated by the operating system, when the connection is initiated, and the server port is constant and depends on the service provided. Each standard protocol has an associated port number according to the list maintained by Internet Assigned Numbers Authority. Examples are:

  • port 20 - File Transfer Protocol (data)
  • port 21 - File Transfer Protocol (control)
  • port 21 - Secure Shell (SSH)
  • port 25 - Simple Mail Transfer Protocol (SMTP)
  • port 80 - Hyper Text Transfer Protocol (HTTP)
  • etc.

Here you have the complete list.

In a Linux operating system, ports from 0 to 1023 are reserved by the kernel and are not accessible unless you have root privileges. In Linux, to find out what ports are opened (used), you can use the nmap command, if it's installed:

nmap arh.pub.ro

Client Sockets

Generally, a network connection is established from a host called client to another host called server. For a client to connect to a server, it needs two pieces of information: the address of the server and the port on which the service application listens to. The class which implements the client functionality in Java is called java.net.Socket. Any object of type Socket, after instantiation, has two associated streams:

  • the InputStream from which you can read data coming from the other side of the connection, accessible by method Socket.getInputStream();
  • the OutputStream on which you can write data to the other side of the connection, accessible by method Socket.getOutputStream();
import java.net.*;
import java.io.*;

public class Client{

public static void main(String[] _args){
    try{
        //connect to arh.pub.ro on the HTTP port
        Socket _clientSocket = new Socket("arh.pub.ro", 80);
        OutputStream _outputStream = _clientSocket.getOutputStream();
        InputStream _inputStream = _clientSocket.getInputStream();

        _outputStream.write("GET /java.txt\n".getBytes());

        int _char;
        while((_char = _inputStream.read()) != -1){
            System.out.print((char)_char);
        }

        _clientSocket.close();
    }catch(IOException _ioe){
        System.out.println("Communication problem: " + _ioe.getMessage());
    }
}

}

Socket de server

Rolul unui socket de server este de a accepta conexiuni pe la unul sau mai multi clienți. Clasa care implementează acest comportament este java.net.ServerSocket. Pentru ca un ServerSocket să poată funcționa, are nevoie de o singură informație, și anume portul pe care să accepte conexiuni. Pentru a efectua o conectare concretă, clasa conține o metodă numită accept().

Atenție: Metoda ServerSocket.accept() este blocantă! Asta implică faptul că odată intrat în metodă, programul se blochează până în momentul în care un client încearcă să se conecteze. În acel moment abia, metoda accept() întoarce un obiect de tip Socket asociat acelei conexiuni. În același timp, un client nu se poate conecta atâta timp cât serverul nu este blocat în metoda accept().


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

public class Server{

public static void main(String[] _args){
    try{
        // start a new server on port 9000 (must be higher or equal to 1024)
        ServerSocket _serverSocket = new ServerSocket(9000);

        // listen to an incoming connection and creating a communication socket
        // accept() is blocking
        System.out.print("Listening for inc. connections... ");
        Socket _socket = _serverSocket.accept();
        System.out.println("connected!");

        // get the streams associated with the socket
        // output one always first
        OutputStream _outputStream = _socket.getOutputStream();
        InputStream _inputStream = _socket.getInputStream();

        // read the input char by char until newline char (\n)
        // we could do the same thing easier using an InputStreamReader
        // and a BufferedReader
        String _command = "";
        char _char;
        while((_char = (char)_inputStream.read()) != '\n'){
            _command = _command + _char;
        }

        // send a response depending on the received string 
        if(_command.equals("GET /java.txt")){
            _outputStream.write("Congratulations, you have received a message from a Java server!\n".getBytes());
        }else{
            _outputStream.write("Congratulations, you still received a message from a Java server, even though you sent the wrong text!\n".getBytes());
        }

        _socket.close();
        _serverSocket.close();
    }catch(IOException _ioe){
        System.out.println("Communication problem: " + _ioe.getMessage());
    }
}

}

Puteți verifica funcționarea acestui server modificând clientul pentru a se conecta la "localhost", pe portul 9000 și rulând ambele programe pe același calculator, în console diferite.

Pentru a putea programa un server care să administreze mai mulți clienți simultan, trebuie obligatoriu create mai multe fire de execuție (thread-uri).