mini pms v.32: JSON 형식으로 객체 읽고 쓰기
데이터 교환: XML과 JSON
데이터를 공급하는 쪽과 소비하는 쪽의 언어나 운영체제가 일치하지 않을 수 있다. 텍스트 형식은 운영체제나 프로그래밍 언어에 종속되지 않기 때문에 이기종 간 데이터를 주고받을 때 바이너리보다는 텍스트 형식을 사용한다. 또한 데이터를 교환할 때 데이터의 종류를 파악할 수 있도록 데이터 구조가 짜여 있어야 한다. (예: 날짜 데이터인지 온도 데이터인지 구분) 이때 데이터 교환을 하기 위해서 만들어진 언어가 XML이다.
1
2
| <습도>80</습도>
<온도>27</온도>
|
그러나 XML은 치명적인 단점을 가지고 있는데, 메타 데이터의 크기가 너무 크다는 것이다. 이러한 단점을 상쇄하면서도 데이터 교환에 적합한 포맷이 바로 JSON이다. JSON은 JavaScript Object Notation
의 약자로 은 자바스크립트의 객체 표기법을 빌려왔다.
지금은 애플리케이션과 애플리케이션 간 데이터 교환할 때 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 라이브러리
GSON
- 구글에서 제공하는 JSON 자바 라이브러리다.
- 자바 객체를 JSON 형식의 텍스트로 변환하는 기능을 제공한다.
- JSON 형식의 텍스트를 자바 객체로 변환하는 기능을 제공한다.
DSL: Domain Specific Language
- 빌드: 애플리케이션을 컴파일하고 필요하다면 압축해서 고객에게 배포할 파일을 만드는 과정
- 빌드 스크립트 파일: 어떻게 자바 어떤 버전에 호환되게 컴파일할 건지, 외부 라이브러리는 어떤 걸 쓰고 어떤 서버에 있는지 등 빌드 전 과정에 관련되어 과정을 기술하는 빌드 명령서
- DSL: 빌드 명령서를 작성하는 언어 (ex: Kotlin, Gradle, Groovy)
실습
1단계: 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)));
}
}
|
네트워크
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로부터 자동 발급 받는 포트 번호이다.
실습
- 클라이언트: 연결을 요청하는 쪽
- 서버: 연결 요청을 받는 쪽
- 포트번호: 한 컴퓨터에서 네트워크 연결을 기다리는 프로그램의 소켓 번호이다. 같은 아이피 어드레스를 가진 피씨에서 카카오톡, web Browser, 게임 등 여러 프로그램이 있다면 각 프로그램의 포트번호가 있어야 한다. 따라서 포트번호는 서로 달라야 한다. 데이터가 해당 포트번호로 가기 때문이다.
클라이언트 만들기
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();
}
}
|