C, PHP, VB, .NET

Дневникът на Филип Петров


* Чат сървър и клиент с GUI

Публикувано на 17 декември 2013 в раздел ПИК3 Java.

В това упражнение беше показано как може да се изгради графичен интерфейс за чат клиент приложението от предишното упражнение. Използва се Swing, като се използват различни контроли – JLabel, JTextArea, JScrollPane, JTextField и JButton. Освен допълнителния клас (ClientGUI) има промени по другите два – в ClientThread всички System.out.println са променени така, че да обновяват графичните контроли, а в основния клас Client e премахната функционалността за четене на съобщение от клавиатурата чрез конзолата (тази дейност е прехвърлена към действието на бутона). Промени по сървъра няма и той продължава да си работи в конзолен режим. Демонстрирани са два често използвани Layouts – FlowLayout и BorderLayout – за разполагане на контролите. Не на последно място – използва се Event Dispatch Thread (EDT) чрез SwingUtilities.invokeLater(…), за да се прави синхронизирано обновяване на информацията по графичния интерфейс.

1. Сървър

chatserver.java

package chatserver;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.net.ServerSocket;
import java.util.Vector;

public class chatserver{
  static Vector<User> users = new Vector<User>(10);
  static int port = 1111;

  public static void main(String[] args){
    // Starting server
    ServerSocket servSock;
    try{
      servSock = new ServerSocket(port);
    }
    catch(IOException e){
      System.err.println("Can't start server");
      return;
    }
    System.out.println("Server started");
    //Accepting new users
    while(true){
      try{
        Socket newConnection = servSock.accept();
        User u = new User(newConnection);
        u.start();
      }
      catch(IOException e){
        System.err.println("ERR: Can't connect to user: "+e.getMessage());
      }
    }
  }

  synchronized static void removeUser(User u){
    for(int i=0; i<users.size(); i++){
      if(users.get(i).equals(u)){
        users.remove(i);
        System.out.println("User removed");
        break;
      }
    }
  }

  synchronized static boolean usernameIsFree(String username){
    boolean result = true;
    for(User u: users){
      if(u.username.equals(username)){
        result = false;
        break;
      }
    }
    return result;
  }

  synchronized static void sendToAll(String message) throws IOException{
    for(User u: users){
      u.send(message);
    }
  }
}

class User extends Thread{
   String username;
   Socket mySocket;
   DataInputStream in;
   DataOutputStream out;
   public User(Socket s) throws IOException{
     this.mySocket = s;
     this.in = new DataInputStream(s.getInputStream());
     this.out = new DataOutputStream(s.getOutputStream());
   }
   public void run(){
     try{
       out.writeUTF("SRV: Please reply with your username");
       this.username = in.readUTF();
       if(!chatserver.usernameIsFree(this.username)){
         out.writeUTF("SRV: Username is taken");
       }
       else{
         out.writeUTF("SRV: Welcome to our chat");
         chatserver.users.add(this);
         this.startUserChat();
       }
     }
     catch(IOException e){
       System.err.println("ERR: User died. Reason: "+e.getMessage());
     }
     finally{
       chatserver.removeUser(this);
       try{
         if(this.in!=null) in.close();
         if(this.out!=null) out.close();
         if(this.mySocket!=null) mySocket.close();
       }
       catch(IOException e){
         System.err.println("ERR: Can't close socket: "+e.getMessage());
       }
     }
   }

   private void startUserChat() throws IOException{
     String message;
     do{
       message = this.in.readUTF();
       chatserver.sendToAll(this.username+" says: "+message);
     }
     while(!message.equalsIgnoreCase("exit"));
   }

   void send(String message) throws IOException{
     out.writeUTF(message);
   }
}

2. Клиент

chatclient.java

package chatclient;

import java.util.Scanner;
import java.io.IOException;
import java.net.Socket;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import javax.swing.SwingUtilities;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JButton;

public class chatclient{
  final static String host = "localhost";
  final static int port = 1111;
  final static ClientThread c = 
    new ClientThread(chatclient.host, chatclient.port);

  public static void main(String[] args){
    SwingUtilities.invokeLater(new ClientGUI());
  }
}

class ClientThread extends Thread{
  private Socket socket;
  private DataOutputStream out;
  private DataInputStream in;
  public ClientThread(String host, int port){
    try {
      this.socket = new Socket(host, port);
      ClientGUI.setStatusText("Connected!");
      this.in = new DataInputStream(this.socket.getInputStream());
      this.out = new DataOutputStream(this.socket.getOutputStream());
      this.start();
    }
    catch(IOException e){
      ClientGUI.setStatusText("Can't connect to server");
      ClientGUI.disableInputAndButton();      
      this.close();
    }
  }
  public void run(){
    try{
      String message;
      do{
        message = this.in.readUTF();
        ClientGUI.appendChatText(message);   
      }while(!message.equalsIgnoreCase("exit"));
      ClientGUI.setStatusText("Disconnected");
    }
    catch(IOException e){
      ClientGUI.setStatusText("Connection lost");
      ClientGUI.disableInputAndButton();
    }
    finally{
      this.close();
    }
  }
  void sendMessage(String message){
    try{
      this.out.writeUTF(message);
      this.out.flush();
    }
    catch(IOException e){
      ClientGUI.setStatusText("Can't send message");
      ClientGUI.disableInputAndButton();
      this.close();
    }
  }
  void close(){
    try{
      if(this.in!=null) in.close();
      if(this.out!=null) out.close();
      if(this.socket!=null) socket.close();
    }
    catch(IOException e){
      ClientGUI.setStatusText("Can't close socket: "+e.getMessage());
    }
  }
}

