2022.08.17/자바 정리/네트워크/리팩토링

Jimin·2022년 8월 17일
0

비트캠프

목록 보기
23/60

  • board-app 프로젝트 수행
      1. 네트워킹을 이용한 파일 공유: client/server app. 아키텍처로 전환(계속)
      1. Client/Server 리팩토링
  • 네트워크 프로그래밍 개요
    • ServerSocket, Socket 클래스 사용법
    • IP 주소와 포트 번호

040. 네트워킹을 이용한 파일 공유: client/server app. 아키텍처로 전환

board-app-server 작업 내용

1단계 - client/server 프로젝트로 분리한다.

  • board-app/app-client 프로젝트 폴더를 준비한다.
  • board-app/app-server 프로젝트 폴더를 준비한다.

2단계 - server 프로젝트를 Eclipse IDE로 임포트 한다.

  • gradle eclipse 명령을 수행하여 이클립스 설정 파일을 준비한다.
  • 이클립스에서 임포트 한다.

3단계 - ServerApp 클래스를 생성한다.

  • com.bitcamp.board.ServerApp 클래스 생성
    • App 클래스에서 서버 기능을 가져온다.

4단계 - XxxServlet 클래스를 추가한다.

  • com.bitcamp.servlet.Servlet 인터페이스 추가
    • 클라이언트 요청을 다룰 객체의 사용 규칙을 정의한다.
  • com.bitcamp.board.servlet.XxxServlet 클래스 추가
    • 기존의 XxxHandler의 코드를 가져와서 변경한다.
    • 클라이언트 요청에 응답하는 일을 수행한다.
  • com.bitcam.board.dao.XxxDao 클래스 변경
    • update() 메서드 추가한다.
  • com.bitcamp.util.Prompt 클래스 삭제
    • 서버에서는 해당 클래스가 필요 없다.

board-app-client 작업 내용

1단계 - client/server 프로젝트로 분리한다.

  • board-app/app-client 프로젝트 폴더를 준비한다.
  • board-app/app-server 프로젝트 폴더를 준비한다.

2단계 - client 프로젝트를 Eclipse IDE로 임포트 한다.

  • gradle eclipse 명령을 수행하여 이클립스 설정 파일을 준비한다.
  • 이클립스에서 임포트 한다.

3단계 - ClientApp 클래스를 생성한다.

  • com.bitcamp.board.ClientApp 클래스 생성
    • App 클래스에서 클라이언트 기능을 가져온다.

4단계 - XxxHandler 클래스에 통신 기능을 넣는다.

  • com.bitcamp.board.XxxHandler 클래스 변경
    • 데이터를 처리할 때 서버에 요청한다.
  • com.bitcamp.board.dao.XxxDao 클래스 삭제
    • 데이터 관리는 서버에서 수행하기 때문에 클라이언트에서 제거한다.

board-app project

board-app-server

BoardServlet class

  • BoardServlet 생성자
    • 여기서 load()를 호출한다.
private BoardDao boardDao;
  private String fileName;

  public BoardServlet(String dataName){
    fileName = dataName + ".json";
    boardDao = new BoardDao(fileName); 

    try{
      boardDao.load();
    }catch(Exception e) {
      System.out.printf("%s 파일 로딩 중 오류 발생!\n", fileName);
      e.printStackTrace();
    }
  }
  • service() 메소드의 switch문 수정
    • client로부터 command를 읽어온다.
    • case "findAll":
case "findAll": 
          Board[] boards = boardDao.findAll();
          out.writeUTF(SUCCESS); // client에게 응답
          out.writeUTF(new Gson().toJson(boards)); //client에게 전송
          break;
  • case "findByNo":
case "findByNo":
          no = in.readInt(); // client가 보낸 데이터 추가로 읽기
          board = boardDao.findByNo(no);
          if(board != null) {
            out.writeUTF(SUCCESS);
            out.writeUTF(new Gson().toJson(board));
          }else {
            out.writeUTF(FAIL);
          }
          break;
  • case "insert":
    • 데이터에 변화가 생기므로 save() 호출한다.
