mini-pms 34a

9 minute read

Server

01) 간단한 메시지를 송수신 할 수 있도록 한다.

public class ServerApp {
  public static void main(String[] args) {

    // 8888 포트에서 클라이언트를 기다리는 서버 소켓을 생성한다.
    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      // 클라이언트가 연결된 소켓을 리턴 받는다.
      // 소켓을 통해 문자열 메시지를 입출력 할 수 있도록 스트림 객체를 준비한다.
      try (Socket socket = serverSocket.accept();
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream());
          ) {

        // 클라이언트가 보낸 메시지를 읽고, 다시 클라이언트에게 리턴해준다.
        String request = in.readLine();

        out.println(request);
        out.flush();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

02) 사용자가 quit 명령을 입력할 때까지 반복한다.

public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      try (Socket socket = serverSocket.accept();
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream());
          ) {
        // while 문을 사용하여 반복문을 돌려 서버가 종료되지 않도록 한다.
        while (true) {
          String request = in.readLine();

          out.println(request);
          out.flush();

          // quit 명령어를 입력받으면 종료하게 만든다.
          if (request.equalsIgnoreCase("quit")) {
            break;
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

03) 응답의 끝에는 빈 줄을 보내도록 응답 프로토콜을 정의한다.

public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      try (Socket socket = serverSocket.accept();
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream());
          ) {

        while (true) {
          String request = in.readLine();

          // 응답 프로토콜을 정의한 메서드를 실행한다.
          sendResponse(out, request);

          // quit 명령어를 입력받으면 종료하게 만든다.
          if (request.equalsIgnoreCase("quit")) {
            break;
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // 응답의 끝에는 빈 줄을 보내도록 응답 프로토콜을 정의한다.
  private static void sendResponse(PrintWriter out, String message) {
    out.println(message);
    out.println();
    out.flush();
  }
}

04) 다중 클라이언트 요청 처리 - 접속 순서대로 처리함

  • 클라이언트 요청이 끊어지면 다음 클라이언트와 연결하는 것을 반복한다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      // 클라이언트의 요청이 있을 때까지 대기하는 것을 반복한다.
      while (true) {
        handleClient(serverSocket.accept());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void sendResponse(PrintWriter out, String message) {
    out.println(message);
    out.println();
    out.flush();
  }

  // 클라이언트 연결이 끊어지면 다음 클라이언트와 연결하는 것을 반복한다.
  private static void handleClient(Socket clientSocket) {
    try (Socket socket = clientSocket;
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        ) {

      while (true) {
        String request = in.readLine();

        sendResponse(out, request);

        if (request.equalsIgnoreCase("quit")) {
          break;
        }
      }
    } catch (Exception e) {
      System.out.println("클라이언트와의 통신 오류");
    }
  }
}
  • 클라이언트가 접속하거나 연결을 끊으면 로그를 남기도록 한다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      while (true) {
        handleClient(serverSocket.accept());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void sendResponse(PrintWriter out, String message) {
    out.println(message);
    out.println();
    out.flush();
  }

  private static void handleClient(Socket clientSocket) {

    // 클라이언트가 접속하면 로그를 남긴다.
    InetAddress address = clientSocket.getInetAddress();
    System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
        address.getHostAddress());

    try (Socket socket = clientSocket;
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        ) {

      while (true) {
        String request = in.readLine();

        sendResponse(out, request);

        if (request.equalsIgnoreCase("quit")) {
          break;
        }
      }
    } catch (Exception e) {
      System.out.println("클라이언트와의 통신 오류");
    }

    // 클라이언트가 연결을 끊으면 로그를 남긴다.
    System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
        address.getHostAddress());
  }
}

05) 다중 클라이언트의 동시 접속 처리

  • 클라이언트의 요청 처리를 별도의 스레드로 분리한다.
    • ClientHandler 클래스 추가
      • Runnable 인터페이스를 구현한다.
      • 클라이언트의 요청 처리를 담당한다.
      • ServerApp 에서 해당 코드를 가져온다.
    • ServerApp 클래스 변경
      • 클라이언트 요청 처리를 ClientHandler에게 맡긴다.
// 클라이언트 요청을 처리하는 일을 한다.
public class ClientHandler implements Runnable {
  Socket socket;

  // ClientHandler의 내장 변수 socket에 클라이언트가 접속한 소켓을 저장한다.
  public ClientHandler(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    InetAddress address = this.socket.getInetAddress();
    System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
        address.getHostAddress());

