티스토리 뷰

Java Multi Thread Example : Chat


사용한 API

1. ServerSocket

  • 서버 역할을 하는 소켓 객체

  • 클라이언트의 연결 요청을 기다리면서 연결 요청에 대한 수락을 담당한다.


  • public void bind(SocketAddress endpoint) 어떤 소켓으로 연결을 기다릴 것인지 바인딩
    public Socket accept() 연결을 기다리며, 연결이 될 때까지 block 상태

2. Socket

  • 클라이언트와 서버 간의 통신을 직접 담당


  • public SocketAddress getRemoteSocketAddress() 소켓에 연결된 종단의 주소를 반환
    public InputStream getInputStream() 소켓을 위한 input stream을 반환
    public OutputStream getOutputStream() 소켓을 위한 output stream을 반환
    public void connect(SocketAddress endpoint) 서버에 연결

3. InetAddress

  • IP 주소를 표현할 때 사용하는 클래스


  • public static InetAddress getLocalHost() 로컬호스트의 “호스트이름/IP주소”를 반환
    public String getHostAddress() IP주소를 반환
    public String getHostName() 호스트 이름을 문자열로 반환

4. InetSocketAddress

  • SocketAddress를 상속받은 클래스로서, 소켓의 IP주소와 port번호를 알 수 있도록 구현한 클래스이다.

  • 도메인 이름만 알아도 객체를 생성할 수 있습니다.

    • ServerSocket 객체의 bind(), Socket 객체의 connect() 메서드를 호출할 때 인자로 사용


  • InetSocketAddress ( String hostname, int port) InetSocketAddress( InetAddress addr, int port ) 첫 번째 인자로 호스트 이름 또는 IP 주소를,두 번째 인자로 포트번호를 넘겨서socket address를 생성
    public final int getPort() 포트번호를 반환
    public final InetAddress getAddress() InetAddress를 반환

5. InputStream 과 OutputStream

  • Byte 데이터를 입출력하기 위한 IO 클래스

  • InputStream

    public int read(byte[] b) 스트림을 통해 buffer array b를 읽음
    public void write(byte[] b) buffer b를 쓴다.

Study

  1. synchronized

    • 동기화를 한다는 것은 synchronized가 선언된 특정 영역 작업시 오직 한 쓰레드만이 해당 작업을 수행할 수 있도록 하는 것이며, 다른 쓰레드는 접근할 수 없는 상태를 의미

    • 동기화는 객체단위로 이뤄진다.

    • synchronized (listWriters)는 listWriters 객체에 대한 동기화를, synchronized (this)는 해당 메서드를 호출한 곳에 대한 동기화를 의미

    • 동기화가 필요한 부분은 메서드를 호출한 객체가 아닌, 클라이언트들(listWriters)에 대한 동기화가 필요한 것

private void removeWriter(PrintWriter writer) {
    //@TODO synchronized 공부
    synchronized (listWriters) {
        listWriters.remove(writer);
    }
}

CODE

