본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 31일차 (2023-07-05) 자바 기초 DAY29_네트워킹을 이용하여 데이터 공유하기 : Client/Server 아키텍처로 전환_Client와 Server 개념_개인프로젝트 - 마트 관리 시스템

by prometedor 2023. 7. 6.
## 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

ㄴ 코드 수정

 

ㄴ 서버 실행하여 확인 시 목록보기 정상 작동