    // this.socket으로 하면 try 문을 떠날 때 close(); 가 자동으로 호출된다.
    try (Socket socket = this.socket;
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        ) {

      while (true) {
        String request = in.readLine();

        sendResponse(out, request);

        if (request.equalsIgnoreCase("quit")) {
          break;
        }
      }
    } catch (Exception e) {
      System.out.println("클라이언트와의 통신 오류");
    }

    System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
        address.getHostAddress());
  }

  private static void sendResponse(PrintWriter out, String message) {
    out.println(message);
    out.println();
    out.flush();
  }
}
  • 다중 클라이언트의 동시 접속을 처리한다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      // 스레드를 생성해서 메인 스레드와 클라이언트의 요청을 수행하는
      // 클라이언트 핸들러 스레드를 분리해서 실행한다.
      // 이렇게 하면 클라이언트가 접속할 때마다
      // 새로운 스레드를 실행하기 때문에 다중 클라이언트의 동시 접속을 처리할 수 있다.
      while (true) {
        //        ClientHandler clientHandler = new ClientHandler(serverSocket.accept());
        //        Thread t = new Thread(clientHandler);
        //        t.start();
        new Thread(new ClientHandler(serverSocket.accept())).start();

      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
  • ClientHandler 클래스를 중첩 클래스로 만든다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      while (true) {
        new Thread(new ClientHandler(serverSocket.accept())).start();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // ClientHandler 클래스를 ServerApp의 스태틱 중첩 클래스로 선언한다.
  // 다른 클래스가 사용할 일이 없기 때문에 private로 선언한다.
  // 바깥 클래스의 특정 인스턴스 멤버를 사용할 일이 없기 때문에
  // static nested class로 선언한다.
  private static class ClientHandler implements Runnable {
    Socket socket;

    public ClientHandler(Socket socket) {
      this.socket = socket;
    }

    @Override
    public void run() {
      InetAddress address = this.socket.getInetAddress();
      System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
          address.getHostAddress());

      try (Socket socket = this.socket;
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream());
          ) {

        while (true) {
          String request = in.readLine();

          sendResponse(out, request);

          if (request.equalsIgnoreCase("quit")) {
            break;
          }
        }
      } catch (Exception e) {
        System.out.println("클라이언트와의 통신 오류");
      }

      System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
          address.getHostAddress());
    }

    private static void sendResponse(PrintWriter out, String message) {
      out.println(message);
      out.println();
      out.flush();
    }
  }
}
  • 익명 클래스로 만든다.
    • 상속 + 내부 클래스
    • 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
    • 재사용을 안할 것 같은 클래스를 익명 클래스로 만든다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      while (true) {
        Socket clientSocket = serverSocket.accept();

        // 익명 클래스 문법 사용
        new Thread(new Runnable() {
          @Override
          public void run() {
            InetAddress address = clientSocket.getInetAddress();
            System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
                address.getHostAddress());

            try (Socket socket = clientSocket;
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(socket.getOutputStream());
                ) {

              while (true) {
                String request = in.readLine();

                sendResponse(out, request);

                if (request.equalsIgnoreCase("quit")) {
                  break;
                }
              }
            } catch (Exception e) {
              System.out.println("클라이언트와의 통신 오류");
            }

            System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
                address.getHostAddress());
          }

          private void sendResponse(PrintWriter out, String message) {
            out.println(message);
            out.println();
            out.flush();
          }
        }).start();
        new Thread(new ClientHandler(serverSocket.accept())).start();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
  • 익명 클래스를 람다 문법으로 정의한다.
    • 코드가 여러 블록에 중첩되면 될수록 들여쓰기를 하면서 코드를 읽기가 불편해진다.
    • 익명 클래스의 코드를 바깥 클래스의 멤버로 만든 후 그 바깥 클래스의 멤버를 호출한다.
    • 만약 익명 클래스가 메서드 한개짜리 인터페이스를 구현하고 그 코드도 간단하다면 람다 문법을 사용하면 간편하게 할 수 있다.
public class ServerApp {
  public static void main(String[] args) {

    try (ServerSocket serverSocket = new ServerSocket(8888)) {
      System.out.println("서버 실행 중");

      while (true) {
        // 익명 클래스의 바깥 클래스의 멤버를 사용
        Socket clientSocket = serverSocket.accept();

        // 람다 문법 적용
        //        new Thread(new Runnable() {
        //          @Override
        //          public void run() {
        //            handleClient(clientSocket);
        //          }
        //        }).start();
        new Thread(() -> handleClient(clientSocket)).start();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void handleClient(Socket clientSocket) {
    InetAddress address = clientSocket.getInetAddress();
    System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
        address.getHostAddress());

    try (Socket socket = clientSocket;
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        ) {

      while (true) {
        String request = in.readLine();
        sendResponse(out, request);
        if (request.equalsIgnoreCase("quit")) {
          break;
        }
      }
    } catch (Exception e) {
      System.out.println("클라이언트와의 통신 오류");
    }

    System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
        address.getHostAddress());
  }

  private static void sendResponse(PrintWriter out, String message) {
    out.println(message);
    out.println();
    out.flush();
  }
}

Client

01) 간단한 메시지를 송수신 할 수 있도록 한다.

