Java chat sistem

Ivan Mitrović
Web je višekorisničko okruženje, ali ga Java vodi korak dalje: uz malo programiranja, možete interaktivno razmenjivati informacije sa drugom čitaocima neke Web strane. Ograničimo se, za početak, na prenos tekstualnih poruka i isprobajmo Chat u Javi...

 Jedna od najinteresantnijih stvari koje Java nudi programerima jeste mogućnost stvaranja višekorisničkog okruženja. Korisnici mogu da razmenjuju informacije, povezani preko server Slika 1 aplikacije koja je pokrenuta na hostu čija je adresa dostupna - akcije jednog korisnika prikazuju se na ekranima ostalih. Ovakvo klijent/server višekorisničko okruženje pruža ogromne mogućnosti za izradu servisa kakvi se pre upotrebe Jave nisu mogli ni zamisliti.
 Za funkcionisanje chat -a treba isprogramirati server i klijent. Server aplikacija mora da omogući prihvatanje poziva više klijenata na isti port, prijem informacija od klijenata i slanje primljenih informacija svim klijentima odnosno samo određenom klijentu. Klijent mora da neprestano "osluškuje" server i prikazuje poruke servera u obliku koji je prihvatljiv za korisnika, a takođe i da obezbedi obradu akcija korisnika: slanje poruka serveru, razmenu privatnih poruka, napuštanje chata itd.


Klijent

 Dok je za server jasno da mora biti aplikacija, klijent nas stavlja pred dilemu: aplet ili aplikacija? Izbor zavisi od servisa koji treba pružiti, okruženja u kome će se servis izvršavati, pa i od afiniteta samog programera. Aplet je uklopljen u Web okruženje i koristi sve njegove pogodnosti - ako želite javni chat na svom Internet ili intranet sajtu, izrada klijent apleta se podrazumeva. Aplet ima ograničenja u bezbedonosnom smislu: konekcija je ograničena na server koji "trči" na hostu na kome je aplet postavljen, dok je Java aplikacija oslobođena tih stega. Zbog čitave euforije koja ovih dana prati Web , ovde ćemo se osvrnuti na izradu klijent apleta koji se jednostavno ugrađuje u HTML stranu i omogućava nesmetanu komunikaciju korisnika Internet sajta ili zaposlenih u firmi čiji su browser -i usmereni na intranet prezentaciju.
 Pre nego što počnemo da programiramo, treba da projektujemo zahteve koji se postavljaju pred klijenta. Taj posao zavisi od potreba, želja, pa i od umeća programera. Klijent koji mi pravimo omogućava registraciju korisnika, tj. unos imena pod kojim će korisnik biti poznat ostalim učesnicima u razgovoru, održavanje liste korisnika, odnosno mogućnost da svaki klijent zna ko je u chat -u, slanje poruka koje će biti vidljive svim korisnicima i slanje privatnih poruka, odnosno poruka koje će videti samo određeni korisnik odabran sa liste.
 Pošto su zahtevi poznati, treba osmisliti grafički interfejs, recimo kao na slici 1. Poruke koje server šalje ispisuju se u kontroli tipa TextArea koja dominira apletom. Poruke se unose u TextField na dnu apleta, a lista korisnika se prati u kontroli tipa List s desne strane. Kod kojim sve to postižemo izgleda ovako:

setLayout (new BorderLayout ());
add ("East", list1 = new List ());
add ("Center", out = new TextArea ());
out.setEditable (false);
add("South", in = new TextField ());
in.requestFocus ();
 Proces "osluškivanja" servera mora da se obavlja neprekidno i nezavisno od drugih procesa, dakle u niti ( Thread ). String line predstavlja tekst koji šalje server i u zavisnosti od zaglavlja primljenog teksta klijent će odlučiti šta da radi:
