// ChatAsiakas25-26.java SJ // Ottaa yhteyden ChatPalvelimeen import javax.print.DocFlavor; import java.io.IOException; import java.net.*; import java.util.Scanner; import java.io.PrintWriter; import java.io.InputStreamReader; import java.io.BufferedReader; import java.lang.Thread; public class ChatAsiakas { private static String tunnus = "tunnus"; private PrintWriter out; private BufferedReader in; private Socket sock = null; private Thread pSaie = null; // palvelinta kuunteleva säie private Thread kSaie = null; // käyttäjää kuunteleva säie (pääsäie) private static boolean lopeta = false; // säikeet lopettavat toimintansa kun havaitsevat tämän olevan tosi public static void main(String[] args) { // oletusarvot String palvelimenOsoite = "localhost"; int portti = 11217; if (args.length > 0) { palvelimenOsoite = args[0]; } if (args.length > 1) { portti = Integer.valueOf(args[1]); } if (args.length > 2) { tunnus = args[2]; } ChatAsiakas a = new ChatAsiakas(); boolean stat = a.otaYhteys(palvelimenOsoite, portti); } // main() private void lopetaKaikki() { lopeta = true; // herätetään odottava säie, suorituksessa olevalle ei tapahdu mitään kSaie.interrupt(); pSaie.interrupt(); } // yhteydenotto ja keskustelun kutsuminen private boolean otaYhteys(String osoite, int portti) { // talteen viite pääsäikeeseen kSaie = Thread.currentThread(); // yhteydenotto try { sock = new Socket(osoite, portti); // yhteydenotto System.out.println("Yhteys onnistui"); } catch (Exception e) { // yhteys ei varmaankaan onnistunut System.err.println("" + e); return false; } try { // luodaan keskustelukanavat out = new PrintWriter(sock.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(sock.getInputStream())); // liitytään keskusteluun String ok = liity(); if (ok == null) { // käynnistetään taustasäie kuuntelemaan palvelinta ja pSaie = new PalvelinLukija(in); // mennään pääsäikeellä kuuntelemaan käyttäjää juttele(); } else { System.err.println("Liittyminen ei onnistunut: " + ok); } sock.close(); // poikkeusten k?sittely } catch (Exception e) { System.err.println("" + e); if (sock != null) try { sock.close(); // suljetaan varuilta viel? t??ll?kin } catch (Exception e2) {} return false; } // catch return true; } // otaYhteys() /** * Liittyminen kun yhteys on jo otettu. * @return null jos kaikke meni hyvin, virheilmoituksen merkkijonona muuten. */ String liity() { try { out.print(ChatProto.liity + " " + tunnus + ChatProto.EOL); out.flush(); String vastaus = in.readLine(); vastaus = vastaus.trim(); if (vastaus.startsWith("2")) { System.out.println("Liittyminen onnistui: " + vastaus); return null; } else { System.out.println("Liittyminen meni pieleen: " + vastaus); return vastaus; } } catch (IOException e) { System.err.println("liity Poikkeus " + e); return "Poikkeus"; } } /** * Keskustelu käyttäjän kanssa. */ void juttele() { System.out.println("Anna viestejä, pelkkä piste rivillä lopettaa ja lähettää viestin."); System.out.println("LOPETA viestiksi lopettaa"); BufferedReader kayttaja = new BufferedReader( new InputStreamReader(System.in)); // luetaan viestiä tähän StringBuilder sb = new StringBuilder(); while (! lopeta) { sb.setLength(0); // tyhjennetään vanhat lueViesti(kayttaja, sb); // viesti käyttäjältä if (lopeta) break; if ("LOP".equalsIgnoreCase(sb.substring(0, 3))) { lopetaOhjelma(); return; } out.print(ChatProto.uusiViesti + " " + tunnus + ChatProto.EOL); out.print(sb.toString()); // sb sisältää EOL:n out.print(ChatProto.EOM + ChatProto.EOL); out.flush(); } } /** * Lopetetaan kun käyttäjä haluaa lopettaa */ void lopetaOhjelma() { try { lopeta = true; out.print(ChatProto.poistu + ChatProto.EOL); out.flush(); // kuittauksen lukee toinen säie lopetaKaikki(); sock.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Viestin rungon lukeminen. * @param sb merkkipuskuri johon viesti lisätään. * @param in kanava jota luetaan. * @return null jos viesti on ok, muuten virheilmoituksen merkkijonona. **/ private String lueViesti(BufferedReader in, StringBuilder sb) { while (true) { try { String rivi = in.readLine(); if (lopeta) return null; if (rivi == null) return "Kanava loppui"; rivi = rivi.trim(); if (rivi.equals(ChatProto.EOM)) { // viestin loppu return null; // kaikki hyvin EOM:ää ei laiteta viestiin tässä } else { sb.append(rivi); sb.append(ChatProto.EOL); } } catch (IOException e) { if (! lopeta) { System.err.println("lueViesti poikkeus: " + e); return "Poikkeus luvussa"; } else return "Pyydetty lopettamaan"; } } // while } /** * Säie joka lukee palvelimelta viestejä. */ public class PalvelinLukija extends Thread { BufferedReader in; PalvelinLukija(BufferedReader i) { in = i; this.start(); } /** * Säikeen elämä. */ public void run() { // käytetään StringBuilderia ylläpitämään vastaanotettua viestiä StringBuilder sb = new StringBuilder(); try { while (! lopeta) { // luetaan otsikko, toimitaan sen mukaisesti String otsikko = in.readLine(); if (lopeta) // paitsi jos on saatu lopetuskäsky ja keskeytys muualta break; otsikko = otsikko.trim(); if (otsikko.startsWith(ChatProto.poistu)) { out.print(ChatProto.viestiOk + ChatProto.EOL); out.flush(); System.out.println("Palvelin käskee poistumaan"); lopetaKaikki(); break; } if (otsikko.startsWith(ChatProto.uusiViesti)) { String lahTunnus = otsikko.substring(otsikko.indexOf(" ")); sb.setLength(0); String ok = lueViesti(in, sb); if (ok == null) { tulostaViesti(sb, lahTunnus); } else { System.err.println("Ongelma viestin luvussa"); } } else if (otsikko.startsWith("200")) { System.out.println("Saatiin kuittaus"); // tämän voi kommentoida pois } else { System.err.println("Tuntematon viesti: " + otsikko); } } } catch (Exception e) { if (! lopeta) System.err.println(e); } } /** * Tulostaa palvelimelta saadun viestin siten, että joka rivin alussa on tunnus. * @param sb viesti. * @param tunnus tunnus joka tulostetaan joka rivin alkuun. */ public void tulostaViesti(StringBuilder sb, String tunnus) { Scanner sc = new Scanner(sb.toString()); while (sc.hasNextLine()) System.out.println(tunnus + "> " + sc.nextLine()); } } // class PalvelinLukija }