public class ClientApp {
  public static void main(String[] args) {
    // 서버주소 : localhost
    // 서버포트 : 8888
    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      out.println("Hello");
      out.flush();

      String response = in.readLine();
      System.out.println(response);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

02) 사용자가 quit 명령을 입력할 때까지 반복한다.

  • com.eomcs.util 에 Prompt 클래스를 만든다.
public class Prompt {
  static Scanner keyboardScan = new Scanner(System.in);

  // 다른 패키지에서 메서드를 호출할 수 있도록 사용 범위를 public 으로 공개한다.
  public static String inputString(String title) {
    System.out.print(title);
    return keyboardScan.nextLine();
  }

  public static int inputInt(String title) {
    return Integer.parseInt(inputString(title));
  }

  public static Date inputDate(String title) {
    return Date.valueOf(inputString(title));
  }

  // 프롬프트의 사용이 모두 끝났으면
  // 이 메서드를 호출하여 System.in 입력 스트림 자원을 해제하도록 한다.
  public static void close() {
    keyboardScan.close();
  }
}
  • ClientApp 에서 Prompt 를 사용해 명령을 입력받도록 한다.
public class ClientApp {
  public static void main(String[] args) {
    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      // 명령어를 입력받도록 만든다.
      String input = Prompt.inputString("명령> ");
      out.println(input);
      out.flush();

      String response = in.readLine();
      System.out.println(response);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
  • quit 명령어를 입력받으면 종료하게 만든다.
public class ClientApp {
  public static void main(String[] args) {
    // 서버주소 : localhost
    // 서버포트 : 8888
    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      // quit 명령어를 입력받으면 종료하게 만든다.
      while (true) {
        String input = Prompt.inputString("명령> ");
        out.println(input);
        out.flush();

        String response = in.readLine();
        System.out.println(response);

        if (input.equalsIgnoreCase("quit")) {
          break;
        }
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

03) 서버가 빈 줄을 보낼 때까지 응답을 읽는다.

public class ClientApp {
  public static void main(String[] args) {
    // 서버주소 : localhost
    // 서버포트 : 8888
    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      // quit 명령어를 입력받으면 종료하게 만든다.
      while (true) {
        String input = Prompt.inputString("명령> ");
        out.println(input);
        out.flush();

        // 응답을 읽는 메서드를 호출한다.
        receiveResponse(in);

        if (input.equalsIgnoreCase("quit")) {
          break;
        }
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // 서버가 빈 줄을 보낼 때까지 응답을 읽는 메서드를 정의한다.
  private static void receiveResponse(BufferedReader in) throws Exception {
    while (true) {
      String response = in.readLine();
      if (response.length() == 0) {
        break;
      }
      System.out.println(response);
    }
  }
}

04) 다중 클라이언트 요청 처리 - 접속 순서대로 서버에서 리턴받음

  • 애플리케이션 아규먼트를 통해 서버의 주소와 포트 번호를 입력 받는다.
  • 다른 서버에 접속하기 위해서 주소와 포트 번호를 알아야 한다.
public class ClientApp {
  public static void main(String[] args) {

    // 애플리케이션 아규먼트를 통해 서버의 주소와 포트 번호를 입력 받는다.
    if (args.length != 2) {
      System.out.println("프로그램 사용법");
      System.out.println("java -cp ... ClientApp 서버주소 포트번호");
      // JVM 강제 종료 - 정상종료
      // 비정상 종료일 때는 종료 상태값이 1이 된다.
      System.exit(0);
    }

    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      while (true) {
        String input = Prompt.inputString("명령> ");
        out.println(input);
        out.flush();

        receiveResponse(in);

        if (input.equalsIgnoreCase("quit")) {
          break;
        }
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void receiveResponse(BufferedReader in) throws Exception {
    while (true) {
      String response = in.readLine();
      if (response.length() == 0) {
        break;
      }
      System.out.println(response);
    }
  }
}

05) 04와 동일

Categories:

Updated: