본문 바로가기

국비 교육

2020.10.5일자 수업 : connectionless, HTTP, URL, base64

 

 네트워크 

 

git/eomcs-java-basic/src/main com.eomcs.net.ex05
git/eomcs-java-basic/src/main com.eocms.net.ex06
git/eomcs-java-basic/src/main com.eocms.net.ex07
git/eomcs-java-basic/src/main com.eocms.net.ex08
git/eomcs-java-basic/src/main com.eocms.net.ex10

연결지향 (connection-oriented)

여태까지 살펴보았던 모든 연결 방식은 connection-oriented방식으로,일단 연결이 되어야 데이터를 주고 받을 수 있다.  즉, 서버가 일단 실행된 상태에서 클라이언트가 대기열에 들어간 후, 서버가 클라이언트를 accept 해줘야 데이터를 서로 송수신할 수가 있는 것들이었다. 이와 같은 방식에서는 서버 측에서 accept 해야만 클라이언트에 대한 소켓을 생성한다.

 

이같은 방식으로 서버와 클라이언트가 상대측으로 데이터를 송신할 때, 상대측에서 수신을 보장받을 수 있다. 단. 클라이언트 측에서는 클라이언트가 대기열에 들어가게 되면, 데이터가 랜카드의 램까지만 출력되어도 write() 메서드가 리턴되기 때문에 상대측에 데이터가 전송이 되었는지는 알 수 없다. 

 

ex) FTP, Telnet, SMTP, POP3, HTTP 등

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0110 {
  public static void main(String[] args) throws Exception {
    Socket socket = new Socket("localhost", 8888);
    System.out.println("서버에 연결됨!");

    Scanner in = new Scanner(socket.getInputStream());
    PrintStream out = new PrintStream(socket.getOutputStream());

    out.println("Hello!");
    System.out.println("데이터 보냄!");

    String str = in.nextLine();
    System.out.println("데이터 받음!");
    System.out.println(str);

    in.close();
    out.close();
    socket.close();
    System.out.println("서버와 연결 끊음!");
  }
}

비연결 (connectionless)

서버가 실행되지 않아도 클라이언트측에서 데이터를 보낼 수가 있다. 즉, 연결 없이도 데이터를 송수신한다. 그러나 수신자가 없다면 그 패킷은 폐기처분 되기 때문에 상대방이 데이터를 수신한다는 보장을 할 수가 없다. 

 

ex) ping : 네트워크를 통해 상대에게 접근할 수 있는가를 확인하기 위한 프로그램이다. "핑을 날린다", "핑을 쏜다", "핑을 때린다"라는 표현을 많이 사용한다. URL이나 IP를 지정하면 대상에게 echo를 요청하는 데이터를 전송하고 상대의 echo 응답을 기다리는 형태로 동작한다. 보통 네트워크 상태에 따라서 데이터가 중간에 사라지는 경우도 있으므로 여러 번 핑을 날려서 그 응답을 수집하여 평균적인 결과를 보여주는 형태로 동작한다. (출처 : war3.kr/free/2728)

 

ping이라는 패킷을 보낸 것에 대응하기 위해 서버에서 준비한 다른 서버를 ping 서버라고 한다. 대부분의 서버는 ping 서버를 실행하지 않는다. 굳이 서버가 살아있는지 죽어있는지에 대한 ping 데이터를 알려줄 필요가 없기 때문이다. 이처럼 대부분의 ping 서버가 실행되고 있지 않는데도, ping 패킷을 언제든지 송신할 수가 있다.

 

 

데이터 송수신 방법