class ClientGUI extends JFrame implements Runnable{
  // For the system messages
  private static JPanel panel1 = new JPanel(new FlowLayout()); 
  private static JLabel status = new JLabel("Connecting to server...");
  // For the textbox
  private static JPanel panel2 = new JPanel(new BorderLayout());
  private static JTextArea chatText = new JTextArea();
  private static JScrollPane chatTextPane = 
    new JScrollPane(chatText,
                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
                   );
  // For writing and sending messages
  private static JPanel panel3 = new JPanel(new FlowLayout());
  private static JTextField userInput = new JTextField(20);
  private static JButton sendButton =  new JButton("Send");

  public void run(){
    // We are ready to go!
    this.configFrame();
    this.configPanel1();
    this.configPanel2();
    this.configPanel3();

    this.setVisible(true);
  }

  private void configFrame(){
    this.setLayout(new BorderLayout());
    this.setTitle("My Chat");
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    this.add(panel1, BorderLayout.NORTH);
    this.add(panel2, BorderLayout.CENTER);
    this.add(panel3, BorderLayout.SOUTH);
    this.pack();
    this.setSize(400, 300);    
  }

  private void configPanel1(){
    // Adding the status to panel1
    ClientGUI.panel1.add(this.status);    
  }

  private void configPanel2(){
    // Configuring and adding the textbox to panel2
    ClientGUI.chatText.setLineWrap(true);
    ClientGUI.chatText.setEditable(false);
    ClientGUI.chatText.setRows(10);    
    ClientGUI.panel2.add(ClientGUI.chatTextPane, BorderLayout.CENTER);
  }

  private void configPanel3(){
    // Adding the input field to panel3
    ClientGUI.panel3.add(userInput);

    // Adding the SEND button to panel3
    sendButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        String text = ClientGUI.userInput.getText();
        if(text != null && !text.isEmpty()){
          chatclient.c.sendMessage(text);
          if(text.equalsIgnoreCase("exit")){
            chatclient.c.interrupt();
            ClientGUI.disableInputAndButton();
            ClientGUI.appendChatText("Bye bye!\n");
          }
        }
        ClientGUI.userInput.setText("");
      }
    });
    sendButton.setToolTipText("Send");
    panel3.getRootPane().setDefaultButton(sendButton);
    ClientGUI.panel3.add(sendButton);    
  }

  static void autoScrollTextBox(){
    ClientGUI.chatText.setCaretPosition(
        ClientGUI.chatText.getDocument().getLength()
    );
  }

  static void setStatusText(final String s){
    SwingUtilities.invokeLater(new Runnable(){
      public void run(){
        ClientGUI.status.setText(s);
      }
    });
  }

  static void appendChatText(final String s){
    SwingUtilities.invokeLater(new Runnable(){
      public void run(){
        ClientGUI.chatText.append(s+'\n');
        ClientGUI.autoScrollTextBox();
      }
    });
  }

  static void disableInputAndButton(){
    SwingUtilities.invokeLater(new Runnable(){
      public void run(){
        ClientGUI.userInput.setEnabled(false);
        ClientGUI.sendButton.setEnabled(false);        
      }
    });
  }
}

Допълнителна задача: Направете така, че първоначално текущите контроли да са изключени, а клиентът да добавя host и port в два допълнителни JTextField. Свързването към сървъра да се извърши с допълнителен Connect бутон. След като Connect бъде натиснат, ако свързването е успешно, контролите се активират, а „Connect“ бутона да стане „Disconnect“. При повторно настикане (т.е. натисканена Disconnect) да се подава команда „exit“ към сървъра и отново всичко да се връща в първоначалното си състояние.

 



5 коментара


  1. Borislav каза:

    Здравейте, искам да Ви попитам, как да прекъсвам връзката след като клиентът получи отговор на заявката си към сървъра и при въвеждане на нова команда, връзката да се установи отново? Примерът – 28 вариант от последното домашно.

    Поздрави :)

  2. Затваряш стария сокет, после отваряш нов сокет.

  3. Клиентът трябва да се свърже точно на този порт, на който сървъра слуша.

  4. Anonymous каза:

    Защо когато променя порта на сървъра и клиента, програмата не работи- изписва ми can’t connect to server? От друга страна на порт 1111 си работи… В мен някъде ли е проблемът?

  5. Anonymous каза:

    Да, използвам един и същи порт, но не винаги се получава.

Добави коментар

Адресът на електронната поща няма да се публикува


*