Posts 학원 #48일차: JSON 형식, 네트워크
Post
Cancel

학원 #48일차: JSON 형식, 네트워크

mini pms v.32: JSON 형식으로 객체 읽고 쓰기

데이터 교환: XML과 JSON

image image

데이터를 공급하는 쪽과 소비하는 쪽의 언어나 운영체제가 일치하지 않을 수 있다. 텍스트 형식은 운영체제나 프로그래밍 언어에 종속되지 않기 때문에 이기종 간 데이터를 주고받을 때 바이너리보다는 텍스트 형식을 사용한다. 또한 데이터를 교환할 때 데이터의 종류를 파악할 수 있도록 데이터 구조가 짜여 있어야 한다. (예: 날짜 데이터인지 온도 데이터인지 구분) 이때 데이터 교환을 하기 위해서 만들어진 언어가 XML이다.

1
2
<습도>80</습도>
<온도>27</온도>

그러나 XML은 치명적인 단점을 가지고 있는데, 메타 데이터의 크기가 너무 크다는 것이다. 이러한 단점을 상쇄하면서도 데이터 교환에 적합한 포맷이 바로 JSON이다. JSON은 JavaScript Object Notation의 약자로 은 자바스크립트의 객체 표기법을 빌려왔다.

1
2
3
4
{
  습도:80,
  온도:27
}

지금은 애플리케이션과 애플리케이션 간 데이터 교환할 때 XML보다는 JSON이 더 많이 사용된다. Open API는 xml이나 json 형식 모두 제공된다.

JSON

  • 속성- 값 또는 키-값으로 된 데이터 객체를 텍스트로 표현하는 개방형 표준 데이터 포맷이다.
  • {속성: 값, 속성: 값, ...}
1
2
3
4
5
6
7
8
{
  "no":1,
  "name":"1",
  "email":"1",
  "password":"1",
  "photo":"1",
  "tel":"1"
}
  • 텍스트 형식이기 때문에 프로그래밍 언어나 운영체제에 영향을 받지 않는다.
  • 바이너리 방식에 비해 데이터가 커지는 문제가 있지만, 모든 프로그래밍 언어에서 다룰 수 있다는 장점이 있다.
  • 인터넷 상에서 애플리케이션 간에 데이터를 주고받을 때 주로 사용한다.
  • 특히 이기종 플랫폼(OS, 프로그래밍 언어 등) 간에 데이터를 교환할 때 유용하다.

JSON 라이브러리

  • JSON 데이터 포맷을 다루는 라이브러리다.

GSON

  • 구글에서 제공하는 JSON 자바 라이브러리다.
  • 자바 객체를 JSON 형식의 텍스트로 변환하는 기능을 제공한다.
  • JSON 형식의 텍스트를 자바 객체로 변환하는 기능을 제공한다.

DSL: Domain Specific Language

  • 빌드: 애플리케이션을 컴파일하고 필요하다면 압축해서 고객에게 배포할 파일을 만드는 과정
  • 빌드 스크립트 파일: 어떻게 자바 어떤 버전에 호환되게 컴파일할 건지, 외부 라이브러리는 어떤 걸 쓰고 어떤 서버에 있는지 등 빌드 전 과정에 관련되어 과정을 기술하는 빌드 명령서
  • DSL: 빌드 명령서를 작성하는 언어 (ex: Kotlin, Gradle, Groovy)

실습

1단계: Gson 라이브러리를 프로젝트에 추가한다.

  • build.gradle 빌드 스크립트 파일 변경

    Gson 라이브러리 정보를 dependecies {} 블록에 추가한다.

    • https://search.maven.org/ 사이트에서 com.google.code.gson 라이브러리를 선택한다.
    • Gradle Groovy DSL 코드를 복사하여 빌드 스크립트에 붙여 넣는다.
  • $ gradle eclipse 를 실행하여 라이브러리를 다운로드하여 프로젝트에 등록한다.
  • 명령을 실행한 후 eclipse IDE 에서 해당 프로젝트를 refresh 해야 한다.
  • ‘Referenced Libraries’ 노드에서 gson 라이브러리 파일이 추가된 것을 확인한다.

2단계: 데이터를 파일에 저장할 때 JSON 형식으로 출력한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static <T extends CsvObject> void saveObjects(Collection<T> list, File file) {
  BufferedReader in = null;
  try {
    in = new BufferedReader(new FileReader(file));
    
    Gson gson = new Gson();
    String jsonStr = gson.toJson(list);
    out.write(jsonStr);
    
    out.flush();
    
    System.out.printf("총 %s 개의 객체를 '%s' 파일에 저장했습니다.", list.size(), file.getName());
  } catch (IOException e) {
    System.out.printf("객체를 '%s' 파일에  쓰는 중 오류 발생! - %s\n", 
                      file.getName(), e.getMessage());

  } finally {
    try {
      out.close();
    } catch (IOException e) {
    }
  }
}

3단계: 파일로부터 JSON 형식의 텍스트를 읽어서 객체로 변환하여 List 객체에 저장한다.

