2020-10-14 TIL
34-c 사용자가 입력한 명령 처리
- stateful
- 1) c/s projcet 준비
- 2) c/s 간에 메시지 송수신 테스트
- 3) 사용자 입력값 송수신
- 4) 다중 클라이언트 요청 처리
- 5) 스레드 적용
- stateless
- 6) stateful -> stateless 전환
- 7) 스레드 재활용 -> 스레드풀
- 8) 기능을 옮긴다
사용자가 입력한 명령을 서버에 전송하기 - 클라이언트앱
- 프롬프트를 가져온다.
- 클라이언트 앱을 변경해서 사용자가 입력한 명령을 보낼 수 있게 만든다.
terminal
- 백엔드에 서버가 있고 사람들은 terminal을 사용했다.
- 1980 년대 IBM terminal
- 터미널에는 모니터, 키보드 네트워크 연결만 있다.
-
본체 하나에 여러 터미널이 연결되어 있어서 멀티 프로세싱 개념이 있었다.
- pc가 발달하면서 pc 하나를 여러 사람이 썼다.
- 개인별 pc가 지급된 다음에는 파일을 외부 pc를 하나 두고 파일을 저장했다.
- 여러 사람이 중복으로 사용했을 대 파일을 덮어쓰는 문제가 발생함.
- 이를 해결하기 위해 순서를 정해서 각자 시간차를 두고 사용함.
- 불편함을 해결하기 위해 pc에 app을 설치하고 그 앱이 파일을 관리하도록 하고 개인별 pc의 앱은 요청을 하도록 했다.
어플리케이션 아키텍처
- 데스크탑
- c/s 어플리케이션
- 어플리케이션 서버
- 웹 어플리케이션 서버
입출력
- FileDB
- 파일 입출력을 위해 만들어 둔 프로그램
- 파일끼리의 관련이 없는 경우에 사용함
- c/s
- 클라이언트(pc)와 서버(DB)로 나눠서 사용
- 해킹 문제가 생김
- 프로그램이 업그레이드 될때마다 재설치
- WAS(Web Application Server)
- 어플리케이션을 실행하는 서버
- 데이터베이스 서버
- pc는 어플리케이션을 실행하는 서버에 접속하도록 한다.
- 웹이 발달
- 어플리케이션을 실행하는 서버는 웹 서버
- 클라이언트는 브라우저 사용
- 모바일 환경이 발달
- 화면이 다름
- 프론트 HTML, Css, java script / 백 java, sql / 모바일 android, ios 로 세분화 됨
반복해서 클라이언트의 요청을 처리한다 - 서버앱
- 앱 변경
- 반복해서 클라이언트에게 응답한 후 종료하지 않고 계속 요청을 처리한다.
- 클라이언트가 quit 명령을 보내면 클라이언트와 연결을 끊는다.
응답 프로토콜 정의
- 응답을 완료한 다음에는 종료의 의미로 빈줄을 보낸다.
클라이언트 연결
- 클라이언트와 연결을 끊은 후 다음 클라이언트와 연결한다.
34-e
- 서버앱의 핸들 클라이언트에서 예외를 던졌을 때 아무도 받지 않으면 아래쪽의 캐치문에서 예외를 받아서 그대로 서버를 종료해버린다.
- 이 문제를 해결하기 위해서 던져진 예외를 핸들 클라이언트를 호출한 쪽에서 받아서 처리하거나 아래쪽의 핸들 클라이언트 메서드 안에서 자체적으로 처리하게 할 수도 있다.
- 이것은 선택의 문제.
- 보통 스레드라면 메서드 안에서 자체적으로 처리해야 한다.
- 스레드는 실이고 하나의 라인
- 스레드를 별도로 만드는 것은 별도의 실을 만드는 것
- 다른 실에서 발생한 예외를 메인 스레드가 받을 방법이 없다.
- 스레드가 아니라면 호출한 쪽에서 처리해도 괜찮다.
- stateful 방식으로 하면 한 명이 서버에 접속했을 때 다른 사람이 서버에 접속할 수가 없다.
- 그래서 멀티 스레드가 필요하다.
- 서버가 윈도우일때
- java -Dfile.encoding=UTF-8 -cp bin/main com.eomcs.pms.ServerApp
- 윈도우즈가 서버일 때 자바 프로그램은 밖에서 들어오는 것을 다 ms949로 인식하고 있다.
- 클라이언트가 맥일 경우 보내는 파일은 utf-8이다.
- 같이 통신하려면 같은 네트워크에 있어야 한다.
43-f 동시에 여러 사람의 요청을 처리하고 싶을 때 - 멀티스레딩
-
클라이언트의 요청을 처리할 clientHandler를 새로 만든다.
-
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()); }
-
this.socket을 socket으로 다시 받는 이유는 이렇게 하면 try 블록에서 떠날 때 close()를 자동 호출할 수 있기 때문이다.
- 인스턴스 멤버를 사용하지 않는 필드는 스태틱으로 하는 것이 원칙이다.
- ex) private static void sendResponse(PrintWriter out, String message) { out.println(message); out.println(); out.flush(); }
- 하지만 받은 파라미터 값만 사용할 때도 인스턴스로 만들 때가 있다.
- ex) private void sendResponse(PrintWriter out, String message) { out.println(message); out.println(); out.flush(); }
-
왜냐? 지금은 인스턴스를 사용하지 않지만 나중에 인스턴스 멤버를 사용하는 코드가 삽입될 수가 있기 때문에 처음부터 인스턴스 멤버로 만든다.
- 서버 변경
- 클라이언트 요청 처리를 클라이언트 핸들러에게 맡긴다.
중첩 클래스
- 중첩 클래스가 바깥 클래스의 인스턴스 멤버를 쓴다는 것은 바깥 클래스의 특정 인스턴스에 종속된다는 것이다.
- 마깥 클래스의 특정 인스턴스 멤버를 사용할 일이 없기 때문에 static nested class로 만든다.
- 인스턴스 멤버인 경우 inner class
- 3) 중첩 클래스로 만들었을 경우 서버와 연결하는 것은 스레드 밖으로 빼야한다.
- 왜? 서버와 연결된 다음에 스레드를 실행해야 하기 때문에.
- 스레드 안으로 넣으면 안된다.
- 4) 익명 클래스의 코드를 바깥 클래스의 멤버로 만들어 놓고 사용한다.
- 왜? 코드를 읽기 쉽도록 하기 위해서
- 코드가 여러 블록에 중첩되면 될수록 들여쓰기를 하면서 코드를 읽기가 불편해진다.
- 5) 람다 문법 적용
- 인터페이스인 경우 메서드 명 지우기
- 메서드 바디만 남기기
- 화살표
- 한 줄일 경우 중괄호 없앨 수 있음
34-g pms 코드를 c/s로 분리
- 1) Json 데이터 포맷을 다룰 Gson 라이브러리를 추가한다.
- build.gradle 파일에 gson 라이브러리 정보를 추가한다.
- 터미널을 사용해 폴더에서 gradle eclipse 를 실행한다.
- 리프레시
- 2) 기존 애플리케이션에서 관련된 패키지 및 클래스를 가져온다.
- 도메인 패키지
- 핸들러 패키지
- 컨텍스트 패키지
- 리스너 패키지
- 앱 클래스에서 옵저버 패턴과 관련된 코드
- 3) client 의 stop 명령을 처리한다.
- 서버앱에 stop 변수 추가
- 클라이언트와 연결할 때 stop의 상태가 true 이면 서버를 멈춘다.
-
4) 서버에 stop 명령을 보내면 클라이언트를 즉시 종료한다.
- 5) 서버앱에 옵저버를 추가한다.
- 서버를 띄울 때 터미널을 사용하면 gson 파일이 없다고 나온다.
- 외부 라이브러리를 사용하기 때문에.
- .classpath 가 라이브러리 정보를 가지고 있다.
- .classpath의 정보를 가지고 이클립스가 command Line을 만들어서 실행한다.
- 클라이언트의 요청을 처리하는 command 객체를 준비한다.
- RequestMappingListener 클래스를 만들고 App에서 필요한 코드를 복사해온다.
- context에 리스트와 커맨드 객체가 다 담겨있다.
- RequestMappingListener를 등록한다.
- 6) 클라이언트 명령이 들어오면 커맨드 객체를 찾아 실행한다.