본문 바로가기

Project

데이터 관리 서버 만들기 - 5) 통신 기능을 캡슐화

728x90

Cilent

 

1단계 - 서버에 요청하고 응답받는 코드를 캡슐화한다.

 

- `.request.RequestAgent` 클래스 정의

 

// 역할
// - 통신 프로토콜에 맞춰 서버에게 요청을 전달하고 응답을 받는 일을 한다.
//
public class RequestAgent implements AutoCloseable {

  Socket socket;
  PrintWriter out;
  BufferedReader in;

  String status;
  String jsonData;

  public RequestAgent(String ip, int port) throws Exception {
    socket = new Socket(ip, port);  
    out = new PrintWriter(socket.getOutputStream());
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  }

  public void request(String command, Object value) throws Exception {
    // 서버쪽으로 데이터를 보낸다.
    // - 서버에 명령어를 한 줄 보낸다.
    out.println(command);

    // - 객체를 JSON으로 변환하여 서버에 보낸다.
    if (value != null) {
      out.println(new Gson().toJson(value));
    } else {
      out.println(); // 보낼 객체가 없으면 빈 문자열을 보내 서버에게 알린다.
    }
    out.flush();

    // 서버에서 응답을 받는다.
    status = in.readLine();
    jsonData = in.readLine();
  }

  public String getStatus() {
    return status;
  }

  public <T> T getObject(Class<T> type) {
    return new Gson().fromJson(jsonData, type);
  }

  @Override
  public void close() {
    try {out.close();} catch (Exception e) {}
    try {in.close();} catch (Exception e) {}
    try {socket.close();} catch (Exception e) {}
  }
}

 

### 2단계 - 서버에 요청하는 기능을 RequestAgent로 대체한다.

 

- `.pms.ClientApp` 클래스 변경

 

public class ClientApp {

  static RequestAgent requestAgent;

  public static void main(String[] args) throws Exception {
    System.out.println("[PMS 클라이언트]");

    requestAgent = new RequestAgent("127.0.0.1", 8888);

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

      if (input.equals("/board/add")) {
        addBoard();

      } else if (input.equals("/board/detail")) {
        detailBoard();

      } else {
        requestAgent.request(input, null);

        if (requestAgent.getStatus().equals("success")) {
          String result = requestAgent.getObject(String.class);
          System.out.println(">>> " + result);
        } else {
          System.out.println("명령 요청 실패!");
        }
      }

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

    requestAgent.close();

    Prompt.close();
  }

  private static void addBoard() throws Exception {

    Board board = new Board();
    board.setNo(1);
    board.setTitle("제목1");
    board.setContent("내용1");
    board.setRegisteredDate(Date.valueOf("2021-1-1"));

    Member m = new Member();
    m.setNo(100);
    m.setName("aaa");
    m.setEmail("aaa@test.com");
    board.setWriter(m);

    // 서버에 요청하고,
    requestAgent.request("/board/add", board);

    // 서버가 보낸 결과를 확인한다.
    if (requestAgent.getStatus().equals("success")) {
      System.out.println("게시글 저장 성공!");
    } else {
      System.out.println("게시글 저장 실패!");
    }
  }

  private static void detailBoard() throws Exception {

    HashMap<String,Object> map = new HashMap<>();
    map.put("no", 100);

    // 서버에 요청하고,
    requestAgent.request("/board/detail", map);

    // 서버가 보낸 결과를 확인한다.
    if (requestAgent.getStatus().equals("success")) {
      Board board = requestAgent.getObject(Board.class);
      System.out.println(board);

    } else {
      System.out.println("게시글 조회 실패!");
    }

  }

}

 

### 3단계 - 서버가 응답하는 메시지를 상수 필드로 전환한다.

 

- `.request.RequestAgent` 클래스 정의

- `.pms.ClientApp` 클래스 변경

 

// 역할
// - 통신 프로토콜에 맞춰 서버에게 요청을 전달하고 응답을 받는 일을 한다.
//
public class RequestAgent implements AutoCloseable {

  public static final String SUCCESS = "success";
  public static final String FAIL = "fail";

  Socket socket;
  PrintWriter out;
  BufferedReader in;

  String status;
  String jsonData;

