Obsługa zdarzeń i logika aplikacji

Activity naszej aplikacji jest napisany w języku Java i znajduje się w pliku /app/src/main/java/com.delta.tango.mod3/MainActivity.java. Żeby obsłużyć zdarzenie polegające na naciśnięciu przycisku b1, w klasie MainActivity musimy zaimplementować metodę obsługi którą przypisaliśmy do tego przycisku (activity_main.xml).

Programowanie aplikacji sieciowych jest jednym z podstawowych zastosowań platformy Java. Klasy biblioteczne służące do tego celu znajdują się w pakiecie java.net. Oprócz tego pakietu będziemy jeszcze potrzebować strumieni danych. Podstawowy system obsługi wejścia-wyjścia dla platformy Java jest zawarty w pakiecie java.io. Jest on oparty o cztery abstrakcyjne klasy biblioteczne InputStream, OutputStream, Reader i Writer (oraz odpowiednie klasy konkretne, takie jak BufferedReader, czy PrintWriter). InputStream i OutputStream reprezentują strumienie binarne, natomiast Reader i Writer reprezentują strumienie tekstowe. W naszym przypadku komunikator internetowy będzie przesyłał dane tekstowe, w związku z czym wykorzystamy strumienie tekstowe.

Żeby utworzyć gniazdo sieciowe (Socket) musimy podać adres IP serwera oraz numer portu (liczba całkowita z przedziału 0 do 65535 - numery portów od 0 do 1023 są określane jako „well known ports” i zarezerwowane na standardowe usługi takie, jak www, ssh czy poczta elektroniczna, numery od 1024 do 49151 są określane jako „registered”, a numery od 49152 do 65535 jako „dynamic/private”).

package com.delta.tango.n2;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.view.View;

public class MainActivity extends Activity
{
   private String host = "192.168.0.7";
   private int port = 7000;
   private TextView display;

   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      display = (TextView)findViewById(R.id.tv1);
   }

   public void sendPressed(View v)
   {
      try
      {
         java.net.Socket socket = new java.net.Socket(host,port);
         java.io.InputStream is=socket.getInputStream();
         java.io.BufferedReader in=new java.io.BufferedReader(new
                                                       java.io.InputStreamReader(is));
         in.readLine();
         String msg = in.readLine();
         display.setText(msg);
         socket.close();
      }
      catch(java.net.UnknownHostException e1)
      {
         display.setText("Unknown host: " + host);
      }
      catch(java.io.IOException e2)
      {
         display.setText("IOException: " + e2);
      }
   }
}

Część implementacji klasy MainActivity została opuszczona dla przejrzystości. Żeby umożliwić naszej aplikacji dostęp do sieci, konieczne będzie jeszcze umieszczenie odpowiedniego wpisu w pliku AndroidManifest.xml aplikacji.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.delta.tango.n2" >
   <uses-permission android:name="android.permission.INTERNET" />

   ...

</manifest>

W naszym przypadku, aplikacja mobilna będzie aplikacją kliencką - będzie nawiązywała połączenie i wymieniała komunikaty z serwerem. Poniżej znajdziesz model prostej aplikacji serwerowej w języku Java, w postaci aplikacji desktopowej. Twoim zadaniem będzie także rozwinięcie i dostosowanie tego serwera w taki sposób, żeby wymieniał informację z aplikacją mobilną według uzgodnionego protokołu Twojego własnego pomysłu.

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

public class Serwer
{
   public static final int PORT=50007;

   public static void main(String args[]) throws IOException
   {
      //tworzenie gniazda serwerowego
      ServerSocket serv;
      serv=new ServerSocket(PORT);

      //oczekiwanie na polaczenie i tworzenie gniazda sieciowego
      System.out.println("Nasluchuje: "+serv);
      Socket sock;
      sock=serv.accept();
      System.out.println("Jest polaczenie: "+sock);

      //tworzenie strumienia danych pobieranych z gniazda sieciowego
      BufferedReader inp;
      inp=new BufferedReader(new InputStreamReader(sock.getInputStream()));

      //tworzenie strumieni danych pobieranych z klawiatury i dostarczanych do socketu
      BufferedReader klaw;
      klaw=new BufferedReader(new InputStreamReader(System.in));
      PrintWriter outp;
      outp=new PrintWriter(sock.getOutputStream());

      //komunikacja - czytanie danych ze strumienia
      String str;
      str=inp.readLine();
      System.out.println("<Nadeszlo:> " + str);

      //komunikacja - czytanie danych z klawiatury i przekazywanie ich do strumienia
      System.out.print("<Wysylamy:> ");
      String str=klaw.readLine();
      outp.println(str);
      outp.flush();

      //zamykanie polaczenia
      inp.close();
      sock.close();
      serv.close();
   }
}

Ćwiczenie 1

Na podstawie przykładu napisz implementację komunikatora internetowego, który będzie pozwalał na wprowadzanie kolejnych komunikatów z klawiatury i przesyłaniu ich do serwera.

Ćwiczenie 2

Napisz prostą implementację tekstowego komunikatora internetowego działającego w trybie simpleks. Twoja implementacja powinna obejmować następujące etapy

  1. nawiązanie połączenia,
  2. utworzenie obiektów reprezentujących strumienie danych pobieranych z klawiatury i z gniazda sieciowego oraz strumienie danych dostarczanych do gniazda sieciowego,
  3. etap właściwej komunikacji w trybie simpleks (pętla wykonująca na przemian odbieranie i wysyłanie komunikatów tekstowych) oraz
  4. zakończenie komunikacji i
  5. zamknięcie połączenia.

