## 37. 네트워킹을 이용하여 데이터 공유하기 : Client/Server 아키텍처로 전환
- 네트워크 프로그래밍 방법
- Client와 Server 개념
- 프로토콜에 따라 애플리케이션 간에 데이터를 주고 받기
네트워킹을 이용하여 데이터 공유하기 : Client/Server 아키텍처로 전환
네트워크 프로그래밍 방법
ㄴ Client와 Server 개념
ㄴ 프로토콜에 따라 애플리케이션 간에 데이터를 주고 받기
ㄴ app 폴더 복사하여 app-client, app-server 각각 만들기
report 프로젝트의 settings.gradle
ㄴ setting.gradle 파일에서 rootProject.name 부분에서 app-client 와 app-server 를 추가
app-client 의 build.gradle
ㄴ build.gradle 파일에서 application 부분에서 bitcamp.report.App => bitcamp.report.ClientApp 으로 변경해주기
app-client 의 build.gradle
ㄴ build.gradle 파일에서 프로젝트 이름을 report-client 로 변경해줌
app-server 의 build.gradle
ㄴ build.gradle 파일에서 application 부분에서 bitcamp.report.App => bitcamp.report.ServerApp 으로 변경해주기
app-server 의 build.gradle
ㄴ build.gradle 파일에서 프로젝트 이름을 report-server 로 변경해줌
ㄴ gradle 재설정
ㄴ app-client 프로젝트 import
ㄴ app-server 프로젝트 import
ㄴ Refactor 를 이용해 이름 변경해주기
ClientApp.java
ㄴ main 메서드 틀만 남기고 모든 코드 제거 후 Socket 추가
ClientApp.java
ㄴ socket.getOutputStream()은 Socket 객체에서 출력 스트림을 가져오는 메서드
ㄴ socket.getInputStream()은 Socket 객체에서 입력 스트림을 가져오는 메서드
ㄴ out을 통해 데이터를 소켓으로 전송하고, in을 통해 소켓으로부터 데이터를 수신할 수 있음
ClientApp.java
ㄴ 소켓을 통해 1바이트의 데이터 100을 전송
=> 서버나 클라이언트에서 해당 데이터를 수신하여 처리할 수 있도록 전송하는 것을 의미
ServerApp.java
ㄴ ClientApp 과 마찬가지로 main 메서드 틀만 남기고 모든 코드 제거 후 Socket 추가
ㄴ ServerSocket을 생성하여 8888 포트에서 클라이언트의 연결 요청을 대기함
ㄴ serverSocket.accept() 메서드를 호출하여 클라이언트의 연결 요청이 수락되면, 연결된 클라이언트와 통신하기 위한 Socket 객체를 반환함
ㄴ Socket 객체를 통해 클라이언트와 데이터를 주고받기 위한 InputStream과 OutputStream을 생성함
ㄴ InputStream인 in을 사용하여 클라이언트로부터 데이터를 읽어들임
ㄴ in.read()는 클라이언트로부터 1바이트를 읽어들이고, 읽어들인 바이트 값을 반환함
=> 클라이언트로부터 1바이트를 읽어들이고, 읽어들인 값을 출력
ServerApp.java
ㄴ in.close() -> out.close() -> socket.close() -> serverSocket.close() 를 순서대로 작성하여야 함
=> 마지막에 실행한 것을 먼저 해제해줘야 함
ClientApp.java
ㄴ ClientApp 클래스도 ServerClient 클래스와 마찬가지로 리소스를 적절하게 해제해줘야 함
ㄴ in.close() -> out.close() -> socket.close() 를 순서대로 작성하여야 함
=> 마지막에 실행한 것을 먼저 해제해줘야 함
ClientApp.java
ㄴ 터미널 창에서 입력을 받을 수 있도록 localhost -> args[0] 로 변경해줌
ㄴ 넘어오는 값이 없을 경우 실행 예 내용을 출력하도록 함
ServerApp.java
ㄴ 서버에서는 서버가 실행 중인 경우 "서버 실행 중..." 이라는 메시지를 띄움
터미널에서 실행해보기
ㄴ 서버 실행
ㄴ 클라이언트 실행할 때 아이피 주소를 아규먼트로 넘겨줌
ㄴ Client 에서 100이 출력되도록 해놨기 때문에 100이 출력됨을 확인
ㄴ 넘어오는 값이 없을 경우 실행 예 내용이 출력됨을 확인
ClientApp.java
ㄴ 16진수 코드를 출력하도록 추가
ServerApp.java
ㄴ 출려되어야 하는 값이 3개니까 읽는 것도 3번 읽어야 함
터미널에서 실행해보기
ㄴ 서버 실행
ㄴ 클라이언트 실행할 때 아이피 주소를 아규먼트로 넘겨줌
ㄴ Client 에서 A, B, C 가 출력되도록 해놨기 때문에 A, B, C 가 출력됨을 확인
ClientApp.java
ㄴ 문자열 출력해보기 위한 코드
ServerApp.java
ㄴ 버퍼를 넉넉하게 잡아주고 문자열을 읽어서 출력하기
터미널에서 실행해보기
ㄴ 서버 실행
ㄴ 클라이언트 실행할 때 아이피 주소를 아규먼트로 넘겨줌
ㄴ Client 에서 문자열 "안녕! Hello" 가 출력되도록 해놨기 때문에 문자열 "안녕! Hello" 출력됨
ClientApp.java
ServerApp.java
ㄴ DataOutputStream, DataInputStream 활용하여 값 저장 및 출력
ClientApp.java
ㄴ report 의 App 클래스에서 전체 복사해온 후 main 메서드만 ClientApp 클래스에 작성했던 main 메서드 이용
ㄴ 입력 받는 아규먼트가 2개이므로 arg.length < 1 => arg.length < 2 로 변경
ClientApp.java
ㄴ main 메서드에 있던 해당 코드 execute 메서드로 이동
ClientApp.java
ㄴ 소켓에서 ip, port 를 받도록 함
ClientApp.java
ㄴ ClientApp 에 입력값 넘겨주도록 함
ClientApp.java
ㄴ 위에서 Socket, DataOutputStream, DataInputStream 변수 선언해주기
ClientApp.java
ㄴ execute 메서드로 이동했던 해당 코드 ClientApp 생성자로 이동
=>
ClientApp.java
ㄴ 인스턴스 변수로 생성해준 Socket, DataOutputStream, DataInputStream 의 socket, out, int 이용하여 코드 간결하게 만들기
ClientApp.java
ㄴ 인스턴스 변수 이용
ClientApp.java
ㄴ close 를 실행하는 close 메서드 생성하여 ClientApp 생성자에 있던 close 메서드들 이동시키기
ClientApp.java
ㄴ ClientApp 객체를 이용해 execute() 와 close() 실행하기
ㄴ app 객체는 ClientApp 클래스의 인스턴스이며, 해당 클래스의 메서드와 필드에 접근할 수 있음
ClientApp.java
ㄴ execute 메서드에 존재하는 prompt.close() 코드도 위에서 생성한 close 메서드로 이동시켜줌
ClientApp.java
ㄴ 해당 코드가 가장 먼저 실행되도록 하기
ㄴ BoardListDao 클래스 복사하여 BoardNetworkDao 라는 이름으로 dao 에 추가
BoardNetworkDao.java
ㄴ 해당 코드 필요 없으므로 제거
BoardNetworkDao.java
ㄴ DataInputStream, DataOutputStream 이용
=>
BoardNetworkDao.java
ㄴ 인스턴스 변수 in, out 이용
BoardNetworkDao.java
ㄴ 모든 메서드 null or 0 출력하도록 변경
MemberNetworkDao.java
ㄴ BoardNetworkDao 클래스와 동일하게 변경
=> ItemNetworkDao 도 동일하게 변경
MemberNetworkDao.java
ㄴ DataName 을 받도록 함
=> BoardNetworkDao, ItemNetworkDao 도 동일하게 적용
BoardNetworkDao.java
ㄴ Gson 객체를 생성하여 new Gson()으로 가져옴
=> Java 객체를 JSON 문자열로 변환하고 반대로 JSON 문자열을 Java 객체로 변환
ㄴ toJson(board) 메서드를 호출하여 board 객체를 JSON 형식의 문자열로 변환
ㄴ toJson() 메서드는 주어진 객체를 JSON 문자열로 직렬화하여 반환
ㄴ jsonStr 변수에 변환된 JSON 문자열이 저장됨
BoardNetworkDao.java
=>
BoardNetworkDao.java
ㄴ in.readUTF()를 사용하여 입력 스트림(in)을 통해 서버로부터 응답을 받음
ㄴ 응답은 JSON 형식의 문자열로 전달되며, 이를 fromJson() 메서드를 사용하여 Map<String, Object> 형태의 객체로 변환
=> Board 객체를 JSON 형식으로 변환하여 서버로 전송하고, 서버의 응답을 JSON 형식으로 받아 처리
ㄴ 클라이언트와 서버 간의 데이터 교환을 JSON 형식으로 수행 가능
ㄴ 예외 발생 시 "입력 오류!" 라는 메시지 출력하고 예외 정보를 확인할 수 있도록 코드 작성
BoardNetworkDao.java
ㄴ Gson 한 번만 생성하여 이용
BoardNetworkDao.java
ㄴ status 가 success 가 아닐 경우 "데이터 입력 오류!" 메시지를 출력하도록 함
=>
BoardNetworkDao.java
ㄴ response.get("message")는 서버로부터 받은 응답의 "message" 키에 해당하는 값을 가져오는 것
=> 예외 발생 시 예외 메시지로 활용되며, 사용자에게 적절한 오류 메시지를 전달하는 데 사용함
ㄴ 예외 발생 시 RuntimeException을 생성할 때 (String)response.get("message")를 사용하여 예외 메시지로 설정하고 있는데, 이는 response.get("message")에서 반환되는 값이 Object 타입이므로 형변환을 수행하여 String 타입으로 변환
ㄴ e.getMessage()는 예외 객체 e의 메시지를 반환
BoardNetworkDao.java
ㄴ DataName + "/insert" 를 이용해 서버에서 실행할 명령을 보내는 코드 추가
BoardNetworkDao.java
ㄴ response.get("data")는 응답의 "data" 키에 해당하는 값을 가져옴
=> 게시물 목록에 해당하는 데이터
BoardNetworkDao.java
ㄴ gson.fromJson() : Gson 객체를 사용하여 JSON 문자열을 List<Board> 객체로 변환하는 메서드
ㄴ 첫 번째 매개변수 => JSON 문자열을 전달 (제네릭 타입 정보 전달)
ㄴ 두 번째 매개변수 => 변환할 객체의 타입을 전달 (getType()을 호출하여 실제 Type 객체를 얻음)
ㄴ Gson은 JSON 문자열을 주어진 타입의 객체로 변환하여 반환
ㄴ (String) response.get("data") : response 맵에서 "data"라는 키에 해당하는 값을 가져옴
ㄴ 이 값은 JSON 형식의 문자열 => (String) 형변환을 통해 명시적으로 문자열 타입으로 캐스팅
ㄴ new TypeToken<List<Board>>() {}.getType() : Gson에서 제공하는 TypeToken을 사용하여 제네릭 타입인 List<Board>의 타입 정보를 얻음
=> TypeToken을 사용하면 제네릭 타입의 실제 타입 정보를 얻을 수 있음
ㄴ .getType()을 호출하여 실제 Type 객체를 얻음
ㄴ JSON 형식의 문자열을 List<Board> 객체로 변환하여 반환
=>
BoardNetworkDao.java
ㄴ TypeToken.getParameterized(List.class, Board.class)에서는 List를 원시 타입으로, Board를 타입 인자로 지정
=> 이렇게 하면 Gson은 List<Board>와 같은 제네릭 타입 정보를 생성할 수 있음
ㄴ TypeToken.getParameterized() 메서드를 사용하여 Type 객체를 생성할 때, 제네릭 타입 정보를 보존하기 위해 TypeToken 클래스를 사용함
=> 이렇게 생성된 Type 객체는 Gson에게 변환할 객체의 타입 정보를 전달하는 데 사용됨
ServerApp.java
=>
ServerApp.java
ㄴ ClientApp 클래스 모두 복사해와서 원래 있던 ServerApp 클래스 코드 아래에 붙여넣기
ㄴ 해당 코드 복사하여 아래쪽의 ClientApp 클래스 코드의 메인 메서드에 추가
ServerApp.java
ㄴ ServerApp 클래스에서는 해당 코드 필요 없으므로 제거
ServerApp.java
ㄴ ServerSocket 추가
ServerApp.java
ㄴ 위에 남아있는 원래의 ServerApp 모두 제거 후 ClientApp => ServerApp 으로 모두 변경시켜줌
ServerApp.java
ㄴ ServerApp 생성자에서 ip는 받지 않음
ServerApp.java
ㄴ ServerSocket 객체를 생성하고 port 매개변수를 통해 해당 포트 번호에서 클라이언트의 연결을 수신할 수 있도록 서버 소켓을 초기화 시킴
ServerApp.java
ㄴ 필요 없는 코드 제거
ServerApp.java
ㄴ XxxListDao 이용하려면 이렇게 변경해야됨
ServerApp.java
ㄴ 필요 없는 코드 제거
ServerApp.java
ㄴ 생성자에 있던 해당 코드 execute 메서드로 이동시킴
ServerApp.java
ㄴ 생성자에 있던 해당 코드 생성자 바깥으로 이동시킴
ServerApp.java
ㄴ port 변수를 인스턴스 변수로 선언
ServerApp.java
ㄴ 필요 없는 코드 제거
ServerApp.java
ㄴ ServerApp 에서는 생성자의 매개변수로 port 만 받으므로 이렇게 작성
ServerApp.java
=>
ServerApp.java
ㄴ serverSocket 만 해제해주면 됨
ServerApp.java
ㄴ ServerSocket 객체를 생성하여 port 번호에서 클라이언트의 연결을 수신하기 위해 서버 소켓을 초기화 하도록 함
ㄴ serverSocket.accept() 메서드를 호출하여 클라이언트의 연결을 대기하고, 클라이언트가 접속하면 연결된 Socket 객체를 반환받음ㄴ socket.getOutputStream()을 사용하여 Socket에서 출력 스트림을 얻고, 이를 DataOutputStream으로 래핑
ㄴ socket.getInputStream()을 사용하여 Socket에서 입력 스트림을 얻고, 이를 DataInputStream으로 래핑
ServerApp.java
ㄴ execute() 안에서 인스턴스 변수를 생성하면서 객체 초기화할 것이므로 제거
ServerApp.java
ㄴ 예외처리 해주기
ㄴ in, out, socket 순서대로 close 해주기
ServerApp.java
ㄴ 서버 실행 시 출력할 내용 적어주기
ServerApp.java
ㄴ 서버에서는 데이터 DataInputStream, DataOutputStream 순서 중요
=> 읽어들이는 것을 먼저 해야 함(DataInputStream)
=> close 순서도 바꿔줘야 함
ServerApp.java
ㄴ 서버에서 클라이언트로부터 계속해서 명령을 수신하고 출력하는 무한 루프
ㄴ in.readUTF()를 사용하여 클라이언트로부터 명령을 수신
ㄴ readUTF() 메서드는 클라이언트가 전송한 UTF-8 형식의 문자열을 읽
ㄴ 수신한 명령이 "quit"인 경우, 루프를 종료하고 실행을 멈춤
ㄴ 수신한 명령을 화면에 출력
=> 클라이언트가 "quit" 명령을 전송하기 전까지 계속해서 클라이언트로부터 명령을 수신하고 출력
ServerApp.java
ㄴ "status": "failure"
ㄴ "message": "nono!"
ㄴ 이렇게 저장된 키와 값은 클라이언트에게 전송될 응답 데이터로 사용될 수 있음
=> 클라이언트는 "status" 값이 "failure"인 경우 "message" 값을 출력하거나 해당 메시지를 처리할 수 있음
ServerApp.java
ㄴ Gson 객체를 생성 (Gson은 JSON 데이터를 처리하는 라이브러리)
ㄴ gson.toJson(response)를 사용하여 응답 데이터를 JSON 형식으로 변환
ㄴ 변환된 JSON 데이터를 클라이언트에게 전송
=> 이러한 동작을 통해 클라이언트는 명령을 수신하고 그에 대한 응답을 받을 수 있음
=> 응답 데이터는 JSON 형식으로 전송되며, 클라이언트는 해당 데이터를 JSON으로 파싱하여 필요한 정보를 추출할 수 있음
ServerApp.java
ㄴ out.writeUTF("quit")를 사용하여 "quit" 명령을 서버로 전송
=> writeUTF() 메서드는 UTF-8 형식의 문자열을 전송
ㄴ 전송 도중에 오류가 발생하면, catch 블록으로 이동하여 예외를 처리
=> 클라이언트는 서버에게 "quit" 명령을 전송하여 연결을 종료할 수 있음
ServerApp.java
ㄴ serverApp 에서는 아규먼트를 1개 받으므로 해당 코드는 args.length < 1 일 경우에만 실행
서버 실행
=>
클라이언트 실행
=>
ㄴ 예외 처리 확인
ㄴ 서버에서 해당 내용 확인 가능
ㄴ 예외 처리 확인
ㄴ 서버에서 해당 내용 확인 가능
ServerApp.java
ㄴ 코드 수정
ㄴ 서버 실행하여 확인 시 목록보기 정상 작동