  public RequestAgent(String ip, int port) throws Exception {
    socket = new Socket(ip, port);  
    out = new PrintWriter(socket.getOutputStream());
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  }

  public void request(String command, Object value) throws Exception {
    // 서버쪽으로 데이터를 보낸다.
    // - 서버에 명령어를 한 줄 보낸다.
    out.println(command);

    // - 객체를 JSON으로 변환하여 서버에 보낸다.
    if (value != null) {
      out.println(new Gson().toJson(value));
    } else {
      out.println(); // 보낼 객체가 없으면 빈 문자열을 보내 서버에게 알린다.
    }
    out.flush();

    // 서버에서 응답을 받는다.
    status = in.readLine();
    jsonData = in.readLine();
  }

  public String getStatus() {
    return status;
  }

  public <T> T getObject(Class<T> type) {
    return new Gson().fromJson(jsonData, type);
  }

  @Override
  public void close() {
    try {out.close();} catch (Exception e) {}
    try {in.close();} catch (Exception e) {}
    try {socket.close();} catch (Exception e) {}
  }
}

 

public class ClientApp {

  static RequestAgent requestAgent;

  public static void main(String[] args) throws Exception {
    System.out.println("[PMS 클라이언트]");

    requestAgent = new RequestAgent("127.0.0.1", 8888);

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

      if (input.equals("/board/add")) {
        addBoard();

      } else if (input.equals("/board/detail")) {
        detailBoard();

      } else {
        requestAgent.request(input, null);

        if (requestAgent.getStatus().equals(RequestAgent.SUCCESS)) {
          String result = requestAgent.getObject(String.class);
          System.out.println(">>> " + result);
        } else {
          System.out.println("명령 요청 실패!");
        }
      }

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

    requestAgent.close();

    Prompt.close();
  }

  private static void addBoard() throws Exception {

    Board board = new Board();
    board.setNo(1);
    board.setTitle("제목1");
    board.setContent("내용1");
    board.setRegisteredDate(Date.valueOf("2021-1-1"));

    Member m = new Member();
    m.setNo(100);
    m.setName("aaa");
    m.setEmail("aaa@test.com");
    board.setWriter(m);

    // 서버에 요청하고,
    requestAgent.request("/board/insert", board);

    // 서버가 보낸 결과를 확인한다.
    if (requestAgent.getStatus().equals(RequestAgent.SUCCESS)) {
      System.out.println("게시글 저장 성공!");
    } else {
      System.out.println("게시글 저장 실패!");
    }
  }

  private static void detailBoard() throws Exception {

    HashMap<String,Object> map = new HashMap<>();
    map.put("no", "1");

    // 서버에 요청하고,
    requestAgent.request("/board/selectOne", map);

    // 서버가 보낸 결과를 확인한다.
    if (requestAgent.getStatus().equals(RequestAgent.SUCCESS)) {
      Board board = requestAgent.getObject(Board.class);
      System.out.println(board);

    } else {
      System.out.println("게시글 조회 실패!");
    }

  }

}

 

Server

 

1단계 - 클라이언트의 요청 정보를 다루고 응답을 다루는 코드를 캡슐화 한다.

 

- .server.RequestProcessor 클래스 정의

- 클라이언트의 요청 정보를 다루는 일을 한다.

 

// 역할
// - 클라이언트와 통신하는 일을 담당한다.
// - 클라이언트 요청이 들어오면 그 요청을 처리할 객체를 찾아 실행하는 일을 한다.
// - 클라이언트 요청 정보를 객체에 보관하고, 응답 기능을 수행할 객체를 만드는 일을 한다.
// 
public class RequestProcessor implements AutoCloseable {
  Socket socket;
  PrintWriter out;
  BufferedReader in; 

  BoardTable boardTable = new BoardTable();

  public RequestProcessor(Socket socket) throws Exception {
    this.socket = socket;
    out = new PrintWriter(socket.getOutputStream());
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

  }

  @Override
  public void close() {
    try {out.close();} catch (Exception e) {}
    try {in.close();} catch (Exception e) {}
    try {socket.close();} catch (Exception e) {}
  }

