PC Press
O nama
O nama
Pretplata
O nama
Postanite saradnik PC-ja
Kontakt sa redakcijom
PC Press
Novi broj
Novi broj   
Pretrazivanje
Arhiva
Arhiva   
PC Online
PC Plus   
Specijalna izdanja
Programiranje Programiranje
PC #26 : Jul / Avgust 1997

 Naslovna  Sadržaj 
Ivan Mitrović  

Java chat sistem

Uz malo programiranja, Ivan Mitrović pokazuje kako možete interaktivno razmenjivati informacije sa drugom čitaocima neke Web strane.

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 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.

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 sajtu, izrada klijent apleta se podrazumeva. Ovde ćemo se osvrnuti na izradu klijent apleta koji se lako ugrađuje u HTML stranu i omogućava nesmetanu komunikaciju korisnika Internet ili intranet sajta.

Pre programiranja, treba isprojektovati zahteve koji se postavljaju pred klijenta. Klijent koji mi pravimo omogućava registraciju korisnika, 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.

Treba osmisliti i grafički interfejs (slika 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. To postižemo ovim kodom:

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 novom korisniku koga treba dodati u kontrolu tipa List:

if (line.startsWith("") | line.startsWith("*")) {
   out.appendText (line + ""); }

Ako zaglavlje poruke počinje ovim stringovima, radi se ili o poruci 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() + "");
     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 i proslediti korisniku (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 sadržaja 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 će u petlji čitati sve elemente vektora vector (dok ih ima - e.hasMoreElements) i svakome slati poruku. Ovaj proces obavezno mora biti sinhronizovan (synchronized), čime se sprečavaju sve ostale niti dok se ne okonča slanje poruke. Naprimer, nijedan 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, što regulišemo sledećim redovima:

Glavna c2 = (Glavna) vektor.elementAt (vektor1.indexOf(line3));

Ako se desi da, najzad, neki klijent napusti chat, server automatski 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. Ukoliko vam zatreba kompletan listing Java programa koji se izvršava na serveru može ga jednostavno preuzeti sa sistema SezamPro.

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. 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 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 2

Listing 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 ******" + "");
   out.appendText("Autor: Mitrovic Ivan  mivanEUnet.yu"+"");
   out.appendText("Jun 1997." + "");
   out.appendText(" " + "");
   out.appendText("Unesi ime:" + "");
   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 + ""); }
         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 + "");
            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");
            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() + "");
       in.setText(""); }
    return super.handleEvent (e); }
}

Prilozi:

pc026jav.zip (2,34 kB)