Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 네트워킹(HTTP, 연결 방식) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 네트워킹(HTTP, 연결 방식)

끈기JK 2023. 2. 3. 20:51

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);
    }
  }
}