Cal entendre que un sistema distribuït no implica necessàriament una col·laboració entre màquines diferents sinó que pot ser entre processos diferents (IPC):
Bàsicament veurem exemples d'arquitectura client-servidor però sense obviar altres arquitectures distribuïdes com Peer To Peer (P2P).
Els dos protocols de nivell de transport més utilitzats són TCP i UDP.
Connection-oriented
La comunicació es duu a terme en tres fases:
Encaixada de mans TCP (Handshake)
Acabament de la connexió
Estats: en els protocols orientats a connexió, la connexió passa per diferents estats.
Exemple de connexió a la web de la UPC i ús de la comanda netstat:
[email protected]:~$ netstat --inet -t -a -c | grep upc tcp 0 0 A202PC04.aula202.:53571 www.upc.es:https TIME_WAIT tcp 0 0 A202PC04.aula202.:39935 www.upc.es:http ESTABLECIDO tcp 0 0 A202PC04.aula202.:53646 www.upc.es:https ESTABLECIDO tcp 0 0 A202PC04.aula202.:39878 www.upc.es:http TIME_WAIT tcp 0 0 A202PC04.aula202.:39860 www.upc.es:http TIME_WAIT tcp 0 0 A202PC04.aula202.:39938 www.upc.es:http ESTABLECIDO tcp 0 0 A202PC04.aula202.:39893 www.upc.es:http TIME_WAIT tcp 0 0 A202PC04.aula202.:39901 www.upc.es:http TIME_WAIT tcp 0 0 A202PC04.aula202.:39902 www.upc.es:http TIME_WAIT tcp 0 0 A202PC04.aula202.:53649 www.upc.es:https ESTABLECIDO
Control remot amb connexió TCP.
Objecte:
Classe java serializable per a poder enviar-ho per xarxa.
Disposa de constructors, getters, setters i tres atributs:
public class ObjectClient implements Serializable { private static final long serialVersionUID = 1L; protected boolean estatconnexio; protected int accio; protected String missatge; public ObjectClient() { super(); this.estatconnexio = false; } public ObjectClient(boolean tancarlink, int accio, String missatge) { this.estatconnexio = tancarlink; this.accio = accio; this.missatge = missatge; } public boolean getEstatConnexio() { return estatconnexio; } public void setEstatConnexio(boolean estatconnexio) { this.estatconnexio = estatconnexio; } public int getAccio() { return accio; } public void setAccio(int accio) { this.accio = accio; } public String getMissatge() { return missatge; } public void setMissatge(String missatge) { this.missatge = missatge; } }
Servidor:
public class MyServer { /** * @param args * @throws ClassNotFoundException */ @SuppressWarnings("resource") public static void main(String[] args) throws ClassNotFoundException { ServerSocket serverSocket = null; Socket socket = null; ObjectInputStream objectInputStream = null; DataOutputStream dataOutputStream = null; ObjectClient objecte = null; try { serverSocket = new ServerSocket(8888); System.out.println("Listening: 8888"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } boolean estat = false; while (true) { try { socket = serverSocket.accept(); objectInputStream = new ObjectInputStream(socket.getInputStream()); dataOutputStream = new DataOutputStream(socket.getOutputStream()); objecte = (ObjectClient) objectInputStream.readObject(); String[] comanda1= {"/bin/bash", "-c", "/usr/bin/gnome-terminal"}; String[] comanda2= {"/bin/bash", "-c", "/usr/bin/nautilus"}; String[] comanda3= {"/bin/bash", "-c", "/usr/bin/gedit"}; if (objecte.getAccio() == 1) { Process process = Runtime.getRuntime().exec(comanda1); } else if (objecte.getAccio() == 2) { Process process = Runtime.getRuntime().exec(comanda2); } else if (objecte.getAccio() == 3) { Process process = Runtime.getRuntime().exec(comanda3); } if (objecte.getEstatConnexio() == true) { estat = true; } System.out.println("IP: " + socket.getInetAddress()); System.out.println("Missatge: " + objecte.getMissatge()); Date myDate = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy:HH-mm-ss"); String dataString = sdf.format(myDate); dataOutputStream.writeUTF(dataString + "Connexió establerta! Comanda executada"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (objectInputStream != null) { try { objectInputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (dataOutputStream != null) { try { dataOutputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }
Client:
public class MainActivity extends Activity { EditText ip, port, missatge; ToggleButton tancar; Spinner opcions; TextView textIn; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tancar = (ToggleButton) findViewById(R.id.closelink); opcions = (Spinner) findViewById(R.id.spinner); String[] seleccio = {"Obrir terminal","Obrir editor de text","Obrir navegador"}; ArrayAdapter<String> adaptador = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, seleccio); adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); opcions.setAdapter(adaptador); Button buttonSend = (Button) findViewById(R.id.send); ip = (EditText) findViewById(R.id.ip); port = (EditText) findViewById(R.id.port); missatge = (EditText) findViewById(R.id.missatge); textIn = (TextView) findViewById(R.id.textin); buttonSend.setOnClickListener(buttonSendOnClickListener); if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() .permitAll().build(); StrictMode.setThreadPolicy(policy); } } Button.OnClickListener buttonSendOnClickListener = new Button.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub Socket socket = null; ObjectOutputStream objectOutputStream = null; DataInputStream dataInputStream = null; ObjectClient objecte = new ObjectClient(); try { socket = new Socket(ip.toString(), Integer.parseInt(port.toString())); objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); dataInputStream = new DataInputStream(socket.getInputStream()); Date myDate = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy:HH-mm-ss"); String dataString = sdf.format(myDate); objecte.setMissatge(dataString + missatge.getText().toString()); int numaccio = 0; if (opcions.getSelectedItem().toString() == "Obrir terminal") { numaccio = 1; } else if (opcions.getSelectedItem().toString() == "Obrir editor de text") { numaccio = 2; } else if (opcions.getSelectedItem().toString() == "Obrir navegador") { numaccio = 3; } objecte.setAccio(numaccio); boolean estat = false; if (tancar.isChecked()) { estat = true; } else { estat = false; } objecte.setEstatConnexio(estat); objectOutputStream.writeObject(objecte); textIn.setText(dataInputStream.readUTF()); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (dataInputStream != null) { try { dataInputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }; }
Layout del client:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ToggleButton android:id="@+id/closelink" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Obrir/Tancar connexió" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Introdueix la IP" /> <EditText android:id="@+id/ip" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Introdueix el port" /> <EditText android:id="@+id/port" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Introdueix el missatge" /> <EditText android:id="@+id/missatge" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/send" android:layout_width="158dp" android:layout_height="wrap_content" android:text="Enviar missatge" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Tria l'acció:" /> <Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/textin" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
Imatge de l'aplicació client:
Datagrames
User Datagram Protocol (UDP)
Certes aplicaciones no necessiten la fiabilitat d'una connexió punt a punt que proveix un canal TCP. Al contrari per aquelles aplicacions que no sigui imprescindible o en xarxes d'una alta fiabilitat de per si pot ser interessant utilitzar UDP, ja que és un protocol de transmisió de dades molt més ràpid que TCP.
El protocol UDP proveix una forma de comunicació on les aplicacions envien paquets de dades, anomenats datagrames. Un datagrama és un missatge auto-contingut i independent del qual no es pot garantir ni l'arribada, ni l'ordre d'arribada ni tant sol el seu contingut.
El paquet java.net proveix les classes DatagramPacket i DatagramSocket proveixen d'un sistema per a crear datagrames per a qualsevol sistema (recordeu que Java és multiplataforma) utilitzant UDP
IMPORTANT: La implementació concreta del datagrama o socket UDP depén del sistema operatiu on corre l'aplicació Java, en tot cas el codi no cal canviar-lo per adaptar-lo a cada tipus de sistema operatiu
Aquesta clase Java representa un socket que permet enviar paquets de tipus datagrama, típicament utilitzant el protocol UDP.
Quan sigui possible, un objecte acabat de crear de tipus DatagramSocket té l'opció SO_BROADCAST activada i per tant pot rebre datagrames de broadcast.
Exemple:
DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(8888));
És equivalent a.
DatagramSocket s = new DatagramSocket(8888);
Els dos casos crearan un DatagramSocket capaç de rebre broadcasts UDP al port 8888
DatagramSocket java.net.DatagramPacket
Els paquets de tipus datagrama s'utilitzen per implementar un servei de tipus sense connexió. Cada missatge s'encamina de forma independent a la resta i nomes s'utilitza la informació que porta inclosa el propi missatge. Múltiples paquets enviats del mateix tipus d'una màquina a un altre poden ser encaminats de forma diferentm i poden arrivar en diferent ordre (és a dir en diferent ordre respecte a l'ordre de sortida). A més no es garateix l'entrega del paquet.
DatagramPacket java.net.DatagramPacket
Servidor
Primer cal declarar el socket:
protected DatagramSocket socket = null;
Típicament es pot definir com un atribut de la clase servidor.
Fora del bucle infinit (si implementem el servidor com a un servei) cal crear el socket:
socket = new DatagramSocket(4445);
A l'exemple creem un socket UDP al port 4445. El socket s'ha de crear una sola vegada per tant es pot crear al constructor de la clase servidor.
Ara es tracta d'estar rebent continuament paquets de tipus datagrama pel port 4445, per tant dins d'un while infinit:
while (true) { try { byte[] buffer = new byte[256]; // receive request DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); InetAddress address = packet.getAddress(); int port = packet.getPort(); System.out.println("Rebut: " + received + "\nAdreça: " + address + " Port: " + port); } catch (IOException e) { e.printStackTrace(); } }
On
byte[] buf = new byte[256];
És la mida màxima del paquet en bytes que esperem. Ha de ser prou gran per que no es tallin els nostres paquets:
DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet);
És la recepció del paquet.
IMPORTANT: Observem la importància d'alliberar recursos i tancar el socket amb close!
Client
Vegem un exemple d'una sola execució:
public static void main(String[] args) throws IOException, InterruptedException { DatagramSocket socket = new DatagramSocket(); InetAddress address = null; // send request byte[] buffer = new byte[256]; int i = 0; while (i < 50) { String stringToConvert = Integer.toString(i); buffer = stringToConvert.getBytes(); address = InetAddress.getByName("192.168.202.118"); DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 4445); socket.send(packet); Thread.sleep(1000); i++; } socket.close(); }
IMPORTANT: Fem coincidir la mida dels dos buffers tan al costat del client com del servidor per evitar problemes.
Enviament de paquets. Client-servidor. UDP.
Client
public static void main(String[] args) throws IOException, InterruptedException { DatagramSocket socket = new DatagramSocket(); InetAddress address = null; // send request byte[] buffer = new byte[256]; int i = 0; while (true) { String stringToConvert = Integer.toString(i); buffer = stringToConvert.getBytes(); address = InetAddress.getByName("192.168.202.118"); DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 4445); socket.send(packet); Thread.sleep(1000); i++; } socket.close(); }
Servidor
public class Server { protected DatagramSocket socket = null; public Server() throws SocketException { socket = new DatagramSocket(4445); } public void listen() { while (true) { try { byte[] buffer = new byte[256]; // receive request DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); InetAddress address = packet.getAddress(); int port = packet.getPort(); System.out.println("Rebut: " + received + "\nAdreça: " + address + " Port: " + port); } catch (IOException e) { e.printStackTrace(); } } socket.close(); } }
Les adreces IPs estan formades per 32 Bits.
207.142.131.235 correspon als 32 bits: 11001111.10001110.10000011.11101011
Altres notacions:
Un port és una connexió virtual que pot ser utilitzada per les aplicacions per intercanviar dades.
Els ports més comuns són els dels protocols TCP i UDP.
Notació: Decimal (22, 80) o Hexadecimal.
El fitxer /etc/services manté una llista de ports i els seus serveis associats.
El diagrama d'estats complert és el següent:
Servidor:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class MyServer { public static void main(String[] args){ ServerSocket serverSocket = null; Socket socket = null; DataInputStream dataInputStream = null; DataOutputStream dataOutputStream = null; try { serverSocket = new ServerSocket(8888); System.out.println("Listening :8888"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } while(true){ try { socket = serverSocket.accept(); dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream = new DataOutputStream(socket.getOutputStream()); System.out.println("ip: " + socket.getInetAddress()); System.out.println("message: " + dataInputStream.readUTF()); dataOutputStream.writeUTF("Hello!"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ if( socket!= null){ try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if( dataInputStream!= null){ try { dataInputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if( dataOutputStream!= null){ try { dataOutputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }
Client
IMPORTANT: Per evitar l'error NetworkOnMainThreadException desactivem el strictMode [1]
if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }
package com.exercise.AndroidClient; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class AndroidClient extends Activity { EditText textOut; TextView textIn; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textOut = (EditText)findViewById(R.id.textout); Button buttonSend = (Button)findViewById(R.id.send); textIn = (TextView)findViewById(R.id.textin); buttonSend.setOnClickListener(buttonSendOnClickListener); } Button.OnClickListener buttonSendOnClickListener = new Button.OnClickListener(){ @Override public void onClick(View arg0) { // TODO Auto-generated method stub Socket socket = null; DataOutputStream dataOutputStream = null; DataInputStream dataInputStream = null; try { socket = new Socket("192.168.1.101", 8888); dataOutputStream = new DataOutputStream(socket.getOutputStream()); dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream.writeUTF(textOut.getText().toString()); textIn.setText(dataInputStream.readUTF()); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ if (socket != null){ try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (dataOutputStream != null){ try { dataOutputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (dataInputStream != null){ try { dataInputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }}; }
El layout Android és el següent:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <EditText android:id="@+id/textout" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/send" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Send" /> <TextView android:id="@+id/textin" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
També hem de donar permisos a l'aplicació per accedir a Internet afegint el següent codi al fitxer AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
Comprovacions:
nmap -p 8888 192.168.202.156 telnet 192.168.202.156 8888 sudo lsof -i :8888