Komunikacja powinna się odbywać do momentu przesłania przez którąkolwiek ze stron komunikatu koniec albo KONIEC (pomocna może tu być metoda equalsIgnoreCase() klasy String). Po przesłaniu takiego komunikatu zarówno klient jak i serwer powinny wypisać komunikat Koniec polaczenia i zakończyć działanie.

Ćwiczenie 3

Po stronie klienta zidentyfikuj i obsłuż wyjątek (try{}catch()) który wystąpi np. wtedy kiedy klient próbuje nawiązać połączenie z serwerem w sytuacji kiedy pod wskazanym adresem i numerem portu nie działa żaden serwer. W przypadku wystąpienia tego wyjątku Twój komunikator powinien wypisać komunikat Połączenie zostalo przerwane i zakończyć działanie.

Ćwiczenie 4

Po stronie klienta obsłuż wyjątek java.net.SocketException (try/catch) oznaczający wystąpienie błędu związanego z protokołem TCP.

Ćwiczenie 5

Poniżej znajdziesz szkielet implementacji komunikatora internetowego działającego w trybie duplex (komunikaty mogą być wysyłane i odbierane równolegle i w dowolnych sekwencjach, niekoniecznie na przemian). W tym przypadku wysyłanie komunikatów i odbieranie komunikatów musi odbywać się w oddzielnych wątkach.

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

class Odbior extends Thread   {
   Socket sock;
   BufferedReader sockReader;

   public Odbior(Socket sock) throws IOException
   {
      this.sock=sock;
      this.sockReader=new BufferedReader(new InputStreamReader(sock.getInputStream()));
   }

   public void run()
   {

   }
}

public class Serwer
{
   public static final int PORT=50007;

   public static void main(String args[]) throws IOException
   {
      //tworzenie gniazda serwerowego
      ServerSocket serv;
      serv=new ServerSocket(PORT);

      //oczekiwanie na polaczenie i tworzenie gniazda sieciowego
      System.out.println("Nasluchuje: "+serv);
      Socket sock;
      sock=serv.accept();
      System.out.println("Jest polaczenie: "+sock);

      //tworzenie watka odbierajacego
      new Odbior(sock).start();

      //zamykanie polaczenia
      serv.close();
      sock.close();
   }
}

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

class Odbior extends Thread
{
   Socket sock;
   BufferedReader sockReader;

   public Odbior(Socket sock) throws IOException
   {
      this.sock=sock;
      this.sockReader=new BufferedReader(new InputStreamReader(sock.getInputStream()));
   }

   public void run()
   {

   }
}

public class Klient
{
   public static final int PORT=50007;
   public static final String HOST = "127.0.0.1";

   public static void main(String[] args) throws IOException
   {
      //nawiazanie polaczenia z serwerem
      Socket sock;
      sock=new Socket(HOST,PORT);
      System.out.println("Nawiazalem polaczenie: "+sock);

      //tworzenie watka odbierajacego
      new Odbior(sock).start();

      //zamykanie polaczenia
      sock.close();
   }
}

Na podstawie powyższego przykładu napisz implementację komunikatora internetowego działającego w trybie duplex (komunikaty mogą być wysyłane i odbierane równolegle, niekoniecznie na przemian). W tym przypadku wysyłanie komunikatów i odbieranie komunikatów musi odbywać się w oddzielnych wątkach. Trzeba w tym celu napisać klasę która rozszerza klasę biblioteczną java.lang.Thread i umieścić instrukcje składające się na cykl życia wątku w metodzie run() tej klasy. W wątku głównym trzeba utworzyć instancję tej klasy i wywołać na jego rzecz metodę start(). Twój komunikator powinien działać do momentu, kiedy jedna ze stron prześle komunikat koniec albo KONIEC, a potem wypisać odpowiedni komunikat i zakończyć działanie.

Ćwiczenie 6

Poniżej znajdziesz szkielet implementacji serwera wielowątkowego, który oczekuje na połączenia klientów, a następnie dla każdego klienta tworzy oddzielny wątek który zajmie się obsłużeniem tego klienta.

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

class ObslugaZadania extends Thread
{
   Socket sock;

   ObslugaZadania(Socket klientSocket)
   {
      this.sock=klientSocket;
   }

   public void run()
   {

   }
}

public class Serwer
{
   public static void main(String[] args) throws IOException
   {
      ServerSocket serv=new ServerSocket(80);
      while(true)
      {
         //przyjecie polaczenia
         System.out.println("Oczekiwanie na polaczenie...");
         Socket sock=serv.accept();

         //tworzenie watku obslugi tego polaczenia
         new ObslugaZadania(sock).start();
      }
   }
}

Na podstawie powyższego przykładu napisz implementację serwera który będzie czekał na zgłoszenia klientów, przyjmował połączenia, a następnie przekazywał komunikaty otrzymane od dowolnego klienta do wszystkich przyłączonych mobilnych aplikacji klienckich. Powinieneś w tym celu zaimplementować klasę której obiekt będzie wątkiem reprezentującym obsługę danego klienta. Po nawiązaniu połączenia z kolejnym klientem, Twój serwer powinien utworzyć wątek który będzie porozumiewał się z tym klientem. Obiekty reprezentujące wątki możesz przechowywać za pomocą bibliotecznej implementacji listy java.util.ArrayList.

Następna część - Wstęp