개발자입니다
[Java] 예제 소스 정리 - 네트워킹(HTTP, 연결 방식) 본문
com.eomcs.net.ex06~13
예제 소스 정리
네트워킹
com.eomcs.net.ex06
HTTP 클라이언트 만들기
package com.eomcs.net.ex06;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
// HTTP 요청 프로토콜
// ---------------------------------
// GET [자원주소] HTTP/1.1 (CRLF)
// Host: [서버주소] (CRLF)
// (CRLF)
// ---------------------------------
//
// 프로토콜(protocol)?
// => 클라이언트/서버 간의 통신 규칙.
// => 데이터를 주고 받는 규칙.
//
public class HttpClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("corners.auction.co.kr", 80);
PrintStream out = new PrintStream(socket.getOutputStream());
Scanner in = new Scanner(socket.getInputStream());
// HTTP 요청 프로토콜에 따라 서버에 데이터 전송
// => macOS에서 JVM을 실행할 때, println()은 문자열 뒤에 0a(LF) 코드만 붙인다.
// => 이를 해결하려면, 다음과 같이 명확하게 CRLF 코드를 붙여라.
//
out.print("GET /AllKill/AllDay.aspx?SelectedItemno=C266641719 HTTP/1.1\r\n");
out.print("Host: corners.auction.co.kr\r\n");
out.print("\r\n");
out.flush();
// HTTP 응답 프로토콜에 따라 서버가 보낸 데이터를 수신
while (true) {
try {
System.out.println(in.nextLine());
} catch (Exception e) {
break;
}
}
out.close();
in.close();
socket.close();
}
}
// HTTP 요청 예)
// ---------------------------------------------------------------------------------
// GET /devide?a=400&b=300 HTTP/1.1
// Host: localhost
// Connection: keep-alive
// Cache-Control: max-age=0
// sec-ch-ua: "\\Not;A\"Brand";v="99", "Google Chrome";v="85", "Chromium";v="85"
// sec-ch-ua-mobile: ?0
// Upgrade-Insecure-Requests: 1
// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
// Sec-Fetch-Site: none
// Sec-Fetch-Mode: navigate
// Sec-Fetch-User: ?1
// Sec-Fetch-Dest: document
// Accept-Encoding: gzip, deflate, br
// Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
// (빈 줄)
HTTP 서버 만들기
package com.eomcs.net.ex06;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
// HTTP 응답 프로토콜
// --------------------------------
// HTTP/1.1 200 OK(CRLF)
// Content-Type: text/html; charset=UTF-8(CRLF)
// (CRLF)
// 보낼 데이터
// --------------------------------
public class HttpServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8888);
System.out.println("서버 실행!");
while (true) {
Socket socket = ss.accept();
Scanner in = new Scanner(socket.getInputStream());
PrintStream out = new PrintStream(socket.getOutputStream());
// 클라이언트가 보낸 데이터를 HTTP 요청 프로토콜에 맞춰 읽는다.
while (true) {
String str = in.nextLine();
System.out.println(str);
if (str.equals(""))
break;
}
// HTTP 응답 프로토콜에 따라 클라이언트에게 데이터를 보낸다.
// => macOS에서 JVM을 실행할 때, println()은 문자열 뒤에 0a(LF) 코드만 붙인다.
// => 이를 해결하려면, 다음과 같이 명확하게 CRLF 코드를 붙여라.
//
out.print("HTTP/1.1 200 OK\r\n");
out.print("Content-Type: text/html; charset=UTF-8\r\n");
out.print("\r\n");
out.print("<html><body><h1>안녕!-강사</h1></body></html>\r\n");
out.close();
in.close();
socket.close();
}
}
}
com.eomcs.net.ex07
URL(Uniform Resource Locator) - URL을 다루는 클래스
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam01 {
public static void main(String[] args) throws Exception {
// 웹 상에서 자원의 위치를 표현하는 방법
// => [프로토콜]://서버주소:포트번호/자원의경로?파라미터명=값&파라미터명=값
// - 프로토콜: http(80), https(443), ftp(21/20) 등
// - 서버주소: IP 주소(192.168.0.1), 도메인명(www.bitcamp.co.kr)
// - 포트번호: 80 또는 443(생략할 수 있다), 8080(프록시 서버) 등
// - 자원의경로: /index.html, /board/list.jsp 등
// - 서버에 보내는 파라미터(Query String): 파라미터명=값&파라미터명=값
//
// 자원
// - 정적 자원(static)
// - 요청할 때 마다 결과 콘텐트가 변경되지 않는 자원. 즉 파일을 가리킨다.
// - 예) HTML, GIF, JPEG, PNG, CSS, JavaScript, TXT 등의 파일
// - 동적 자원(dynamic)
// - 요청할 때 마다 결과 콘텐트가 변할 수 있는 자원.
// - 메일 조회, 게시물 변경, 주문 등의 웹 프로그램을 가리킨다.
// - 예) index.php, index.jsp, /board/list 등
//
URL url = new URL("https://search.naver.com:443/search.naver?where=nexearch&sm=top_hty&fbm=1&ie=utf8&query=bitcamp");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort()); // 지정하지 않으면 -1 리턴. 실제 접속할 때는 기본 포트번호 사용.
System.out.printf("자원경로: %s\n", url.getPath());
System.out.printf("서버에 보내는 파라미터: %s\n", url.getQuery());
}
}
URL(Uniform Resource Locator) - 포트번호 생략
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam02 {
public static void main(String[] args) throws Exception {
// 포트번호 명시
// - http://서버주소:포트번호/자원경로?파라미터명=값&파라미터명=값&파라미터명=값
// URL url = new URL("https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=bitcamp");
URL url = new URL("https://search.naver.com/search.naver");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort());
// 웹브라우저에서는 포트번호를 생략하면 80(HTTP), 443(HTTPS)번으로 간주한다.
// 다만 getPort()의 리턴 값은 -1 이다.
System.out.printf("자원경로: %s\n", url.getPath());
System.out.printf("QueryString: %s\n", url.getQuery()); // 없으면 null 리턴
}
}
URL(Uniform Resource Locator) - 문서 내의 위치 지정
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam03 {
public static void main(String[] args) throws Exception {
// 자원의 내부 위치를 표현하는 방법
// - http://서버주소:포트/자원의경로/../xxx#문서의 내부 위치
URL url = new URL("https://tools.ietf.org/html/rfc2616#section-5.1");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort());
System.out.printf("자원경로: %s\n", url.getPath());
System.out.printf("참조경로(내부위치): %s\n", url.getRef());
// 자원 경로 다음에 문서의 내부 위치를 지정하면
// 웹브라우저는 해당 위치로 자동 스크롤 한다.
}
}
URL(Uniform Resource Locator) - 부가 데이터 지정
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam04 {
public static void main(String[] args) throws Exception {
// 서버에 보내는 파라미터를 표시
// - http://서버주소:포트/자원경로?파라미터명=값&파라미터명=값&파라미터명=값
URL url = new URL("https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=bitcamp");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort());
System.out.printf("자원경로: %s\n", url.getPath());
System.out.printf("쿼리스트링: %s\n", url.getQuery());// 없으면 null 리턴
// Query String
// - 자원의 경로(예: /index.php) 다음(물음표 ? 다음)에 오는 파라미터
// - 형식: 파라미터명=값&파라미터명=값&파라미터명=값
// - 예) sm=top_hty&fbm=1&ie=utf8&query=비트캠프
}
}
URL(Uniform Resource Locator) - Windows OS의 로컬 파일 경로
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam05 {
public static void main(String[] args) throws Exception {
// 로컬 자원의 위치를 URL로 표현하는 방법
// file://자원의 경로
// - 자원의 경로
// /드라이브명:/디렉토리 또는 파일 경로 (Windows)
// /루트디렉토리/디렉토리 또는 파일 경로 (Linux/macOS/Unix)
URL url = new URL("file:///c:/Users/user/git/bitcamp-study/Hello.java");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort());
System.out.printf("자원경로: %s\n", url.getPath());
}
}
URL(Uniform Resource Locator) - 유닉스 OS의 로컬 파일 경로
package com.eomcs.net.ex07;
import java.net.URL;
public class Exam06 {
public static void main(String[] args) throws Exception {
// 로컬 자원의 위치를 URL로 표현하는 방법
// file://자원의 경로
// - 자원의 경로
// /드라이브명:/디렉토리 또는 파일 경로 (Windows)
// /루트디렉토리/디렉토리 또는 파일 경로 (Linux/macOS/Unix)
URL url = new URL("file:///Users/eomjinyoung/git/bitcamp-study/Hello.java");
// URL 분석
System.out.printf("프로토콜: %s\n", url.getProtocol());
System.out.printf("서버주소: %s\n", url.getHost());
System.out.printf("포트번호: %d\n", url.getPort());
System.out.printf("자원경로: %s\n", url.getPath());
}
}
com.eomcs.net.ex08
URL 클래스를 이용하여 HTTP 요청 수행하기
package com.eomcs.net.ex08;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class Exam0110 {
public static void main(String[] args) throws Exception {
// URL 클래스를 이용하면 HTTP 프로토콜을 신경쓰지 않고
// HTTP 요청을 수행할 수 있다.
// 특히 HTTPS까지도 처리할 수 있다.
// => URL 주소를 검증하고 준비한다.
URL url = new URL("https://sports.news.naver.com/index");
// => 서버와 연결하고 HTTP 요청을 수행한다.
// => 그런 후에 웹서버의 응답 데이터를 읽어들일 도구를 준비한다.
InputStream in = url.openStream();
// => 서버가 보낸 데이터를 한 줄씩 읽기 위해 데코레이터를 붙인다.
BufferedReader in2 = new BufferedReader(new InputStreamReader(in));
while (true) {
String str = in2.readLine();
if (str == null)
break;
System.out.println(str);
}
in2.close();
in.close();
}
}
URL 요청하기 - URLConnection 사용
package com.eomcs.net.ex08;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class Exam0210 {
public static void main(String[] args) throws Exception {
// => URL 주소를 준비한다.
URL url = new URL("https://sports.news.naver.com/index");
// => URL 정보를 가지고 HTTP 요청을 수행할 객체를 얻는다.
URLConnection con = url.openConnection();
// => 웹서버와 연결한 후 HTTP 요청한다.
con.connect();
// URL.openStream()을 사용하는 것 보다 이점?
// - 응답 헤더의 다양한 값을 추출할 수 있다.
System.out.printf("Content-Type: %s\n", con.getContentType());
System.out.printf("Content-Length: %d\n", con.getContentLength());
System.out.printf("Content-Encoding: %s\n", con.getContentEncoding());
// - 직접 헤더 이름을 사용해서 헤더 값을 추출할 수 있다.
System.out.printf("Content-Type: %s\n", con.getHeaderField("Content-Type"));
System.out.printf("Server: %s\n", con.getHeaderField("Server"));
System.out.println();
// => 웹서버의 응답 데이터를 읽어들일 도구를 리턴한다.
InputStream in = con.getInputStream();
// => 서버가 보낸 데이터를 한 줄씩 읽기 위해 데코레이터를 붙인다.
BufferedReader in2 = new BufferedReader(new InputStreamReader(in));
while (true) {
String str = in2.readLine();
if (str == null)
break;
System.out.println(str);
}
in2.close();
in.close();
}
}
com.eomcs.net.ex10
Base64 인코딩 : byte[] 인코딩 및 디코딩
package com.eomcs.net.ex10;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
public class Exam0110 {
public static void main(String[] args) throws Exception {
String str = "AB";
byte[] bytes = str.getBytes("UTF-8");
for (byte b : bytes) {
System.out.printf("%x ", b);
}
System.out.println();
System.out.println("------------------------------");
// Base64 인코딩
// => 바이너리 데이터를 문자화시킨다.
// => 바이너리 값을 6비트식 잘라서(2의 6승)
// 64진수(0 ~ 63)으로 만든 후 Base64 표에 정의된 대로
// 해당 값을 문자로 변환한다.
// => 보통 바이너리 데이터를 텍스트로 전송하고 싶을 때 많이 사용한다.
// => "ABC012가간" 문자열
// 414243303132EAB080EAB081(UTF-8 코드)
// 4142 ==> 0100000101000010... (2진수)
// 010000 010100 0010... (6비트씩 자른 것)
// 6비트로 자른 것을 다시 10진수로 표현하면 ==> 16 20 ...
// 16을 Base64 코드표에 따라 문자로 바꾸면 ==> Q
// 20을 Base64 코드표에 따라 문자로 바꾸면 ==> U
// ...
// 이런 식으로 문자열을 Base64로 바꾸면 결과는 다음과 같다.
// QUJDMDEy6rCA6rCB6rCE
Encoder encoder = Base64.getEncoder();
byte[] base64Bytes = encoder.encode(bytes);
System.out.println(new String(base64Bytes));
System.out.println("------------------------------");
// Base64 디코딩
// => Base64 코드를 원래의 바이너리 값으로 변환한다.
//
Decoder decoder = Base64.getDecoder();
byte[] bytes2 = decoder.decode(base64Bytes);
System.out.println(new String(bytes2, "UTF-8"));
}
}
Base64 인코딩 : 이미지 파일 --> 인코딩
package com.eomcs.net.ex10;
import java.io.File;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;
public class Exam0120 {
public static void main(String[] args) throws Exception {
File file = new File("./sample/test1.jpg");
FileInputStream in = new FileInputStream(file);
byte[] bytes = in.readAllBytes();
in.close();
Encoder encoder = Base64.getEncoder();
byte[] encodedBytes = encoder.encode(bytes);
System.out.println(new String(encodedBytes));
}
}
계산기 클라이언트 만들기 - 1단계: 단순히 서버에 요청하고 응답을 받아 출력한다.
package com.eomcs.net.ex11.step01;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class CalculatorClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
String input = in.readLine();
System.out.println(input);
input = in.readLine();
System.out.println(input);
input = in.readLine();
System.out.println(input);
} catch (Exception e) {
e.printStackTrace();
}
}
}
계산기 서버 만들기 - 1단계: 단순히 클라이언트 요청에 응답하기
package com.eomcs.net.ex11.step01;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
계산기 클라이언트 만들기 - 2단계: 응답의 종료 조건을 설정하기
- 응답의 종료 조건을 설정하면 언제까지 읽어야 할 지 결정하기 쉽다.
- 빈 줄을 받을 때까지 응답을 읽어 출력한다.
package com.eomcs.net.ex11.step02;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class CalculatorClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
계산기 서버 만들기 - 2단계: 응답의 종료 조건을 설정하기
- 응답의 종료 조건을 설정하면 언제까지 읽어야 할 지 결정하기 쉽다.
- 응답을 완료했다면 빈 줄을 보내 응답을 완료했음을 표시한다.
package com.eomcs.net.ex11.step02;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
out.println("[계산기 서비스]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
계산기 클라이언트 만들기 - 3단계: 응답을 읽는 코드를 별도의 메서드로 분리한다.
- 기능 별로 메서드로 분리하면 코드를 관리하기가 편하다.
package com.eomcs.net.ex11.step03;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class CalculatorClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
readResponse(in);
} catch (Exception e) {
e.printStackTrace();
}
}
static void readResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 3단계: 안내 메시지 전송 코드를 별도의 메서드로 분리한다.
- 클라이언트가 접속했을 때 안내하는 문구를 보내는 코드를 별도의 메서드로 분리한다.
package com.eomcs.net.ex11.step03;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 4단계: 사용자로부터 계산식을 입력 받아서 서버에 전달한다.
package com.eomcs.net.ex11.step04;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
readResponse(in); // 서버의 인사말을 읽기
while (true) {
String input = keyboardScanner.nextLine();
out.println(input);
out.flush();
readResponse(in); // 서버의 실행 결과를 출력
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void readResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 4단계: 클라이언트가 보낸 요청을 받아 그대로 되돌려 준다.
package com.eomcs.net.ex11.step04;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
out.println(request);
out.println();
out.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 5단계: 코드 리팩토링
package com.eomcs.net.ex11.step05;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
receiveResponse(in); // 서버의 인사말을 받기
while (true) {
String input = keyboardScanner.nextLine();
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 5단계: 코드 리팩토링
package com.eomcs.net.ex11.step05;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
sendResponse(out, request); // 클라리언트에게 응답한다.
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 6단계: 사용자에게 프롬프트를 제시하고 계산식을 입력 받기
package com.eomcs.net.ex11.step06;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
receiveResponse(in); // 서버의 인사말을 받기
while (true) {
String input = prompt(keyboardScanner);
if (input == null) {
continue;
}
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 6단계: 클라이언트가 보내온 계산식을 실행하여 결과를 리턴한다.
package com.eomcs.net.ex11.step06;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
String message = compute(request);
sendResponse(out, message); // 클라리언트에게 응답한다.
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String compute(String request) {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
}
static void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 7단계: 프로그램 종료 명령 'quit' 처리하기
package com.eomcs.net.ex11.step07;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
receiveResponse(in); // 서버의 인사말을 받기
while (true) {
String input = prompt(keyboardScanner);
if (input == null) {
continue;
}
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 7단계: 클라이언트의 종료 요청 'quit' 처리
package com.eomcs.net.ex11.step07;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
if (request.equalsIgnoreCase("quit")) {
sendResponse(out, "안녕히 가세요!");
break;
}
String message = compute(request);
sendResponse(out, message); // 클라리언트에게 응답한다.
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String compute(String request) {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
}
static void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 8단계: 예외 처리 추가 (클라이언트는 변경 사항 없음)
keyboardScanner);
if (input == null) {
continue;
}
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 8단계: 예외 처리 추가
package com.eomcs.net.ex11.step08;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
if (request.equalsIgnoreCase("quit")) {
sendResponse(out, "안녕히 가세요!");
break;
}
String message = compute(request);
sendResponse(out, message); // 클라리언트에게 응답한다.
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String compute(String request) {
try {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
static void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
static void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 9단계: 리팩토링 (클라이언트 변경 없음)
package com.eomcs.net.ex11.step09;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
receiveResponse(in); // 서버의 인사말을 받기
while (true) {
String input = prompt(keyboardScanner);
if (input == null) {
continue;
}
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 9단계: 리팩토링
- 클라이언트의 요청을 처리하는 메서드를 별도의 클래스로 분리하기
package com.eomcs.net.ex11.step09;
import java.net.ServerSocket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
// Socket socket = serverSocket.accept();
// RequestProcessor requestProcessor = new RequestProcessor(socket);
// requestProcessor.service();
new RequestProcessor(serverSocket.accept()).service();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.eomcs.net.ex11.step09;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
// 역할:
// - 소켓에 연결된 클라이언트 요청을 처리한다.
public class RequestProcessor {
Socket socket;
public RequestProcessor(Socket socket) {
this.socket = socket;
}
public void service() throws Exception {
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
if (request.equalsIgnoreCase("quit")) {
sendResponse(out, "안녕히 가세요!");
break;
}
String message = compute(request);
sendResponse(out, message); // 클라리언트에게 응답한다.
}
}
}
private String compute(String request) {
try {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
private void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
private void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 10단계: 여러 개의 클라이언트 접속 처리 (클라이언트 변경 없음)
package com.eomcs.net.ex11.step10;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
try (
Scanner keyboardScanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
receiveResponse(in); // 서버의 인사말을 받기
while (true) {
String input = prompt(keyboardScanner);
if (input == null) {
continue;
}
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 10단계: 여러 개의 클라이언트 접속 처리
- 반복문을 이용하여 계속해서 클라이언트의 접속을 처리한다.
package com.eomcs.net.ex11.step10;
import java.net.ServerSocket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
RequestProcessor requestProcessor = new RequestProcessor();
while (true) {
requestProcessor.setSocket(serverSocket.accept());
requestProcessor.service();
// 현재 방식의 문제점?
// - 현재 작업 중인 클라이언트와의 연결이 끝날 때까지
// 다른 클라이언트는 대기열에서 기다려야 한다.
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.eomcs.net.ex11.step10;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
// 역할:
// - 소켓에 연결된 클라이언트 요청을 처리한다.
public class RequestProcessor {
Socket socket;
public void setSocket(Socket socket) {
this.socket = socket;
}
public void service() throws Exception {
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
sendIntroMessage(out);
while (true) {
String request = in.readLine();
if (request.equalsIgnoreCase("quit")) {
sendResponse(out, "안녕히 가세요!");
break;
}
String message = compute(request);
sendResponse(out, message); // 클라리언트에게 응답한다.
}
}
}
private String compute(String request) {
try {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
private void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
private void sendIntroMessage(PrintStream out) throws Exception {
out.println("[비트캠프 계산기]");
out.println("계산기 서버에 오신 걸 환영합니다!");
out.println("계산식을 입력하세요!");
out.println("예) 23 + 7");
out.println(); // 응답의 끝을 표시하는 빈 줄을 보낸다.
out.flush();
}
}
계산기 클라이언트 만들기 - 11단계: Stateful 방식을 Stateless 방식으로 전환
- 요청할 때 연결해서 요청이 끝나면 즉시 연결을 끊는다.
- Stateless 방식은 요청 때마다 연결하기 때문에 요청을 처리하는 데 연결 시간이 추가되는 문제가 있다.
- 즉 한 번에 한 클라이언트의 요청을 처리하기 때문에
특정한 클라이언트에 서버가 묶이는 현상이 덜하다.
- 서버 입장에서는 클라이언트의 대기 시간을 줄일 수 있다.
- 클라이언트 입장에서도 다른 클라이언트가 서버를 독점하는 것을 피할 수 있다.
package com.eomcs.net.ex11.step11;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
Scanner keyboardScanner = new Scanner(System.in);
while (true) {
// 요청 때 마다 연결하기 때문에 서버의 인사말은 더이상 출력하지 않는다.
String input = prompt(keyboardScanner);
if (input == null) {
continue;
} else if (input.equalsIgnoreCase("quit")) {
// quit 명령을 입력할 경우 서버에 접속할 필요가 없이 즉시 클라이언트를 종료한다.
break;
}
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
} catch (Exception e) {
e.printStackTrace();
}
}
keyboardScanner.close();
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 11단계: Stateful 방식을 Stateless 방식으로 전환
- 클라이언트 한 번 접속에 한 번만 요청만 처리한다.
package com.eomcs.net.ex11.step11;
import java.net.ServerSocket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
RequestProcessor requestProcessor = new RequestProcessor();
while (true) {
requestProcessor.setSocket(serverSocket.accept());
requestProcessor.service();
// Stateless 방식도 완벽한 것은 아니다.
// 클라이언트의 특정한 요청을 처리하는 동안에는
// 다른 클라이언트의 요청을 처리하지 못하는 것은 마찬가지이다.
// 예)
// - 더하기를 실행할 때 5초 정도 실행을 지연시켜보자.
// - 그리고 클라이언트의 실행을 테스트 해보라.
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.eomcs.net.ex11.step11;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
// 역할:
// - 소켓에 연결된 클라이언트 요청을 처리한다.
public class RequestProcessor {
Socket socket;
public void setSocket(Socket socket) {
this.socket = socket;
}
public void service() throws Exception {
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
// 클라이언트 접속에 대해 더이상 안내 메시지를 제공하지 않는다.
// 한 번 접속에 한 번의 요청만 처리한다.
sendResponse(out, compute(in.readLine()));
}
}
private String compute(String request) {
try {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/":
result = a / b;
// Stateless 방식의 통신이라 하더라도 결국 순차 처리 방식이기 때문에
// 현재 클라이언트 작업 처리가 늦어지면
// 다음 클라이언트는 무조건 기다려야 한다.
// 이것을 보여주기 위해 다음과 같이 일부러 / 연산에 대해
// 시간을 지연시킬 것이다.
Thread.sleep(10000);
break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
private void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
}
계산기 클라이언트 만들기 - 12단계: 동시에 여러 클라이언트의 요청을 처리하기 (클라이언트 변경 없음)
- 스레드를 이용하면 동시에 여러 클라이언트 요청을 처리할 수 있다.
package com.eomcs.net.ex11.step12;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class CalculatorClient {
public static void main(String[] args) {
Scanner keyboardScanner = new Scanner(System.in);
while (true) {
// 요청 때 마다 연결하기 때문에 서버의 인사말은 더이상 출력하지 않는다.
String input = prompt(keyboardScanner);
if (input == null) {
continue;
} else if (input.equalsIgnoreCase("quit")) {
// quit 명령을 입력할 경우 서버에 접속할 필요가 없이 즉시 클라이언트를 종료한다.
break;
}
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
sendRequest(out, input); // 서버에 요청을 보내기
receiveResponse(in); // 서버의 실행 결과를 받기
} catch (Exception e) {
e.printStackTrace();
}
}
keyboardScanner.close();
}
static String prompt(Scanner keyboardScanner) {
System.out.print("계산식> ");
String input = keyboardScanner.nextLine();
if (input.equalsIgnoreCase("quit")) {
return input;
} else if (input.split(" ").length != 3) { // 사용자가 입력한 값을 검증
System.out.println("입력 형식이 올바르지 않습니다. 예) 23 + 5");
return null;
}
return input;
}
static void sendRequest(PrintStream out, String message) throws Exception {
out.println(message);
out.flush();
}
static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String input = in.readLine();
if (input.length() == 0) {
// 빈 줄을 읽었다면 읽기를 끝낸다.
break;
}
System.out.println(input);
}
}
}
계산기 서버 만들기 - 12단계: 동시에 여러 클라이언트의 요청을 처리하기
- 클라이언트 요청을 처리하는 코드를 main 실행과 분리하여 별도로 실행하게 한다.
- 어떻게?
- 스레드 문법을 이용한다.
package com.eomcs.net.ex11.step12;
import java.net.ServerSocket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
while (true) {
// 클라이언트가 접속하면,
// 각 클라이언트의 요청을 main 실행에서 분리하여 별도로 실행해야 하기 때문에
// 각 클라이언트에 대해 Thread 객체를 따로 만들어 실행한다.
// 그래서 이전처럼 한 객체를 사용할 수는 없다.
RequestProcessor thread = new RequestProcessor(serverSocket.accept());
// run() 메서드를 직접 호출하면 안된다.
// 스레드에게 독립적으로 실행하라고 명령해야 한다.
thread.start();
// start() 메서드는 main 실행과 분리하여 별도의 실행 모드에서 run()을 호출한다.
// 그런 후 run() 메서드가 리턴할 때까지 기다리지 않고 즉시 리턴한다.
// 따라서 기존 클라이언트의 요청을 처리하는데 시간이 걸리더라도
// 다른 클라이언트는 영향을 받지 않는다.
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.eomcs.net.ex11.step12;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
// 역할:
// - 소켓에 연결된 클라이언트 요청을 처리한다.
// - 해당 코드를 main 실행과 분리하여 실행한다.
//
public class RequestProcessor extends Thread {
Socket socket;
public RequestProcessor(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 이 메서드는 한 번 호출되면 재호출될 수 없다.
// 따라서 한 스레드당 한 번만 호출될 수 있다.
//
// => main 실행과 분리하여 독립적으로 실행할 코드가 있다면 이 메서드 안에 둔다.
//
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
// 클라이언트 접속에 대해 더이상 안내 메시지를 제공하지 않는다.
// 한 번 접속에 한 번의 요청만 처리한다.
sendResponse(out, compute(in.readLine()));
} catch (Exception e) {
System.out.printf("클라이언트 요청 처리 중 오류 발생! - %s\n", e.getMessage());
}
}
private String compute(String request) {
try {
String[] values = request.split(" ");
int a = Integer.parseInt(values[0]);
String op = values[1];
int b = Integer.parseInt(values[2]);
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/":
result = a / b;
Thread.sleep(20000); // 일부러 응답을 지연시킨다.
break;
default:
return String.format("%s 연산자를 지원하지 않습니다.", op);
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
private void sendResponse(PrintStream out, String message) {
out.println(message);
out.println();
out.flush();
}
}
계산기 서버 만들기 - 13단계: HTTP 프로토콜 기반 애플리케이션 실행
- 웹브라우저로부터 요청을 받아 응답한다.
- 요청 및 응답 프로토콜은 웹 기반 프로토콜인 HTTP를 사용한다.
- 서버에 요청하는 방법
웹브라우저에서 주소창에 다음과 같이 URL을 입력한다.
예) http://localhost/plus?a=100&b=200
package com.eomcs.net.ex11.step13;
import java.net.ServerSocket;
public class CalculatorServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(80)) {
System.out.println("서버 실행 중...");
while (true) {
RequestProcessor thread = new RequestProcessor(serverSocket.accept());
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.eomcs.net.ex11.step13;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
// 역할:
// - 소켓에 연결된 클라이언트 요청을 처리한다.
// - 해당 코드를 main 실행과 분리하여 실행한다.
// - HTTP 요청에 대해 HTTP 응답을 수행한다.
//
public class RequestProcessor extends Thread {
Socket socket;
public RequestProcessor(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream out = new PrintStream(socket.getOutputStream());) {
// 웹브라우저가 보낸 첫 줄에는 데이터가 포함되어 있기 때문에 따로 추출한다.
String requestLine = in.readLine();
// 나머지 요청과 관련된 헤더 정보(부가 정보)는 현재는 사용할 일이 없기 때문에 버린다.
while (true) {
if (in.readLine().length() == 0) {
break;
}
}
sendHttpResponse(out, compute(requestLine));
} catch (Exception e) {
System.out.printf("클라이언트 요청 처리 중 오류 발생! - %s\n", e.getMessage());
}
}
private String compute(String request) {
try {
// 웹브라우저가 보낸 request line에서 데이터를 추출한다.
// 예) "GET /plus?a=100&b=200 HTTP/1.1"
String[] values = request.split(" ")[1].split("\\?"); // ["/plus", "a=100&b=200"]
String op = getOperator(values[0]); // "/plus", "/multiple" 등
String[] parameters = values[1].split("&"); // "a=100&b=200" ==> ["a=100", "b=200"]
int a = 0;
int b = 0;
for (String parameter : parameters) {
String[] kv = parameter.split("=");
if (kv[0].equals("a")) { // "a=100"
a = Integer.parseInt(kv[1]);
} else if (kv[0].equals("b")) { // "b=200"
b = Integer.parseInt(kv[1]);
}
}
int result = 0;
switch (op) {
case "+": result = a + b; break;
case "-": result = a - b; break;
case "*": result = a * b; break;
case "/": result = a / b; break;
default:
return "해당 연산자를 지원하지 않습니다.";
}
return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);
} catch (Exception e) {
return String.format("계산 중 오류 발생! - %s", e.getMessage());
}
}
private String getOperator(String name) {
switch (name) {
case "/plus": return "+";
case "/minus": return "-";
case "/multiple": return "*";
case "/devide": return "/";
default:
return "?";
}
}
private void sendHttpResponse(PrintStream out, String message) throws Exception {
out.println("HTTP/1.1 200 OK");
out.println("Content-Type: text/plain;charset=UTF-8");
out.println();
out.print(message);
out.flush();
}
}
package com.eomcs.net.ex11.step13;
public class StringTest {
public static void main(String[] args) {
String requestLine = "GET /plus?a=100&b=200 HTTP/1.1";
String[] values = requestLine.split(" ")[1].split("\\?")[1].split("&");
for (String value : values) {
System.out.println(value);
}
}
}
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[Java] 예제 소스 정리 - 디자인 패턴 13가지 (0) | 2023.02.19 |
---|---|
[Java] 예제 소스 정리 - 스레드 (0) | 2023.02.04 |
[비트캠프] 63일차(13주차5일) - Java(네트워킹 연결방식, 스레드), myapp-33~35 (0) | 2023.02.03 |
[Java] 예제 소스 정리 - 네트워킹(Client/Server, 연결 방식) (0) | 2023.02.02 |
[비트캠프] 62일차(13주차4일) - Java(Client, Server 아키텍처), myapp-32-1~2 (0) | 2023.02.02 |