case "insert": 
          json = in.readUTF(); // json 형식의 문자열 읽기
          board = new Gson().fromJson(json, Board.class); // 객체의 타입정보 알려주기
          boardDao.insert(board);
          boardDao.save();
          out.writeUTF(SUCCESS);
          break;
  • case "update":
    • 데이터에 변화가 생기므로 save() 호출한다.
case "update":
          json = in.readUTF(); // json 형식의 문자열 읽기
          board = new Gson().fromJson(json, Board.class); // 객체의 타입정보 알려주기
          if(boardDao.update(board)) {
            boardDao.save();
            out.writeUTF(SUCCESS);
          }else {
            out.writeUTF(FAIL);
          }
          break;
  • case "delete":
    • 데이터에 변화가 생기므로 save() 호출한다.
case "delete": 
          no = in.readInt(); // client가 보낸 데이터 추가로 읽기
          if(boardDao.delete(no)) {
            boardDao.save();
            out.writeUTF(SUCCESS);
          } else {
            out.writeUTF(FAIL);
          }
          break;

BoardDao class

  • BoardDao class에 update() 메소드 추가
    • BoardServlet class의 case update에서 사용된다.
public boolean update(Board board) {
    for (int i = 0; i < list.size(); i++) {
      Board b = list.get(i);
      if (b.no == board.no) {
        list.set(i, board);
        return true;
      }
    }
    return false;
  }
  • 새 게시글을 등록할 때, 게시글 번호가 알맞게 설정되게 하기 위하여 load() 메소드에 다음의 코드를 추가한다.
// 게시글 데이터를 로딩한 후, 마지막 게시글 번호를 설정해 준다.
// 새 게시물의 번호 설정
boardNo = arr[arr.length-1].no; 

Servlet interface

  • success와 fail을 반복해서 BoardServlet, MemberServlet에서 출력하므로 인터페이스안에 규칙으로 만들어준다.
    → 오타 방지
  • 인터페이스에서의 변수는 public static final 표시가 없더라도 public 스태틱 상수로 선언된다.
public interface Servlet {
  String SUCCESS = "success";
  String FAIL = "fail";
  ...
}

App class, Prompt class 삭제하기


board-app-client

BoardHandler

  • onList()
private void onList() throws Exception{

    out.writeUTF(dataName); // 데이터명
    out.writeUTF("findAll"); // command

    if(in.readUTF().equals("fail")) {
      System.out.println("목록을 가져오는데 실패했습니다!");
      return;
    }

    String json = in.readUTF();
    Board[] boards = new Gson().fromJson(json, Board[].class);
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

    System.out.println("번호\t제목\t조회수\t작성자\t등록일");

    for (Board board : boards) {
      Date date = new Date(board.createdDate);
      String dateStr = formatter.format(date); 
      System.out.printf("%d\t%s\t%d\t%s\t%s\n",
          board.no, board.title, board.viewCount, board.writer, dateStr);
    }

  }
  • onDetail()
private void onDetail() throws Exception{

    int boardNo = 0;
    while (true) {
      try {

        boardNo = Prompt.inputInt("조회할 게시글 번호? ");
        break;
      } catch (Exception ex) {
        System.out.println("입력 값이 옳지 않습니다!");
      }
    }

	// 서버에 데이터 요청 정보 전송
    out.writeUTF(dataName);
    out.writeUTF("findByNo");
    out.writeInt(boardNo);
    
    // 서버로부터 요청했던 데이터 받기
    if(in.readUTF().equals("fail")) {
      System.out.println("해당 번호의 게시글이 없습니다!");
      return;
    }
    String json = in.readUTF();
    Board board = new Gson().fromJson(json, Board.class);

    System.out.printf("번호: %d\n", board.no);
    System.out.printf("제목: %s\n", board.title);
    System.out.printf("내용: %s\n", board.content);
    System.out.printf("조회수: %d\n", board.viewCount);
    System.out.printf("작성자: %s\n", board.writer);
    Date date = new Date(board.createdDate);
    System.out.printf("등록일: %tY-%1$tm-%1$td %1$tH:%1$tM\n", date);
  }
  • onInput()
