실습 - JSON 형식
git/eomcs-java-project-2020/mini-pms-32
다양한 언어와 플랫폼에서 모두 호환되는 데이터 형식은 xml과 JSON 이 있다. 다만 xml는 메타데이터의 비중이 크기 때문에 메모리 낭비가 심하다. 따라서 JSON 형식에 따라 데이터 파일을 만들어보려고 한다.
JSON이란?
- 속성-값 또는 키-값 으로 된 데이터 객체를 텍스트로 표현하는 개방형 표준 데이터 포맷이다. 예를 들어 다음과 같은 형식을 취한다.
{속성:값, 속성:값, ...}
{"no":1,"name":"1","email":"1","password":"1","photo":"1","tel":"1"}
- 텍스트 형식이기 때문에 프로그래밍 언어나 운영체제에 영향을 받지 않는다.
- 바이너리 방식에 비해 데이터 커지는 문제가 있지만 모든 프로그래밍 언어에서 다룰 수 있다는 장점이 있다.
- 인터넷 상에서 애플리케이션 간에 데이터를 주고 받을 때 주로 사용한다.
- 특히 이기종 플랫폼(OS, 프로그래밍 언어 등) 간에 데이터를 교환할 때 유용하다.
- JSON 공식 홈인 https://www.json.org 사이트에 자세한 내용이 있다.
JSON 라이브러리이란?
- JSON 데이터 포맷을 다루는 라이브러리다.
- JSON 홈페이지에 다양한 프로그래밍 언어에서 사용할 수 있는 라이브러리를 소개한다. (홈페이지 가장 밑에 소개되어있다)
GSON이란?
- 구글에서 제공하는 JSON 자바 라이브러리다.
- 자바 객체를 JSON 형식의 텍스트로 변환하는 기능을 제공한다.
- JSON 형식의 텍스트를 자바 객체로 변환하는 기능을 제공한다.
- 자세한 내용과 임포트 방법은 여기를 참고할 수 있다.
search.maven.org/artifact/com.google.code.gson/gson/2.8.6/jar
훈련 목표
- GSON 자바 라이브러리를 프로젝트에 임포트한다.
- save/load 메서드에서 Gson 객체를 생성하여 도메인 객체들의 Collection <-> Json 포맷의 String 변환하는데 사용한다.
- 각 파일을 .csv 파일이 아니라 .json 파일로 바꿔주고 수정한 메서드를 호출한다.
1단계 : build.gradle 파일에서 다음과 같이 한 줄을 추가한다.
implementation 'com.google.code.gson:gson:2.8.6'
dependencies {
// This dependency is used by the application.
implementation 'com.google.guava:guava:29.0-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.13'
implementation 'com.google.code.gson:gson:2.8.6'
}
2단계 : 터미널에서 해당 프로젝트 파일로 가서 gradle eclipse를 실행한 후, 이클립스에서 프로젝트를 refresh 하면, JRE System. Library에 gson 라이브러리가 추가된 것을 확인할 수 있다.
3단계 : gson 라이브러리 임포트를 완료헀다면, 기존에 csv 포맷의 파일에 직접 FileWriter를 호출하여 String을 출력해주던 saveObjects를 다음과 같이 수정한다.
- Gson 객체를 생성한다.
- 도메인 객체가 들어있는 리스트를 파라미터로 주어, toJson을 호출하면, 리스트 안의 객체들을 모두 json 포맷의 문자열로 변환한다.
- 변환된 문자열을 BufferedWriter를 통해 출력한다.
- 버퍼에 남은 데이터까지 모두 출력되도록 flush를 호출한다.
private static <T extends CsvObject> void saveObjects(Collection<T> list, File file) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(file));
Gson gson = new Gson();
String Json = gson.toJson(list);
out.write(Json);
out.flush();
System.out.printf("총 %d 개의 객체를 '%s' 파일에 저장했습니다.\n", list.size(), file.getName());
} catch (IOException e) {
System.out.printf("객체를 '%s' 파일에 쓰기 중 오류 발생! - %s\n", file.getName(), e.getMessage());
} finally {
try {
out.close();
} catch (IOException e) {
}
}
}
4단계 : 이렇게 메서드를 바꾸고 나면 App 클래스의 스태틱 필드로 지정되어있던 파일 객체들의 경로를 .csv 파일에서 .json으로 변경한다.
static File boardFile = new File("./board.json"); // 게시글을 저장할 파일 정보
static File memberFile = new File("./member.json"); // 회원을 저장할 파일 정보
static File projectFile = new File("./project.json"); // 프로젝트를 저장할 파일 정보
static File taskFile = new File("./task.json"); // 작업을 저장할 파일 정보
5단계 : loadObjects도 다음과 같이 수정한다.
- StringBuilder 객체를 생성하여 json 파일에 저장된 모든 데이터를 입력받아 저장한다.
- Gson 객체를 생성한다.
- 데이터를 저장한 StringBuilder 객체를 String 으로 변환해준 것과 변환받고자하는 도메인 객체의 배열에 대한 클래스 정보를 파라미터로 주어, fromJson을 호출하면 각 객체들의 배열을 만들어 리턴한다.
- 배열에 있는 도메인 객체들을 하나씩 꺼내어 객체를 담아야할 Collection에 담아준다.
private static <T> void loadObjects(Collection<T> list, File file, Class<T[]> clazz) {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
StringBuilder strBuilder = new StringBuilder();
int b = 0;
while ((b = in.read()) != -1) {
strBuilder.append((char) b);
}
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) {
}
}
}
loadObejcts를 다음과 같이 수정할 수도 있다.
- Gson 객체를 생성한다.
- 파일 입력 도구와 입력받고자하는 도메인 객체 배열의 클래스 정보를 파라미터로 주고, fromJson을 호출하면 도메인 객체들의 배열을 리턴한다.
- 리턴받은 배열에서 객체를 하나씩 꺼내서 list에 추가한다.
private static <T> void loadObjects(Collection<T> list, File file, Class<T[]> clazz) {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
Gson gson = new Gson();
T[] arr = gson.fromJson(in, 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) {
}
}
}
다음과 같이 한줄로 줄이는 방법도 있다.
- Gson 객체를 생성하여 위의 방법처럼 fromJson을 호출하여 배열을 리턴받는다.
- Arrays 클래스의 asList를 호출하여 T[]배열을 List<T> 객체로 변환한다.
- addAll을 통해 List<T>에 있는 모든 객체를 한번에 list에 추가한다.
private static <T> void 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)));
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) {
}
}
}
6단계 : save / load 메서드를 위와 같이 수정해준 후 수정 사항에 알맞게 메서드를 호출해준다.
특히 loadObjects 의 세번째 파라미터로 도메인 객체의 클래스 정보를 담은 필드를 넘겨주어야 한다.
public class App {
public static void main(String[] args) {
loadObjects(boardList, boardFile, Board[].class);
loadObjects(memberList, memberFile, Member[].class);
loadObjects(projectList, projectFile, Project[].class);
loadObjects(taskList, taskFile, Task[].class);
.
.
.
saveObjects(boardList, boardFile);
saveObjects(memberList, memberFile);
saveObjects(projectList, projectFile);
saveObjects(taskList, taskFile);
}
}
네트워크
git/eomcs-java-basic/src/main com.eomcs.net.ex02.Client0110
git/eomcs-java-basic/src/main com.eomcs.net.ex02.Server0110
클라이언트(client)와 서버(server)
-
클라이언트 (client) : 연결을 요청하는 프로그램
-
서버 (server) : 연결 요청을 받는 프로그램
-
소켓 (Socket) : 네트워크를 경유하는 프로세스 간 통신의 접속점.
소켓을 통해 클라이언트와 서버 프로그램 사이에 데이터를 송수신할 수 있다.
예를 들어 택배를 보낸다고 했을 때, 상자에 물건을 넣고 인적사항과 주소를 적어야 하는 역할이 소켓의 역할이라 할수 있다. -
IP 주소(Internet Protocol address) : 컴퓨터 네트워크에서 장치들이 서로를 인식하고 통신을 하기 위해서 사용하는 특수한 번호
-
포트 번호 (Port Number) : Ip주소를 통해 데이터를 전송할 상대 컴퓨터까지 도달한 후, 데이터를 받을 프로세스를 구별하기 위한 식별자.
클라이언트와 서버가 소켓을 통해 연결되는 과정
-
서버 소켓과 클라이언트 소켓을 만든다.(socket())
-
서버 소켓은 로컬 IP를 가지고 포트를 열고(bind()) 클라이언트 연결을 기다린다.(listen())
-
클라이언트 소켓은 IP 주소를 이용해 목적지 호스트를 찾아내고 포트를 이용해 통신 접속점을 찾아내서 연결을 만든다.(connect())
-
서버 소켓이 연결을 수락하면(accept()), 포트를 이용해 데이터를 주고 받는다.
(send(), recv())
출처 : https://qlyh8.tistory.com/86
클라이언트
- 클라이언트가 서버에 연결 요청을 하기 위해서는 Socket 객체가 필요하다.
- Socket 객체를 생성하는 생성자에는 로컬 컴퓨터의 IP주소 또는 도메인 이름이 필요하며, 또한 서버가 갖는 포트번호가 필요하다. 서버와의 연결이 이루어져야만 Socket 객체가 리턴된다.
- 연결이 실패하면 에러를 띄운다.
- 서버와의 연결을 끊으려면 close()를 호출해주면 된다. 작업이 끝난 후에는 서버측에서 해당 클라이언트와 연결하는 동안 사용한 자원을 다른 클라이언트에 빠르게 사용할 수 있도록 바로바로 close()를 호출해주는 것이 좋다.
import java.net.Socket;
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("서버와 연결을 끊었음!");
}
}
서버
- 네트워크 연결을 기다리는 역할을 수행할 ServerSocket 객체를 준비한다.
- ServerSocket 객체의 생성자를 호출할 때에는 파라미터로 이 프로그램의 포트번호를 결정하여 넣어주면 OS는 이 번호를 갖고 데이터를 받을 프로그램을 결정할 것이다.
- 포트 번호를 지정할 때에는 다른 프로그램에서 사용하고 있는 포트 번호를 사용하면 안되는데, 자주 사용되는 포트 번호가 있으므로 그런 포트 번호는 피해서 지정해야 한다.
- 실행에 시동을 걸 수 있도록 Scanner.nextLine()를 호출하여 실행 중에 사용자가 엔터를 치기 전까지 다음 코드를 실행하지 못하도록 한다.
- close()를 호출하여 서버를 종료시킨다.
포트 번호(0~65535, 2바이트)
- 0~1023(well-known port)
특정 프로그램이 관습적으로 사용하는 포트 번호로 가능한 이 포트 번호를 피해야 한다.
ex - 7(echo), 20(FTP 데이터 포트), 21(FTP 제어포트), 23(telnet), 25(SMTP), 53(DNS), 80(HTTP), 110(POP3), 143(IMAP) 등 - 1024 ~ 49151 (registered port)
일반적인 통신 프로그램을 작성할 때 이 범위 포트 번호를 사용한다. 다만 이 범위에서도 특정 프로그램이 널리 사용하는 번호가 있다.
ex - 8080(proxy), 1521(Oracle), 3306(MySQL) 등의 번호를 피해야 한다. - 49152 ~ 65535 (dynamic port)
이 범위는 클라이언트가 OS로부터 자동 발급 받는 포트 번호이다.
import java.net.ServerSocket;
import java.util.Scanner;
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();
}
}
'국비 교육' 카테고리의 다른 글
2020.9.8일자 수업 : 로컬 클래스 (0) | 2020.10.01 |
---|---|
2020.9.24 일자 수업 : character stream class (0) | 2020.09.25 |
2020.9.23 일자 수업 : 파일 입출력 (0) | 2020.09.23 |