3.0 이전 버전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void loadObjects(Collection<T> list, File file, CsvObjectFactory<T> factory) {
  BufferedReader in = null;
  
  try {
    in = new BufferedReader(new FileREader(file));
    while (true) {
      String record = in.readLine();
      if (record == null) {
        break;
      }
      list.add(factory.create(record));
    }
    System.out.printf("'%s' 파일에서 총 %d 개의 객체를 로딩했습니다.\n", file.getName(), list.size());
  } catch (Exception e) {
    System.out.printf("'%s' 파일 읽기 중 오류 발생! - %s\n", file.getName(), e.getMessage());
  } finally {
    try {
      in.close();
    } catch (Exception e) {}
  }
}

3.1 직접 문자열을 읽어 Gson에게 전달하기

파일에서 모든 문자열을 읽어 StringBuilder에 담은 다음 최종적으로 String 객체를 꺼낸다. 그리고 JSON 문자열을 가지고 자바 객체를 생성한다.

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
31
32
private static void loadObjects(
  Collection<T> list,
  File file, 
  Class<T[]> clazz // JSON 문자열을 어떤 타입의 배열로 만들 것인지 알려주는 클래스 정보 
) {
  BufferedReader in = null;
  
  try {
    in = new BufferedReader(new FileREader(file));
    // --------------------------------------------
    // 직접 문자열을 읽어 Gson에게 전달하기
    StringBuilder strBuilder = new StringBuilder();
    int b = 0;
    while ((b = in.read()) != -1) {
      strBuilder.append((char)b);
    }
    // JSON 문자열 형식을 가지고 자바 객체를 생성한다.
    Gson gson = new Gson();
    T[] arr = gson.fromJson(strBuilder.toString(), clazz);
    for (T obj : arr) {
      list.add(obj);
    }
    // -------------------------------------------
    System.out.printf("'%s' 파일에서 총 %d 개의 객체를 로딩했습니다.\n", file.getName(), list.size());
  } catch (Exception e) {
    System.out.printf("'%s' 파일 읽기 중 오류 발생! - %s\n", file.getName(), e.getMessage());
  } finally {
    try {
      in.close();
    } catch (Exception e) {}
  }
}

3.2 입력 스트림을 직접 Gson에 전달하기

1
2
3
4
Gson gson = new Gson();
T[] arr = gson.fromJson(in, clazz);
for (T obj : arr)
  list.add(obj);

3.3 배열을 컬렉션에 바로 전달하기

개발자가 반복문을 실행하는 대신 메서드 호출을 통해 목록에 넣는다. Arrays.asList()를 사용하여 배열을 List 구현체로 만들 수 있다. List.addAll()을 이용하면 List 객체를 통째로 추가할 수 있다. 반복문을 사용하는 것보다 간결하다.

컬렉션으로 바로 바꾸는 메서드도 있지만 배열로 읽는 것이 가장 깔끔하다. Json에서는 낱개 말고는 배열밖에 없다. 읽을 때도 배열로 읽는다.

1
2
3
Gson = new Gson();
T[] arr = gson.fromJson(in, clazz);
list.addAll(Arrays.asList(arr));

3.4 코드 정리

1
list.addAll(Arrays.asList(new Gson().fromJson(in, clazz)));

최종 loadObjects

1
2
3
4
5
6
7
private static <T> loadObjects(Collection<T> list, File file, Class<T[]> clazz) {
  BufferedReader in = null;
  try {
    in = new BufferedReader(new FileReader(file));
    list.addAll(Arrays.asList(new Gson().fromJson(in, clazz)));
  }
}

네트워크

image image image

IP Address

터미널에서 ipconfig -all 명령어를 치면 아이피 주소를 알수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ipconfig

Windows IP 구성

무선 LAN 어댑터 Wi-Fi:

   //..
   물리적 주소 . . . . . . . . :
   DHCP 사용 . . . . . . . . . :
   자동 구성 사용. . . . . . . :
   링크-로컬 IPv6 주소 . . . . : fe80.....
   IPv4 주소 . . . . . . . . . : 192....
   서브넷 마스크 . . . . . . . : 255....
   임대 시작 날짜. . . . . . . :
   임대 만료 날짜. . . . . . . :
   기본 게이트웨이 . . . . . . :
   DHCP 서버 . . . . . . . . . :
   DHCPv6 IAID . . . . . . . . :
   DHCPv6 클라이언트 DUID. . . : 
   DNS 서버. . . . . . . . . . : 210.220..
                                 219.250...
   Tcpip를 통한 NetBIOS. . . . : 
  • IPv6 주소: ipv4의 주소 고갈 문제를 해결하기 위하여 등장한 프로토콜이다. 공식적으로는 사용하지 않지만 내부적으로 다 부여되어 있다. 이해하기 힘들다는 단점이 있다.
  • IPv4 주소: 여기 나오는 주소는 사설 아이피 주소로, 공유기에서 지정한 아이피주소이다. 아이피가 부족해서 그냥 공유기에서 부여한 아이피를 받는다. 공유기가 같으면 아이피가 같을 수 있다.
  • DNS 서버: 도메인 이름 입력하면 아이피 주소를 알려주거나, 아이피 주소를 알만한 서버의 아이피 주소를 알려주는 서버이다. DNS 서버 주소가 두 개가 있는 이유는 한 쪽에 문제가 생겼을 때도 다른 서버에 접속할 수 있도록 하기 위해서이다. DNS 서버가 둘다 다운되면 도메인 이름으로 서버에 접속할 수 없다.
  • 인터넷에서 통신하려면 아이피 주소를 알고 있어야 한다. naver.com은 별명일 뿐이다.