private void onInput() throws Exception{
    // 사용자로부터 입력 받기
    Board board = new Board();
    board.title = Prompt.inputString("제목? ");
    board.content = Prompt.inputString("내용? ");
    board.writer = Prompt.inputString("작성자? ");
    board.password = Prompt.inputString("암호? ");
    board.viewCount = 0;
    board.createdDate = System.currentTimeMillis();

    out.writeUTF(dataName);
    out.writeUTF("insert");
    String json = new Gson().toJson(board);
    out.writeUTF(json); // json 서버로 보내기

    if(in.readUTF().equals("success")) {
      System.out.println("게시글을 등록했습니다.");
    } else {
      System.out.println("게시글 등록에 실패했습니다!");
    }
  }
  • onDelete()
private void onDelete() throws Exception{
    // 삭제할 게시글 번호 받기
    int boardNo = 0;
    while (true) {
      try {
        boardNo = Prompt.inputInt("삭제할 게시글 번호? ");
        break;
      } catch (Exception ex) {
        System.out.println("입력 값이 옳지 않습니다!");
      }
    }

    out.writeUTF(dataName);
    out.writeUTF("delete");
    out.writeInt(boardNo);
    if(in.readUTF().equals("success")) {
      System.out.println("삭제하였습니다.");
    } else {
      System.out.println("해당 번호의 게시글이 없습니다!");
    }
  }
  • onUpdate()
    • command를 두 번 입력한다 → (findByNo, update)
    • 원래 변경 객체에 직접 접근하기 때문에 board.title = Prompt...와 같이 수정 안됐는데, 이제 실제 수정은 서버에서 이루어직 때문에 가능하다.