  public void service() throws Exception {
    while (true) {
      String command = in.readLine();

      if (command.equalsIgnoreCase("quit")) {
        in.readLine(); // 클라이언트가 quit 명령을 보낼 때 이어지는 값으로 빈 문자열을 데이터로 보낸다.
        out.println("success");
        out.println("goodbye");
        out.flush();
        break;

      } else if (command.startsWith("/board/")) {
        Request request = new Request(command, in.readLine());
        Response response = new Response();

        boardTable.execute(request, response);

        // Response 객체에 보관된 실행 결과를 클라이언트에게 보낸다.
        out.println(response.status);
        if (response.getValue() != null) {
          out.println(new Gson().toJson(response.getValue()));
        } else {
          out.println();
        }
        out.flush();

      } else {
        in.readLine(); // 클라이언트가 보낸 문자열을 읽어서 버린다.
        out.println("success");
        out.println(command);
        out.flush();
      }
    }
  }

}

 

- .server.Request 클래스 정의

- 요청 명령을 조회하고 요청 데이터를 꺼내는 일을 한다.

 

// 역할
// - 클라이언트 보낸 명령과 JSON 데이터를 보관하는 일을 한다.
// - 클라이언트가 보낸 데이터를 특정 타입의 객체로 리턴하는 일을 한다.
//
public class Request {

  String command;
  String jsonData;

  public Request(String command, String jsonData) {
    this.command = command;
    this.jsonData = jsonData;
  }

  public String getCommand() {
    return command;
  }

  public <T> T getValue(Class<T> type) {
    if (jsonData == null || jsonData.length() == 0) {
      return null;
    }
    return new Gson().fromJson(jsonData, type);
  }
}

 

- .server.Response 클래스 정의

- 클라이언트에게 응답할 데이터를 보관하는 일을 한다.

 

// 역할
// - 클라이언트에게 응답할 정보를 보관하는 일을 한다.
public class Response {

  public static final String SUCCESS = "success";
  public static final String FAIL = "fail";

  String status;
  Object value;

  public String getStatus() {
    return status;
  }
  public void setStatus(String status) {
    this.status = status;
  }
  public Object getValue() {
    return value;
  }
  public void setValue(Object value) {
    this.value = value;
  }
}

 

2단계 - 게시글 데이터를 저장하고 꺼내는 코드를 캡슐화 한다.

 

- .pms.table.BoardTable 클래스 정의

 

// 역할
// - 게시글 데이터를 저장하고 조회하는 일을 한다.
// 
public class BoardTable {

  List<Board> list = new ArrayList<>();

  public void execute(Request request, Response response) throws Exception {
    switch (request.getCommand()) {
      case "/board/insert": insert(request, response); break;
      case "/board/selectOne": selectOne(request, response); break;
      default:
        response.setStatus(Response.FAIL);
        response.setValue("해당 명령을 지원하지 않습니다.");
    }
  }

  private void insert(Request request, Response response) throws Exception {
    Board board = request.getValue(Board.class);
    list.add(board);
    response.setStatus(Response.SUCCESS);
  }

  @SuppressWarnings("unchecked")
  private void selectOne(Request request, Response response) throws Exception {
    Map<String,String> params = request.getValue(Map.class);
    int no = Integer.parseInt(params.get("no"));
    Board board = findByNo(no);
    if (board != null) {
      response.setStatus(Response.SUCCESS);
      response.setValue(board);
    } else {
      response.setStatus(Response.FAIL);
      response.setValue("해당 번호의 게시글이 없습니다.");
    }

  }

  private Board findByNo(int no) {
    for (Board b : list) {
      if (b.getNo() == no) {
        return b;
      }
    }
    return null;
  }
}

 

3단계 - BoardTable 클래스를 이용하여 클라이언트 요청을 처리한다.

 

- .pms.ServerApp 클래스 변경

 

public class ServerApp {

  public static void main(String[] args) throws Exception {
    System.out.println("[PMS 서버]");

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

    Socket socket = serverSocket.accept();
    System.out.println("클라이언트가 접속했음");

    RequestProcessor requestProcessor = new RequestProcessor(socket);
    requestProcessor.service();
    requestProcessor.close();

    System.out.println("서버 종료");
    serverSocket.close();
  }
}