1. ChatClient

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
​
public class ChatClient {
   private static final String SERVER_IP = "127.0.0.1";
   private static final int SERVER_PORT = 5000;
​
   public static void main(String[] args) {
       String name = null;
       Scanner sc = new Scanner(System.in);
​
       while (true) {
           System.out.println("대화명을 입력하세요.");
           System.out.print(">>> ");
           name = sc.nextLine();
​
           if (name.isEmpty() == false) {
               break;
          }
           System.out.println("대화명은 한글자 이상 입력해야 합니다.\n");
      }
       sc.close();
​
       Socket socket = new Socket();
       try {
           socket.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
           consoleLog("채팅방에 입장하였습니다.");
           System.out.println("name:" + name);
           new ChatGUI(name, socket).show();
​
           PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
           String request = "join:" + name + "\r\n";
           printWriter.println(request);
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
   private static void consoleLog(String log) {
       System.out.println(log);
  }
}

2. ChatGUI

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
​
public class ChatGUI {
   private String name;
   private Frame frame;
   private Panel pannel;
   private Button buttonSend;
   private TextField textField;
   private TextArea textArea;
   private Socket socket;
​
   public ChatGUI(String name, Socket socket) {
       this.name = name;
       frame = new Frame(name);
       pannel = new Panel();
       buttonSend = new Button("Send");
       textField = new TextField();
       textArea = new TextArea(30, 80);
       this.socket = socket;
​
       new ChatClientReceiveThread(socket).start();
  }
​
   public void show() {
       // Button
       buttonSend.setBackground(Color.GRAY);
       buttonSend.setForeground(Color.WHITE);
       buttonSend.addActionListener(new ActionListener() {
           @Override
           public void actionPerformed(ActionEvent actionEvent) {
               sendMessage();
          }
      });
​
       // Textfield
       textField.setColumns(80);
       textField.addKeyListener(new KeyAdapter() {
           public void keyReleased(KeyEvent e) {
               char keyCode = e.getKeyChar();
               if (keyCode == KeyEvent.VK_ENTER) {
                   sendMessage();
              }
          }
      });
​
       // Pannel
       pannel.setBackground(Color.LIGHT_GRAY);
       pannel.add(textField);
       pannel.add(buttonSend);
       frame.add(BorderLayout.SOUTH, pannel);
​
       // TextArea
       textArea.setEditable(false);
       frame.add(BorderLayout.CENTER, textArea);
​
       // Frame
       frame.addWindowListener(new WindowAdapter() {
           public void windowClosing(WindowEvent e) {
               PrintWriter pw;
               try {
                   pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
                   String request = "quit\r\n";
                   pw.println(request);
                   System.exit(0);
              } catch (IOException e1) {
                   e1.printStackTrace();
              }
          }
      });
       frame.setVisible(true);
       frame.pack();
  }
​
   // 쓰레드를 만들어서 대화를 보내기
   private void sendMessage() {
       PrintWriter pw;
       try {
           pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
           String message = textField.getText();
           String request = "message:" + message + "\r\n";
           pw.println(request);
​
           textField.setText("");
           textField.requestFocus();
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
​
   private class ChatClientReceiveThread extends Thread {
       Socket socket = null;
​
       ChatClientReceiveThread(Socket socket) {
           this.socket = socket;
      }
​
       public void run() {
           try {
               BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
               while (true) {
                   String msg = br.readLine();
                   textArea.append(msg);
                   textArea.append("\n");
              }
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }
}

3. ChatServer

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
​
public class ChatServer {
   public static final int PORT = 5000;
​
   public static void main(String[] args) {
       ServerSocket serverSocket = null;
       List<PrintWriter> listWriters = new ArrayList<PrintWriter>();
​
       try {
           //1. 서버 소켓 생성
           serverSocket = new ServerSocket();
​
           //2. 바인딩
           String hostAdd = InetAddress.getLocalHost().getHostAddress();
​
           serverSocket.bind(new InetSocketAddress(hostAdd, PORT));
           consoleLog("연결 기다림 - " + hostAdd + ":" + PORT);
​
           //3. 요청대기
           while (true) {
               Socket socket = serverSocket.accept();
               new ChatServerProcessThread(socket, listWriters).start();
          }
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           try {
               if (serverSocket != null && !serverSocket.isClosed()) {
                   serverSocket.close();
              }
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }
   private static void consoleLog(String log) {
       System.out.println("[server " + Thread.currentThread().getId() + "] " + log);
  }
}

4. ChatServerProcessThread

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.List;
​
public class ChatServerProcessThread extends Thread {
​
   private String name = null;
   private Socket socket = null;
   List<PrintWriter> listWriters = null;
​
   public ChatServerProcessThread(Socket socket, List<PrintWriter> listWriters) {
       this.socket = socket;
       this.listWriters = listWriters;
  }
​
   @Override
   public void run() {
       super.run();
       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
           PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
           while (true) {
               String request = bufferedReader.readLine();
               if (request == null) {
                   consoleLog("Client로부터 연결 끊김");
                   doQuit(printWriter);
                   break;
              }
​
               String[] tokens = request.split(":");
               if ("join".equals(tokens[0])) {
                   doJoin(tokens[1], printWriter);
              } else if ("message".equals(tokens[0])) {
                   doMessage(tokens[1]);
              } else if ("quit".equals(tokens[0])) {
                   doQuit(printWriter);
              }
          }
​
      } catch (IOException e) {
           consoleLog(this.name + "님이 채팅방을 나갔습니다.");
      }
  }
​
   private void doQuit(PrintWriter printWriter) {
       removeWriter(printWriter);
       String data = this.name + "님이 채팅방을 나갔습니다.";
       broadcast(data);
  }
​
   private void removeWriter(PrintWriter writer) {
       synchronized (listWriters) {
           listWriters.remove(writer);
      }
  }
​
   private void doMessage(String data) {
       broadcast(this.name + ":" + data);
  }
​
   private void doJoin(String name, PrintWriter printWriter) {
       this.name = name;
       String data = this.name + "님이 채팅방을 입장하샸습니다.";
       broadcast(data);
​
       //writer pool에 저장
       addWriter(printWriter);
  }
​
   private void addWriter(PrintWriter printWriter) {
       synchronized (listWriters) {
           listWriters.add(printWriter);
      }
  }
​
   private void broadcast(String data) {
       synchronized (listWriters) {
           for (PrintWriter printWriter : listWriters) {
               printWriter.println(data);
               printWriter.flush();
          }
      }
​
  }
​
   private void consoleLog(String log) {
       System.out.println(log);
  }
}

실습

 

출처 : https://victorydntmd.tistory.com/135#comment12053787

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함