보통 클라이언트 측에서 서버에 데이터를 보내기 위해서는 connectionless 방식으로 통신을 수행할 DatagramSocket 객체가 필요하다. 기 객체를 생성하고 난 후에는 본격적으로 데이터를 담아 상대측에 보낼 DatagramPacket 객체가 필요하다. Packet 객체를 생성할 때에는 꼭 필요한 정보들을 파라미터로 주어야한다. 이 정보는 다음과 같다.

  • 데이터 내용 (byte[])
  • 데이터 크기 (int)
  • 받는 이의 주소 (InetAddress) : InetAddress는 생성자가 default로 막혀있기 때문에 스태틱 메서드 getByName을 통해 생성할 수 있다.
  • 받는 이의 포트 번호 (int)

이것에 더하여 패킷이 생성되면 내부적으로 보내는 이의 주소와 포트 번호도 담긴다. 이렇게 생성된 패킷을 소켓의 send 메서드로 전송할 수 있다. 물론 정말 상대측에서 패킷을 받았는 지는 보장할 수 없다.

* Factory 메서드 / 클래스

만약 어떤 클래스의 생성자가 private이나 default로 막혀 사용할 수가 없을 때에는 대신 객체를 생성할 수 있는 Factory 역할을 수행하는 스태틱 메서드 혹은 외부의 다른 클래스가 존재한다.

ex) Calendar - getInstance()

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Client0210 {
  public static void main(String[] args) throws Exception {
    DatagramSocket socket = new DatagramSocket();

    byte[] bytes = "Hello".getBytes("UTF-8");

    DatagramPacket packet = new DatagramPacket(//
        bytes, 
        bytes.length, 
        InetAddress.getByName("localhost"),
        8888
        );

    socket.send(packet);
    System.out.println("데이터 전송 완료!");

    socket.close();

  }
}

 

한편, 서버에서 클라이언트의 데이터를 받을 때에는 서버용 소켓이 따로 없기 때문에 보내는 측과 같이 DatagramSocket을 생성한다. 단, 받는 쪽에서는 소켓 생성할 때 포트번호를 설정해야 한다. 적어도 서버쪽에서 DatagramSocket도 생성되어있지 않으면 클라이언트에서 전송된 패킷은 곧바로 모두 버려진다.

 

본격적으로 데이터를 받기 위해서는 받은 데이터를 저장할 버퍼와 그것을 담은 빈 패킷을 준비해야 한다. DatagramPacket을 생성할 때에는 다음과 같은 정보가 파라미터로 들어가야 한다.

  • 버퍼 / 빈 배열 (byte[])
  • 버퍼의 크기

생성된 빈 패킷을 파라미터로 주고 소켓의 receive 메서드를 호출하면 데이터를 받아 빈 패킷 안에 담는다.

패킷의 데이터를 조회하려면, getData()를 호출하여 바이트 배열을 리턴받을 수 있으며, 패킷에서 받은 바이트의 개수를 알려면 getLength()를 호출하면 된다.

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;

public class Server0210 {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    System.out.println("서버 실행 중...");

    System.out.println("소켓 생성 전 잠깐!>");
    keyScan.nextLine();

    DatagramSocket socket = new DatagramSocket(8888);

    byte[] buf = new byte[8196];

    DatagramPacket emptyPacket = new DatagramPacket(buf, buf.length);

    System.out.println("데이터를 읽기 전에 잠깐 멈춤>");
    keyScan.nextLine();

    socket.receive(emptyPacket);
    System.out.println("데이터를 받았음!");

    socket.close();
    keyScan.close();

    String message = new String(//
        emptyPacket.getData(), 
        0, 
        emptyPacket.getLength(), 
        "UTF-8" 
        );
    System.out.println(message);

  }
}

HTTP 프로토콜

* 프로토콜이란?

클라이언트/서버 간의 통신 규칙, 즉 데이터를 주고 받을 때 사용되는 규칙이다.

 

 

웹서버와 http 클라이언트가 서로 연결되어 데이터를 주고받기 위해서는 http 프로토콜에 따라 올바르게 요청하고 응답해야한다. 보통 클라이언트가 요청을 하게 되고, 서버가 이에 대한 응답을 하게 된다.

 

HTTP에 관한 자세한 정보는 네트워크 페이지에서 틈틈히 정리할 것이다.

 