public void run () {
   try {
    while (true) {
       String line = i.readUTF ();
       if(!line.startsWith("<<") & !line.startsWith("*") & !line.startsWith("Napusta") & !line.startsWith("To ime") ) {
	   		list1.addItem(line); }
 Ako zaglavlje teksta koji dolazi sa servera ne počinje na neki od navedenih načina, onda se radi o imenu novog korisnika koga treba dodati u kontrolu tipa List :
if (line.startsWith("<<") | line.startsWith("*")) {
	 out.appendText (line + "\n"); }

 Ako zaglavlje poruke počinje ovim stringovima, radi se ili o poruci nekog korisnika ili o poruci samog servera, pa tekst treba prikazati u kontroli tipa TextArea .
if(line.startsWith("Napusta")) {
   String line1 = line.substring(7,8);
   int k = Integer.parseInt(line1);
   list1.delItem(k); }
 Ako server šalje poruku koja počinje sa stringom "Napusta", na taj string je dodat broj koji označava položaj korisnika koji je napustio Chat , pa ga treba ukloniti sa liste aktivnih korisnika. Pri napuštanju chat -a, zatvaramo konekciju:
finally {
   runner = null;
   in.hide ();
   validate ();
   try {
      o.close (); }
   catch (IOException ex) {
        ex.printStackTrace (); }
}
 Obrada i prihvatanje korisnikovih akcija obavlja se u metodu handleEvent . Ako veza sa serverom nije uspostavljena (ind = 0), prihvata se uneseno ime korisnika, otvara konekcija sa serverom definisanjem soketa ( Socket ) gde navodimo DNS broj servera ili pun naziv servera, recimo www.fcs.yu i port na kome server osluškuje; u našem slučaju to je port 9999. Nakon uspostavljanja veze, pokrećemo nit ( Thread ) runner koji neprekidno osluškuje poruke servera. Svaki sledeći uneseni tekst, neće se tretirati kao ime korisnika već kao poruka (ind = 1).
 Naravno, želimo da omogućimo i slanje privatnih poruka samo određenim korisnicima. U tom slučaju upisujemo poruku i sa liste biramo korisnika kome želimo da je pošaljemo:
try {
   o.writeUTF ("* Privatna poruka " + lin + ", salje " + i1 + "Glasi:" + ' ' + in.getText());
   o.flush (); }
   catch (IOException ex) {
     ex.printStackTrace();
     runner.stop (); }
     out.appendText("<<" + i1 + "-->>" + lin + ">>" + " " + in.getText() + "\n");
     in.setText(""); }
 Takva poruka dobija zaglavlje sa početkom "* Privatna poruka", imenom korisnika kome se šalje poruka i imenom korisnika koji šalje poruku. Takvo zaglavlje će server protumačiti, izdvojiti poruku i korisnike i pravilno poruku proslediti određenom korisniku sa propratnim tekstom (slika 2).


Server

 Server koji je multithreaded (podržava više niti) osluškuje na portu 9999 i prihvata nove klijente. Informacije od svakog klijenta primaju se u posebnim nitima ( Thread ). Server prihvata svakog novog klijenta i predaje ga klasi Glavna , čija je podklasa Thread . Klijent se zatim upisuje u vektor koji je klase vector (omogućava dinamičko dodeljivanje elemenata), a ime klijenta se upisuje u vector vektor1.

 while (true) {
   Socket klijent = server.accept ();
   System.out.println ("Poziv od " + klijent.getInetAddress ());
   Glavna c = new Glavna (klijent);
      c.start (); }
 Informaciju koju primi od klijenta server ispituje i, u zavisnosti od zaglavlja (obična poruka, privatna poruka, napuštanje chat -a itd), šalje je svim korisnicima ili samo određenom korisniku, a ako se radi o napuštanju chat -a, izbacuje korisnika iz vektora vector i vector1 .
 Ako je u pitanju poruka koja se šalje svim korisnicima, server u petlji čita sve elemente vektora vector (dok ih ima - e.hasMoreElements ) i svakome šalje poruku. Proces mora biti sinhronizovan ( synchronized ), čime se sprečavaju sve ostale niti dok se ne okonča slanje poruke. Naprimer, korisnik ne može da napusti chat dok traje prenos poruke jer bi se u tom slučaju desilo da server šalje poruku nepostojećem korisniku (slika 3).
 Privatna poruka se šalje na sličan način, osim što ne ide na sve adrese u nizu vector , već je usmerena na određeni element: Glavna c2 = (Glavna) vektor.elementAt(vektor1.indexOf(line3));
 Ako, najzad, neki klijent napusti chat , server pravi poruku za sve preostale aktivne klijente u čijem se zaglavlju kriju podaci o klijentu koji je otišao i koga treba izbaciti sa liste. Kompletan listing Java programa koji se izvršava na serveru (slika 4) možete da preuzmete sa SezamaPro.


Zaključak

 Projektovanje klijent/server sistema u Javi znatno je lakše nego programiranje takvih sistema u ostalim programskim jezicima. Java API ima sve klase neophodne za pisanje programa koji ostvaruju komunikaciju preko mreže koristeći TCP/IP mrežni protokol. Java klijent/server sistemi mogu da pokriju čitav spektar servisa a za šta će se upotrebiti zavisi samo od ideje i umeća programera. Nije teško, recimo, napraviti sistem koji će omogućiti igranje šaha preko Interneta - suštinska razlika je samo u programiranju adekvatnog klijenta i servera koji će razlikovati zaglavlja poruka koje se razmenjuju.
 Ovde prikazani chat sistem može se po volji poboljšavati, naprimer otvaranjem više "soba" koje se bave različitim temama. To ostavljam vama za dalje istraživanje, a ja ću vam pomoći kada kažem da za svaku novootvorenu "sobu" treba na serveru dinamički dodeliti novi port. Naravno, ako ste umorni od pisanja Java programa, svratite na Web stranu časopisa "PC" ( www.pcpress.co.yu/chat ) i popričajte sa prijateljima preko Web -a...

 Slika 2: Klijent.java
// Klijent.java Ivan Mitrovic Jun, 1997.
import java.net.*;
import java.io.*;
import java.awt.*;
public class Klijent extends java.applet.Applet implements Runnable {
  Socket s;
  protected DataInputStream i;
  protected DataOutputStream o;
  protected TextArea out;
  protected TextField in;
  protected List list1;
  protected Thread runner;
  String Imehosta, i1;
  int ind;
public void init() {
   ind = 0;
   try {
      Imehosta = InetAddress.getLocalHost().toString(); }
   catch(Exception e);
   setLayout (new BorderLayout ());
   add ("East", list1 = new List ());
   add ("Center", out = new TextArea ());
   out.setEditable (false);
   out.appendText("  ********* Java Chat sistem *********" + "\n");
   out.appendText("Autor: Mitrovic Ivan  mivan@EUnet.yu" + "\n");
   out.appendText("Jun 1997." + "\n");
   out.appendText(" " + "\n");
   out.appendText("Unesi ime:" + "\n");
   add("South", in = new TextField ());
   in.requestFocus (); }
public void stop() {
  if ((runner != null) && runner.isAlive()) {
      runner.stop(); runner = null; destroy(); }
}
public void run () {
   try {
      while (true) {
         String line = i.readUTF ();
         if (!line.startsWith("<<") & !line.startsWith("*") & !line.startsWith("Napusta") & !line.startsWith("To ime") ) {
            list1.addItem(line); }
         if (line.startsWith("<<") | line.startsWith("*")) {
            out.appendText (line + "\n"); }
         if (line.startsWith("Napusta")) {
            String line1 = line.substring(7,8);
            int k = Integer.parseInt(line1);
            list1.delItem(k); }
         if(line.startsWith("To ime")) {
            out.appendText(line + "\n");
            String line2 = line.substring(44);
            i1 = line2; }
      }
   }
   catch (IOException ex) {
      ex.printStackTrace (); }
   finally {
      runner = null; in.hide (); validate ();
      try {
         o.close (); }
      catch (IOException ex) {
        ex.printStackTrace (); }
   }
}
public boolean handleEvent (Event e) {
    if ((e.target == in) && (e.id == Event.ACTION_EVENT)) {
       if (ind == 1) {
          try {
             o.writeUTF ("<<" + i1 + ">>" + ' ' + (String) e.arg);
             o.flush (); }
          catch (IOException ex) {
             ex.printStackTrace();
             runner.stop (); }
       }
       if (ind == 0) {
          i1 = (String) e.arg; ind = 1;
          try {
             getAppletContext().showStatus("Uspostavljam vezu sa hostom 194.247.206.194...");
             s = new Socket ("194.247.206.194", 9999);
             i = new DataInputStream (new BufferedInputStream (s.getInputStream()));
             o = new DataOutputStream (new BufferedOutputStream(s.getOutputStream())); }
          catch(Exception e1) {
            getAppletContext().showStatus(e1.toString()); }
          runner = new Thread (this); runner.start ();
          out.replaceText(" ",0,150);
          getAppletContext().showStatus(" ");
          try {
             o.writeUTF (i1); o.flush (); }
          catch (IOException ex) {
             ex.printStackTrace();
             runner.stop (); }
       }
       in.setText ("");
       return true;
    }
    else if (e.target instanceof List & e.id == Event.LIST_SELECT) {
       String lin = list1.getSelectedItem();
       try {
          o.writeUTF ("* Privatna poruka " + lin + ", salje " + i1 + "Glasi:" + ' ' + in.getText());
          o.flush (); }
       catch (IOException ex) {
          ex.printStackTrace();
          runner.stop (); }
       out.appendText("<<" + i1 + "-->>" + lin + ">>" + " " + in.getText() + "\n");
       in.setText(""); }
    return super.handleEvent (e); }
}

 Slika 3
synchronized (vektor) {
   Enumeration e = vektor.elements ();
   while (e.hasMoreElements ()) {
     Glavna c = (Glavna) e.nextElement ();
     try {
        synchronized (c.o) {
            c.o.writeUTF (poruka); }
        c.o.flush (); }
     catch (IOException ex) {
        c.stop (); }
   }
}

 Slika 4
finally {
   int k = vektor.indexOf(this);
   String ime = vektor1.elementAt(k).toString();
   vektor.removeElement (this);
   vektor1.removeElementAt(k);
   synchronized (vektor) {
      Enumeration e = vektor.elements ();
      while (e.hasMoreElements ()) {
        Glavna c = (Glavna) e.nextElement ();
        try {
           synchronized (c.o) {
              c.o.writeUTF ("Napusta" + k); }
           c.o.flush (); }
        catch (IOException ex) {
           c.stop (); }
   }
}

 Server.java
// Server.java Ivan Mitrovic, jun 1997.
import java.net.*;
import java.io.*;
import java.util.*;
public class Server {
  String ime;
  public Server (int port) throws IOException {
     ServerSocket server = new ServerSocket (port);
     while (true) {
        Socket klijent = server.accept ();
        System.out.println ("Poziv od " + klijent.getInetAddress ());
        Glavna c = new Glavna (klijent);
        c.start (); } }
  public static void main (String args[]) throws IOException {
     new Server (9999); }
}
class Glavna extends Thread {
  protected Socket s;
  protected DataInputStream i;
  protected DataOutputStream o;
  int ind,ind2,ind3;
  public Glavna (Socket s) throws IOException {
    this.s = s;
    i = new DataInputStream (new BufferedInputStream (s.getInputStream ()));
    o = new DataOutputStream (new BufferedOutputStream (s.getOutputStream ())); }
  protected static Vector vektor = new Vector();
  protected static Vector vektor1 = new Vector();
  public void run () {
    try {
      ind2 = 0; ind = 0;
      while (true) {
        ind3 = 0;
        String msg = i.readUTF ();
        if (!msg.startsWith("<<") & !msg.startsWith("*")) {
           while(vektor1.indexOf(msg) >> -1) {
              ind2 = 1; msg = msg + "*"; }
           if (vektor1.indexOf(msg) == -1) {
              vektor.addElement (this);
              vektor1.addElement(msg);
              Glavna c = (Glavna) vektor.lastElement ();
              for (int i = 0; i << vektor1.size()-1;i ++) {
                 String msg1 = vektor1.elementAt(i).toString();
                 try {
                    synchronized (c.o) {
                       c.o.writeUTF (msg1); }
                    c.o.flush (); }
                 catch (IOException ex) {
                    c.stop (); }
              }
              if (ind2 ==1) {
                 Glavna c1 = (Glavna) this;
                 try {
                    synchronized (c1.o) {
                       c1.o.writeUTF ("To ime je vec u upotrebi, promenio sam ga u " + vektor1.lastElement().toString()); }
                    c1.o.flush (); }
                 catch (IOException ex) {
                    c1.stop (); }
              }
              salji("***** " + vektor1.lastElement().toString() + " se prikljucuje razgovoru *****");
              msg = vektor1.lastElement().toString();
              ind = 1; }
           }
           if(msg.startsWith("***** Privatna")) {
              String line3 = msg.substring(22,msg.indexOf(","));
              Glavna c2 = (Glavna) vektor.elementAt(vektor1.indexOf(line3));
              int pol = msg.indexOf("salje") + 6 ;
              int pol1 = msg.indexOf("Glasi:");
              try {
                 synchronized (c2.o) {
                    c2.o.writeUTF ("***** Privatna poruka od korisnika sa imenom " + msg.substring(pol,pol1) + "\n" + "GLASI: " + msg.substring(pol1+6)); }
                 c2.o.flush (); }
              catch (IOException ex) {
                 c2.stop (); }
              ind3 = 1; }
           if (ind == 1 & ind3 == 0) {
              salji (msg); }
        }
    }
    catch (IOException ex) {
       ex.printStackTrace (); }
    finally {
      int k = vektor.indexOf(this);
      String ime = vektor1.elementAt(k).toString();
      vektor.removeElement (this);
      vektor1.removeElementAt(k);
      synchronized (vektor) {
         Enumeration e = vektor.elements ();
         while (e.hasMoreElements ()) {
            Glavna c = (Glavna) e.nextElement ();
            try {
               synchronized (c.o) {
                  c.o.writeUTF ("Napusta" + k); }
               c.o.flush (); }
            catch (IOException ex) {
               c.stop (); }
         }
      }
      synchronized (vektor) {
         Enumeration e = vektor.elements ();
         while (e.hasMoreElements ()) {
            Glavna c = (Glavna) e.nextElement ();
            try {
               synchronized (c.o) {
                  c.o.writeUTF ("* " + ime + " napusta razgovor *"); }
               c.o.flush (); }
            catch (IOException ex) {
               c.stop (); }
         }
      }
      try {
        s.close (); }
      catch (IOException ex) {
         ex.printStackTrace(); }
  }
}
protected static void salji (String poruka) {
   synchronized (vektor) {
      Enumeration e = vektor.elements ();
      while (e.hasMoreElements ()) {
        Glavna c = (Glavna) e.nextElement ();
        try {
          synchronized (c.o) {
             c.o.writeUTF (poruka); }
          c.o.flush (); }
        catch (IOException ex) {
           c.stop (); }
      }
   }
}
}





PC home - osnovna strana Novi broj|Arhiva|Pretrazivanje svih brojeva|O nama
Pretplatite se na PC|Postanite saradnik casopisa PC|Pitanja i komentari u vezi casopisa