private void onUpdate() throws Exception{
    //    변경할 번호 입력 받기
    int boardNo = 0;
    while (true) {
      try {
        boardNo = Prompt.inputInt("변경할 게시글 번호? ");
        break;
      } catch (Throwable ex) {
        System.out.println("입력 값이 옳지 않습니다!");
      }
    }

    // 변경할 게시글 가져오기
    out.writeUTF(dataName);
    out.writeUTF("findByNo");
    out.writeInt(boardNo);

    if(in.readUTF().equals("fail")) {
      System.out.println("해당 번호의 게시글이 없습니다!");
      return;
    }

    String json = in.readUTF();
    Board board = new Gson().fromJson(json, Board.class);

    board.title = Prompt.inputString("제목?(" + board.title + ") ");
    board.content= Prompt.inputString(String.format("내용?(%s) ", board.content));

    String input = Prompt.inputString("변경하시겠습니까?(y/n) ");
    if (input.equals("y")) {
      // 게시글 변경하기
      out.writeUTF(dataName);
      out.writeUTF("update");
      out.writeUTF(new Gson().toJson(board));
      if(in.readUTF().equals("success")) {
        System.out.println("변경했습니다.");
      }else {
        System.out.println("변경 실패했습니다!");
      }
    } else {
      System.out.println("변경 취소했습니다.");
    }

App class, Dao 패키지 삭제하기


Member server

  • Board와의 차이점은 게시글 번호가 아닌, 이메일로 회원을 조회한다는 점이다.

MemberServlet class

public class MemberServlet implements Servlet {

  private MemberDao memberDao;
  private String fileName;

  public MemberServlet(String dataName) {
    fileName = dataName + ".json";
    memberDao = new MemberDao(fileName);
    try {
      memberDao.load();
    }catch(Exception e) {
      System.out.printf("%s 파일 로딩 중 오류 발생!\n", fileName);
      e.printStackTrace();
    }
  }

  @Override
  public void service(DataInputStream in, DataOutputStream out){
    try {
      String command = in.readUTF();

      // 여러 군데에서 사용하기 위해 여기서 선언
      Member member = null; 
      String email = null;
      String json = null;

      switch (command) {
        case "findAll": 
          Member[] members = memberDao.findAll();
          out.writeUTF(SUCCESS); // client에게 응답
          out.writeUTF(new Gson().toJson(members)); //client에게 전송
          break;

        case "findByEmail":
          email = in.readUTF(); // client가 보낸 데이터 추가로 읽기
          member = memberDao.findByEmail(email);
          if(member != null) {
            out.writeUTF(SUCCESS);
            out.writeUTF(new Gson().toJson(member));
          }else {
            out.writeUTF(FAIL);
          }
          break;

        case "insert": 
          json = in.readUTF(); // json 형식의 문자열 읽기
          member = new Gson().fromJson(json, Member.class); // 객체의 타입정보 알려주기
          memberDao.insert(member);
          memberDao.save();
          out.writeUTF(SUCCESS);
          break;

        case "update":
          json = in.readUTF(); // json 형식의 문자열 읽기
          member = new Gson().fromJson(json, Member.class); // 객체의 타입정보 알려주기
          if(memberDao.update(member)) {
            memberDao.save();
            out.writeUTF(SUCCESS);
          }else {
            out.writeUTF(FAIL);
          }
          break;

        case "delete": 
          email = in.readUTF(); // client가 보낸 데이터 추가로 읽기
          if(memberDao.delete(email)) {
            memberDao.save();
            out.writeUTF(SUCCESS);
          } else {
            out.writeUTF(FAIL);
          }
          break;
        default:
          out.writeUTF(FAIL);
      }//switch
    }catch(Exception e) {
      throw new RuntimeException(e); 
    }//try-catch

  }

}

MemberDao class

public class MemberDao {

  ...

  public boolean update(Member member) {
    for (int i = 0; i < list.size(); i++) {
      Member m = list.get(i);
      if (m.email.equals(member.email)) {
        list.set(i, member);
        return true;
      }
    }
    return false;
  }

  ...
  
}

ServerApp class

// 클라이언트 요청을 처리할 memberServlet 객체 준비
 MemberServlet memberServlet = new MemberServlet("member");

// switch문에 memberServlet의 경우 추가하기
switch(dataName) {

            ...
            
            case "member":memberServlet.service(in, out);break;
            
            default:
              out.writeUTF("fail");
          }

Member client

MemberHandler class

public class MemberHandler extends AbstractHandler {

  String dataName;
  DataInputStream in;
  DataOutputStream out;

  public MemberHandler(String dataName, DataInputStream in, DataOutputStream out) {
    super(new String[] {"목록", "상세보기", "등록", "삭제", "변경"});

    this.dataName = dataName;
    this.in = in;
    this.out =out;
  }

  @Override
  public void service(int menuNo){
    try{
      switch (menuNo) {
        case 1: this.onList(); break;
        case 2: this.onDetail(); break;
        case 3: this.onInput(); break;
        case 4: this.onDelete(); break;
        case 5: this.onUpdate(); break;
      }
    }catch(Exception e) {
      throw new RuntimeException();
    }
  }

  private void onList() {
    try {
      out.writeUTF(dataName);
      out.writeUTF("findAll");
      if(in.readUTF().equals("fail")) {
        System.out.println("목록을 가져오는데 실패했습니다!");
        return;
      }
      String json = in.readUTF();
      Member[] members = new Gson().fromJson(json, Member[].class);

      System.out.println("이메일       이름");

      for (Member member : members) {
        System.out.printf("%s   %s\n",
            member.email, member.name);
      }
    }catch(Exception e) {
      throw new RuntimeException(e);
    } // try-catch

  }

  private void onDetail() {
    try {
      String email = Prompt.inputString("조회할 회원 이메일? ");
      out.writeUTF(dataName);
      out.writeUTF("findByEmail");
      out.writeUTF(email);

      if(in.readUTF().equals("fail")) {
        System.out.println("해당 이메일의 회원이 없습니다!");
        return;
      }

      String json = in.readUTF();
      Member member = new Gson().fromJson(json, Member.class);

      System.out.printf("이름: %s\n", member.name);
      System.out.printf("이메일: %s\n", member.email);
      Date date = new Date(member.createdDate);
      System.out.printf("등록일: %tY-%1$tm-%1$td %1$tH:%1$tM\n", date);

    }catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

  private void onInput() throws Exception{
    try {
      Member member = new Member();

      member.name = Prompt.inputString("이름? ");
      member.email = Prompt.inputString("이메일? ");
      member.password = Prompt.inputString("암호? ");
      member.createdDate = System.currentTimeMillis();

      out.writeUTF(dataName);
      out.writeUTF("insert");
      String json = new Gson().toJson(member);
      out.writeUTF(json); // json 서버로 보내기
      if(in.readUTF().equals("success")) {
        System.out.println("회원을 등록했습니다.");
      }else {
        System.out.println("회원 등록에 실패했습니다!");
      }
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

  private void onDelete() throws Exception{
    try {
      String email = Prompt.inputString("삭제할 회원 이메일? ");
      out.writeUTF(dataName);
      out.writeUTF("delete");
      out.writeUTF(email);

      if(in.readUTF().equals("success")) {
        System.out.println("삭제하였습니다.");
      } else {
        System.out.println("해당 이메일의 회원이 없습니다!");
      }

    }catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

  private void onUpdate() throws Exception{
    try {
      String email = Prompt.inputString("변경할 회원 이메일? ");

      out.writeUTF(dataName);
      out.writeUTF("findByEmail");
      out.writeUTF(email);

      if(in.readUTF().equals("fail")) {
        System.out.println("해당 이메일의 회원이 없습니다!");
        return;
      }

      String json = in.readUTF();
      Member member = new Gson().fromJson(json, Member.class);

      member.name = Prompt.inputString("이름?(" + member.name + ") ");

      String input = Prompt.inputString("변경하시겠습니까?(y/n) ");
      if (input.equals("y")) {
        // 회원 변경하기
        out.writeUTF(dataName);
        out.writeUTF("update");
        out.writeUTF(new Gson().toJson(member));
        if(in.readUTF().equals("success")) {
          System.out.println("변경했습니다.");
        }else {
          System.out.println("변경 실패했습니다!");
        }

      } else {
        System.out.println("변경 취소했습니다.");
      }
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
  }
}

ClientApp class

  • MemberHandler 객체를 핸들러 배열에 추가해준다.
// 핸들러를 담을 레퍼런스 배열을 준비한다.
      Handler[] handlers = new Handler[] { // 파일명을 목적에 맞게 각각 전달
      
          ...
          
          new MemberHandler("member", in, out) // 회원
      };

Client/Server 리팩토링

목록관리ListSetMap
데이터 저장 및 조회시 사용하는 인덱스값: 0부터 순차적으로 증가하는 정수 값
(양수)
key: hash 알고리즘으로 계산한 정수값hash 알고리즘으로 계산한 정수값
입력 순서 유지OXX
객체 중복 저장OXKey가 중복되지 않는다.
특징입력 순서 유지중복 제거빠른 조회 → key를 이용
- ArrayList
- LinkedList
- Vector
HashSet- HashMap: key, value, null 허용
- HashTable:key, value, null 불가!

1단계 - Servlet 객체의 목록을 HashSet으로 관리한다.

  • com.bitcamp.board.ServerApp 클래스 변경
profile
https://github.com/Dingadung

0개의 댓글