포트번호

0 ~ 1023 (well-known port)

  • 특정 프로그램이 관습적으로 사용하는 포트 번호
  • 프로그램을 작성할 때 가능한 이 포트 번호를 사용하지 말아야 한다.
  • 7(echo), 20(FTP 데이터 포트), 21(FTP 제어포트), 23(telnet), 25(SMTP), 53(DNS), 80(HTTP), 110(POP3), 143(IMAP) 등

1024 ~ 49151 (registered port)

  • 일반적인 통신 프로그램을 작성할 때 이 범위 포트 번호를 사용한다.
  • 다만 이 범위에 번호 중에서 특정 프로그램이 널리 사용하는 번호가 있다. 가능한 그 번호도 피하라!
  • 8080(proxy), 1521(Oracle), 3306(MySQL) 등

49152 ~ 65535 (dynamic port)

  • 통신을 하는 프로그램은 반드시 포트번호를 가져야 한다. 그래야 OS가 해당 프로그램을 구분할 수 있다. 따라서 클라이언트 프로그램도 포트번호를 갖는데, 프로그램에서 결정하는 것이 아니라, OS로부터 자동 발급 받는다.
  • 이 범위의 포트번호는 클라이언트가 OS로부터 자동 발급 받는 포트 번호이다.

실습

image

  • 클라이언트: 연결을 요청하는 쪽
  • 서버: 연결 요청을 받는 쪽
  • 포트번호: 한 컴퓨터에서 네트워크 연결을 기다리는 프로그램의 소켓 번호이다. 같은 아이피 어드레스를 가진 피씨에서 카카오톡, web Browser, 게임 등 여러 프로그램이 있다면 각 프로그램의 포트번호가 있어야 한다. 따라서 포트번호는 서로 달라야 한다. 데이터가 해당 포트번호로 가기 때문이다.

클라이언트 만들기

  • Socket socket = new Socket("localhost", 8888)

    • 서버에 연결 요청을 할 때 사용할 도구를 준비한다.
    • 클라이언트 측의 포트 번호는 OS가 자동으로 부여한다. 서버 측은 개발자가 명시적으로 부여해야 한다. 포트 번호는 서버와 연결될 때마다 충돌이 되지 않도록 새로 부여된다.

    IP 주소가 회사 대표번호라면, 포트번호는 내선번호라 할 수 있다.

    • 특수 IP: 127.0.0.1: 로컬 컴퓨터를 가리킨다.
    • 특수 도메인명: localhost: 127.0.0.1을 가리킨다.
  • socket.close()

    • 작업이 끝난 후에는 항상 서버와의 연결을 해제해야 한다.
    • 해제하지 않아도 서버 측에서 일정 시간이 지나면 자동으로 연결과 관련된 자원을 해제한다.
    • 그러나 가능한 명시적으로 연결을 해제하는 것이 좋다. 서버 측에서 해당 클라이언트와 연결하기 위해 사용한 자원을 빠르게 회수하여 다른 클라이언트를 위해 사용할 수 있기 때문이다.
1
2
3
4
5
6
7
8
9
public class Client0110 {

  public static void main(String[] args) throws Exception {
    Socket socket = new Socket("localhost", 8888 );
    System.out.println("서버와 연결되었음!");
    socket.close();
    System.out.println("서버와 연결을 끊었음!");
  }
}

서버 열리지 않았을 때 다음과 같은 ConnectException 예외가 발생한다.

1
Exception in thread "main" java.net.ConnectException: Connection refused: connect

서버 만들기

  • ServerSocket ss = new ServerSocket(8888)
    • 네트워크 연결을 기다리는 역할을 수행할 객체 준비
    • 현재 실행 중인 프로그램과 포트 번호가 중복되어서는 안 된다.
    • 포트번호는 한 컴퓨터에서 네트워크 연결을 기다리는 프로그램의 식별번호이다. OS는 이 번호를 가지고 데이터를 받을 프로그램을 결정한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Server0110 {
  public static void main(String[] args) throws Exception {
    // 중간에 실행을 잠깐 멈출 때 사용하기 위함.
    Scanner keyboard = new Scanner(System.in);

    System.out.println("서버 실행!");
    ServerSocket ss = new ServerSocket(8888);

    System.out.println("클라이언트 연결을 기다리는 중...");

    // 잠깐 멈추기
    keyboard.nextLine(); // 사용자가 엔터를 칠 때까지 리턴하지 않는다.

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

This post is licensed under CC BY 4.0 by the author.

코어 자바스크립트 #2.6: alert, prompt, confirm을 이용한 상호작용

코어 자바스크립트 #2.7: 형변환

Loading comments from Disqus ...