HTTP 프로토콜에 따른 요청 구조는 다음과 같다.

 

참고 : https://ychae-leah.tistory.com/82

1. Start Line (요청 내용)
HTTP 메서드 Request target HTTP version<CR><LF>

  • HTTP 메서드 : 요청의 의도를 담는 명령어 GET, POST 등
  • Request target : HTTP 요청이 전송되는 목표 주소
  • HTTP version : HTTP의 버전 명시 

2. Header
HTTP 요청에 대한 자체 정보를 담고 있으며, key:value형식을 따른다.

  • Host : 요청이 전송되는 target의 호스트 URL 주소

  • User-Agent : 요청을 보내는 클라이언트의 정보 ex 웹 브라우저의 정보
  • Accept
    • 해당 요청이 받을 수 있는 응답 body 데이터 타입의 정보
    • 모든 타입을 허용하는 경우 */*로 지정
    • MIME type : Multipurpost Internet Mail Extensions (ex. application/json)
  • Connection
    • 해당 요청이 끝난 후에 클라이언트와 서버간의 연결을 계속 유지할 것인지 끊을 것인지 알려주는 헤더
    • HTTP 요청 때마다 네트워크 연결을 새로 만드는 건 번거롭기 때문에 요청이 계속되는 한 처음 연결을 재사용하는 것이 바람직
    • keep-alive : 네트워크 연결을 유지해라
    • close : 더이상 요청을 보내지 않으니 연결을 닫아라
  • Content-Type
    • HTTP 요청이 보내는 메시지 body 타입을 알려줌
    • MIME type
  • Content-Length : 요청이 보내는 메시지 body의 총 사이즈 정보

3. <CR><LF>

4. Body
HTTP 요청이 전송하는 데이터를 담고 있는 부분이며 전송하는 데이터가 없다면 body 부분은 비어있다.

 

 

줄바꿈을 해줄 때마다 <CR>에 해당하는 \r과 <LF>에 해당하는 \n을 붙여줘야 하며, 이외에 whitespace가 있어서는 안된다. 특히 헤더가 끝났을 때는 요청 내용이 끝났다는 의미의 <CR> <LF>도 반드시 붙여야한다.


 *macOS에서 JVM을 실행할 때, println()은 문자열 뒤에 0a(LF) 코드만 붙이므로 println()를 사용하지 않고 직접 \r\n을 붙인다.

 

예시는 다음과 같다.

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class HttpClient {
  public static void main(String[] args) throws Exception {
    Socket socket = new Socket("itempage3.auction.co.kr", 80);
    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());
    
    // GET [자원주소] HTTP/1.1 (CRLF)
    // Host: [서버주소] (CRLF)
    // (CRLF)
    out.print("GET /DetailView.aspx?itemno=C204190906 HTTP/1.1\r\n");
    out.print("Host: itempage3.auction.co.kr\r\n");
    out.print("\r\n");

    while (true) {
      try {
        System.out.println(in.nextLine());
      } catch (Exception e) {
        break;
      }
    }

    out.close();
    in.close();
    socket.close();
  }
}


 

이런식으로 http프로토콜을 모두 작성한 다음 한번에 서버에 보내면 서버가 이에 대한 응답을 한다.

 

HTTP 프로토콜에 따른 응답 구조는 다음과 같다.

참고 : https://ychae-leah.tistory.com/82

1. Status Line
HTTP version Status Code Status Text<CR><LF>

  • HTTP version
  • Status Code
    • 응답 상태를 나타내는 코드 
      (상세 내용 참고 : httpstatuses.com/)
      • 1XX Informational
      • 2XX Success
      • 3XX Redirection
      • 4XX Client Error
      • 5XX Server Error
  • Status Text
    • 응답 상태를 간략하게 글로 설명해줌 (ex. OK)

2. Header

  • HTTP 요청 메시지의 헤더와 동일
  • 단 User-Agent 헤더 대신 Server 헤더가 사용됨

4.<CR><LF>

3. Body

  • HTTP 요청 메시지의 body와 동일
  • 전송하는 데이터가 없으면 비어 있음

서버의 응답에 대한 예시는 다음과 같다.

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

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

      while (true) {
        String str = in.nextLine();
        System.out.println(str);
        if (str.equals(""))
          break;
      }
      
      // HTTP/1.1 200 OK(CRLF)
      // Content-Type: text/html; charset=UTF-8(CRLF)
      // (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();
    }
  }
}

 

이렇게 서버가 응답과 함께 클라이언트가 요청한 html 데이터를 보내면 클라이언트인 웹 브라우저는 이 html을 해석해서 화면에 그린다. 이것을 렌더링이라고 한다.


HTTPS

HTTP의 보안 상의 약점을 보완하기 위하여 HTTP에 암호화나 인증 등의 구조를 더한 것을 HTTPS (HTTP Secure)라고 한다.
HTTPS는 웹페이지의 로그인이나 쇼핑의 결제화면 등에서 사용되고 있으며 HTTPS를 사용할 경우 도메인주소를 갖고 웹 브라우저에 접속 시, http://가 아닌 https://를 사용합니다. 브라우저에서도 HTTPS가 유효한 웹 사이트에 액세스할 경우 자물쇠 마크가 표시되는 등 HTTP와 다르게 표시되기도 한다.

출처: https://blog.sonim1.com/99 [Kendrick's Blog]

HTTPS 프로토콜 방식은 요청과 응답 메시지가 전송되는 과정에서 암호화될뿐만 아니라, 메시지를 받고나서도 올바른 곳에서 온 메시지가 맞는지 검증한다. HTTP 프로토콜을 따르는 사이트는 HTTP번호로 80를 사용하고 이밖에 HTTPS를 따르는 사이트의 포트번호는 443번이다.

 

더 자세한 내용은 네트워크 페이지에서 차차 채워나가게 될 것이다.


URL 클래스

URI ( Uniform Resource Identifier)란?

통합 자원 식별자로, 인터넷에 있는 자원의 위치를 나타내는 유일한 주소이다. URI의 하위 개념으로 URL과 URN이 있다. 일반 사용자는 보편적으로 URL을 많이 사용하고 있다. 일단 URI에서 나타나는 IP주소를 통해 자원을 갖는 컴퓨터에 먼저 접근하고, 그 후에는 포트번호를 통해서 컴퓨터내의 자원을 제공하는 프로그램에 도달한다.

참고 : velog.io/@pa324/%EA%B0%9C%EB%B0%9C%EC%83%81%EC%8B%9D-URI-URL-%EC%B0%A8%EC%9D%B4-%EC%A0%95%EB%A6%AC
  •  

URN (Uniform Resource Name)이란?

위치와 상관없이 리소스의 이름값을 이용해서 접근하는 방식으로 다음과 같이 표현된다.

blog.com/page.html

 

URL ( Uniform Resource Locator)이란?

웹 상에 서비스를 제공하는 각 서버들에 있는 자원의 위치를 표시하는 방식을 취한다.

URL은 다음과 같이 작성한다.

[프로토콜]://서버주소:포트번호/자원의경로?파라미터명=값&파라미터명=값#자원의 내부 위치

  • 프로토콜: http, https, ftp 등
  • 서버주소: IP 주소(192.168.0.1), 도메인명(www.bitcamp.co.kr), 특수 IP(127.0.0.1), 특수 도메인(localhost) 등
  • 포트번호: 포트 번호가 생략되면 서버가 따르는 프로토콜의 기본 포트 번호로 지정된다.
  • 자원의경로: /index.html, /board/list.jsp 등
  • 서버에 보내는 파라미터(Query String): 파라미터명=값&파라미터명=값
  • 자원 내부 위치 : 한 웹페이지에 그려지는 하나의 자원 안에서도 지정된 위치를 사용하면 웹 브라우저가 해당 위치로 자동 스크롤 된다.
    ex) section-5.1

이 밖에 웹 상이 아니라 로컬 자원의 위치도 URL로 표현이 가능하다. 이 URL을 웹 브라우저의 검색창에 올리면, 파일의 내용을 웹 브라우저의 띄울 수도 있다.

로컬 자원에 대한 URL은 다음과 같이 작성한다.

file://자원 경로

자원 경로의 표현 방식은 OS별로 다르다.

  • /드라이브명:/디렉토리 또는 파일 경로 (Windows)
  • /루트디렉토리/디렉토리 또는 파일 경로 (Linux/macOS/Unix)

Windows에서 로컬 파일에 대한 URL 주소 표현은 다음을 예로 들 수 있다.

"file:///c:/Users/user/git/bitcamp-study/Hello.java"

Linux/macOS/Unix에서 파일에 대한 URL 주소 표현은 다음을 예로 들 수 있다.

"file:///Users/eomjinyoung/git/bitcamp-study/Hello.java"

* 자원이란?

웹상에서 클라이언트에게 서비스를 제공하기 위해 서버에 의해 올려진 특정 형식(파일, 프로그램 등)의 데이터

  • 정적 자원(static)
    요청할 때 마다 결과 콘텐트가 변경되지 않는 자원으로 즉 파일을 가리킨다.
    예) HTML, GIF, JPEG, PNG, CSS, JavaScript, TXT 등의 파일
  • 동적 자원(dynamic)
    요청할 때 마다 결과 콘텐트가 변할 수 있는 자원으로 메일 조회, 게시물 변경, 주문 등의 웹 프로그램을 가리킨다.
    예) index.php, index.jsp 등

URL 클래스 생성하기

웹상에 존재하는 자원의 URL 정보를 파라미터로 주어 URL 객체를 생성할 수 있으며, 이 객체의 메서드들을 통해 자원에 대한 정보를 얻을 수 있다.

  • getProtocol : 서버가 따르는 프로토콜의 정보를 리턴한다.
  • getHost : 서버의 주소를 리턴한다. 
  • getPort : 서버의 포트 번호를 리턴하며 만약 포트 번호가 지정되어있지 않다면  메서드는 -1을 리턴한다. 그러나 이것은 각 프로토콜에 맞는 기본 포트 번호를 따른다는 의미로, 만약 해당 서버가 HTTPS를 따른다면 443일 것이며, HTTP를 따른다면 80일 것이다. 
  • getPath : 서버내의 자원이 있는 경로를 리턴한다. 즉, 서버의 주소를 제외한 자원의 경로만이 리턴된다.
  • getQuery : 자원으로 보내지는 파라미터에 대한 정보를 리턴하며 파라미터가 없으면 null을 리턴한다.
  • getRef : 자원에서 지정된 내부 위치에 대한 정보를 리턴한다.
URL url = new URL("https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=bitcamp");

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());
System.out.printf("참조경로(내부위치): %s\n", url.getRef());

 

URL 클래스로 HTTP 요청 수행하기

URL 객체를 통해서 HTTP 프로토콜을 신경쓰지 않고 자동으로 HTTP 요청을 수행할 수가 있다. 심지어는 보안처리된 HTTPS까지도 처리할 수 있다.

 

URL을 통한 HTTP 요청 방법은 두 가지가 있다.

 

1. openStream 메서드 사용하기
URL 객체에서 제공되는 메서드인 openStream을 호출하면 자동으로 URL에 저장된 주소 서버와 연결 후 HTTP 요청을 수행한 후, 웹 서버의 응답 데이터를 읽어들일 도구를 리턴한다. 이 입력 도구를 사용하여 응답데이터를 읽는다.

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 url = new URL("https://www.daum.net");

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

2. openConnection 메서드 이용하기

URL 객체에서 제공되는 메서드인 openConnection 메서드를 호출하면 HTTP 요청을 수행할 객체를 리턴받을 수 있다. 이 객체는 URLConnection이라는 객체로, connect 메서드를 호출하면 웹서버와 자동으로 연결되어 HTTP 요청을 수행한다. 그리고 이 URLConnection이라는 객체의 getInputStream이라는 메서드를 통해서 서버의 응답데이터를 입력받을 수 있는 도구를 리턴받을 수 있다.

 

이뿐만 아니라, 응답 헤더에 대한 다양한 값을 추출하는 메서드도 URLConnection에서 제공하고 있다.

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 = new URL("http://itempage3.auction.co.kr/DetailView.aspx?itemno=C204190906");

    URLConnection con = url.openConnection();

    con.connect();

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

base64

base64는 바이너리 데이터를 텍스트 파일에 삽입하기 위해 바이너리 데이터를 문자화하는 규칙을 말한다.

 

base64는 다음과 같은 방법으로 바이너리 데이터를 문자화한다.

 

바이너리 값을 6비트씩 잘라 64진수로 만든 후 Base64 표(알파벳 대소문자와 숫자)에 정의된 대로 해당 값을 문자로 변환한다.

예 )

"ABC012가간" 문자열

=> 414243303132EAB080EAB081 (16진수, UTF-8 코드)

=> 0100000101000010... (2진수)

=> 010000 / 010100 / 0010... (6비트씩 자른 것)

=> 16 / 20 / ....

=> Q / U / ... (Base64 코드표에 따라 0~63을 문자로 바꾼 것이다.)

=> QUJDMDEy6rCA6rCB6rCE

 

Base64에 따라 문자화된 텍스트 파일을 바이너리 형식으로 다시 바꿀 때는 이 과정을 거꾸로 수행한다.

 

각 변환 과정을 다음과 같이 칭하고 있다.

  • 인코딩 : 바이너리 데이터를 base64에 따라 텍스트 데이터로 변환
  • 디코딩 : base64로 변환된 텍스트 데이터를 다시 원래의 바이너리 데이터로 변환하는 과정

 

텍스트 형식으로 된 html 파일에 base64를 따라 텍스트로 변환한 바이너리 파일을 삽입한 후, 이 html파일을 웹브라우저에 올리면 텍스트로 변환된 데이터가 다시 base64 규칙에 따라 바이너리 형식으로 변환되어 텍스트가 아닌 본래의 형태로 웹페이지에 띄워진다. 

 

자바에서 Base64를 통해 파일 변환하기

자바에서 base64를 따르며 바이너리 파일 <-> 텍스트 파일 변환을 하려면 자바 API에서 제공되는 Base64 클래스를 사용할 수가 있다.

Base64의 getEncoder 스태틱 메서드를 통해 Encoder 객체를 리턴받은 후, 이것으로 encode 메서드를 호출하면 바이너리 파일을 64진수로 표현한 문자 정보를 갖는 byte 배열을 리턴받는다. 이것을 문자열로 변환해주면 원하는 텍스트 형식의 데이터를 얻을 수 있다.

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

이렇게 변환된 텍스트를 다시 바이너리 데이터로 디코딩하고 싶다면, Base64의 getDecoder 스태틱 메서드를 통해 Decoder 객체를 리턴받은 후, 이것으로 decode 메서드를 호출하면 원래의 바이너리 파일을 구성하는 바이트들의 배열을 리턴받을 수 있다.

import java.io.File;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;

public class Exam0121 {

  public static void main(String[] args) throws Exception {
    File file = new File("./sample/test2.txt");

    FileInputStream in = new FileInputStream(file);
    byte[] bytes = in.readAllBytes();
    in.close();

    Decoder decoder = Base64.getDecoder();
    byte[] decodedBytes = decoder.decode(bytes);
    for (byte b : decodedBytes)
      System.out.prinf("%x ", b);
  }
}