Laboratorul 1 Retele de calculatoare Socket 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); TCP sockets 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 Life-cycle 1. 2. 3. 4. 5. creation -- a client socket object is created; binding -- the socket's local address is established; connection -- the socket's remote address is established and a connection is made; data exchange; 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 • java.net: • Socket: • bind(SocketAddress) -- used to establish the socket's local address; • connect(SocketAddress) -- used to establish the socket's remote address, and try to establish a connection; • shutdownInput(); • shutdownOutput(); • close() -- used to terminate the connection; • getInputStream() -- used to obtain the input -- incoming -- byte stream; • getOutputStream() -- used to obtain the output -- outgoing -- byte stream; • getLocalSocketAddress() -- used to obtain the local socket address; • getRemoteSocketAddress() -- used to obtain the remote socket address -- who is on the other side; Examples • 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 Life-cycle 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 • 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; Examples • 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 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.net: • InetAddress -- an instance of this class is used as an IP address: • getByName(String) -- used to get an IP address from a DNS name; • getAllByName(String) -- used to get all the IP addresses from a DNS name; • getLocalHost() -- used to obtain the IP address of the computer -- not to be confused with localhost name which always is resolved to 127.0.0.1; getLocalHosts() output is determined by the hosts file. • getAddress() -- used to obtain the raw -- byte array -- address; • getHostAddress() -- used to obtain the name of the IP address; • getCanonicalHostName() -- used to obtain the DNS name of the IP address; • isReachable(int); • InetSocketAddress -- an instance of this class is used as a socket address, and it is a wrapper for an IP address and port: • constructor(InetAddress, int); • getAddress() -- used to obtain only the IP address; • getPort() -- used to obtain only the port; • java.lang: • Integer: • parseInt(String) -- used to parse (obtain) an integer from a string; Java IO generic API • java.io: • InputStream -- is a binary (byte) stream that we can read from: • read(byte, int, int); • close(); • OutputStream -- is a binary (byte) stream that we can write to: • write(byte, int, int); • flush() -- in case there is a buffer holding information, force the send; • close(); • InputStreamReader -- adapts (transforms) a binary input stream into a character output stream; • OutputStreamWriter -- adapts (transforms) a binary output stream into a character output stream; • BufferedReader: • readLine() -- used to read an entire line as a string; • BufferedWriter: • write(String); • flush(); Complete example 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 import import import import import import import java.io.BufferedReader; java.io.BufferedWriter; java.io.InputStreamReader; java.io.OutputStreamWriter; java.net.InetAddress; java.net.InetSocketAddress; 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 import import import import import import import import java.io.BufferedReader; java.io.BufferedWriter; java.io.InputStreamReader; java.io.OutputStreamWriter; java.net.InetAddress; java.net.InetSocketAddress; java.net.ServerSocket; 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 (); }