네트워크의 기초 개념, Retrofit 라이브러리를 이용한 Open API 조회, TCP 소켓 프로그래밍의 기초를 학습한다.
네트워크는 너무 방대하고 어렵기 때문에 프로그래밍 하기 어렵다고 한다.. 실제로 네트워킹 프로그래밍을 직접 하는 개발자가 많지 않으니 꼭 필요한 개념들만 알아보자.
네트워킹(Networking)
클라이언트(Client) / 서버(Server) (컴퓨터간의 관계를 역할로 구분)
인터넷 상에서 컴퓨터를 구별하는데 사용되는 고유한 값으로 인터넷에 연결이 되어있는 모든 컴퓨터는 IP 주소를 갖는다.
윈도우즈 에서는
명령 프롬프트
, MacOS 의 경우terminal
을 실행하고
"ipconfig"를 입력하면 컴퓨터의 IP 주소를 확인할 수 있다!
URL은 인터넷에 존재하는 여러 서버들이 제공하는 자원에 접근할 수 있는 주소를 표현하기 위한 것이다. (우리가 네이버, 구글을 검색할 때도 URL을 이용하여 접근을 한다)
'프로토콜://호스트명:포트번호/경로명/파일명?쿼리스트링#참조' 의 형식을 띄고 있다.
→ 프로토콜은 복수의 컴퓨터 사이에서 데이터 통신을 원활하게 하기 위해 필요한 통신 규약을 의미한다. 대표적인 예시로는 Http가 있다.
컴퓨터에서 어떤 프로세스가 통신에 사용할 통로다.
포트(port)는 보통 항구나 공항을 의미함. 컴퓨터에서도 비슷하게 포트는 외부의 다른 장비와 접촉하기 위한 플러그 역할을 한다.
포트번호는 어떤 프로그램에 접속 할 것인지를 식별하는 역할을 한다. 아무것도 입력하지 않으면 기본값은 http는 80번, https는 443 포트.
API(Application Programming Interface) 란?
응용 프로그램에서 사용할 수 있도록 운영체제나 프로그래밍 언어가 제공하는 기능을 제어 할 수 있게 해주는 인터페이스를 의미한다. client-server 관점에서 API는 요청과 응답의 형식에 대한 약속이다.
Retrofit을 사용하기 위해서는 아래의 3가지 라이브러리가 필요하다. build.gradle
파일의 dependencies{}
코드블럭에 붙여넣기 해야함.
다른 곳에 구현된 코드를 라이브러리(library)라고 한다. 라이브러리는 빌드도구인 Gradle를 통해서 import 된다. 즉, 우리가 Retrofit 라이브러리를 사용한다고 했으니 필요한 라이브러리를 import하는 것이다.
build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squ areup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.5'
단축키(Mac)
Command + Shift + O
단축키(윈도우)
Ctrl + Shift + O
가져올 데이터는 reqres.in 사이트에 위치한 JSON 데이터다.
오른쪽창에서 JSON 데이터가 어떻게 들어가 있는지 확인할 수 있다. API를 호출하려면, api가 위치한 페이지 URL을 확인해야한다.
맨 위에 Request라고 되어있는 부분을 보면 우리가 찾을 데이터는 /api/users?page=2에 있다는 것을 알 수 있다. 전체 URL은 https://reqres.in/api/users?page=2
이다. 이 주소는 다음과 같은 의미를 가진다
GET
은 HTTP METHOD중 하나로, 해당하는 데이터를 가져온다.reqres.in
이다./api/users
다.page=2
이다. API의 의도는 해당 API의 2페이지를 줘!
라는 의미이다.쿼리스트링(Query String)
→ url 주소에 ? 는 쿼리스트링을 작성하겠다는 신호다. 쿼리스트링은 사용자가 API에 조회할 때 조건으로 줄 데이터를 입력하는 방법중 하나이다.
RetrofitService.java
public interface RetrofitService {
@GET("/api/users/")
Call<Object> retrofitTest(@Query("page") int page);
}
// @Query는 Retrofit 라이브러리를 이용할 때 쿼리스트링을 입력하는 방법이다.
// 이렇게 파라미터 변수로 작성해놓으면 함수를 호출할 때 파라미터를 바꿔가며 원하는 페이지를 조회할 수 있다.
RetrofitClient.java
public class RetrofitClient {
private static final String BASE_URL = "https://reqres.in/";
// BASE_URL에는 변하지 않는 URL주소를 입력해 주면 된다. 데이터의 위치에 따라 변하지 않도록 static final 로 지정해주자.
public static RetrofitService getApi() {
return getInstance().create(RetrofitService.class);
}// getInstance 메소드를 통해 인스턴스를 반환한다.
private static Retrofit getInstance() {
Gson gson = new GsonBuilder().setLenient().create();// 통신을 할 때 JSON 사용 및 해당 객체로의 파싱을 위해
// 생성한다.
return new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson)).build();
// 서버에서는 JSON으로 응답하므로 우리는 build.gradle에 설정한 gson을 이용한다.
}
}
Main.java
public class Main {
public static void main(String[] args) {
Call<Object> retrofitTest = RetrofitClient.getApi().retrofitTest(2);
// 2페이지를 확인할 거니까 2를 삽입
// Call은 retrofit라이브러리의 인터페이스이며 서버에서 응답하는 값을 담는 역할을 한다.
try {
System.out.println(retrofitTest.execute().body());// 서버에서 받은 데이터를 확인해보자.
} catch (IOException e) {
e.printStackTrace();
}
}
}
아래 retrofit 라이브러리의 공식문서에서 Retrofit 라이브리의 더 다양한 활용법을 알 수 있다.
https://square.github.io/retrofit/
TCP/IP 프로토콜은 시스템간의 통신을 위한 표준 프로토콜로, 프로토콜의 집합을 의미한다.
TCP와 UDP 역시 이 TCP/IP 프로토콜에 포함되어 있으며 OSI 7계층의 전송계층에 해당한다.
전화와 유사하다고 생각하면 된다. 데이터를 전송하기 전에 상대 컴퓨터와 연결을 한 후, 데이터를 전송한다. 이후 결과를 확인하며 만약 수신이 제대로 되지 않았을 경우 재전송 실시함.
UDP는 상대편과 연결하지 않고 데이터를 전송한다. 또한 데이터가 제대로 수신되었는지를 확인하지 않기 때문에 신뢰성이 형성되지 않는다.
네트웨크 학습자료 : https://opentutorials.org/course/1688/9483
int read()
: 스트림으로부터 바이트를 읽어서 숫자로 반환한다.int read(byte[])
: byte배열의 크기만큼 정보를 읽고 몇 byte를 읽었는지 반환한다. (데이터는 임시보관)void close()
: 입력 스트림을 닫는다.void write(int value)
: 스트림으로부터 1바이트를 내보낸다.void write(byte[] b)
: 여러 바이트를 보낼 때는 스트림으로부터 주어진 바이트 배열 b의 모든 바이트를 보낸다.Socket : 클라이언트와 서버가 TCP연결이 성립된 후 만들어지는 연결통로다. InputStream
과 OutputStream
을 가지고 있으며 이 두 스트림을 통해 서버와 클라이언트 사이에 데이터를 읽고 쓸 수 있다.
ServerSocket : 서버에서 클라이언트로부터의 연결 요청을 관리하는 소켓이다.
기본적으로 리슨하는 포트를 들고 있는 객체라고 보면 됨. 리슨 상태에서 연결 요청이 들어오게 되면 (Accept라고 함), 커넥션이 생성되고, 그 이후에 Socket을 생성하여 Socket간의 통신이 이루어지는 역할을 수행한다.
TcpServer.java
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalTime;
public class TcpServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8888);
System.out.println("[" + LocalTime.now() + "]" + "서버가 준비되었습니다.");
} catch (IOException e) {
System.out.println(e.getLocalizedMessage());
e.printStackTrace();
}
while (true) {
try {
System.out.println("[" + LocalTime.now() + "]" + " 연결 요청을 기다립니다.");
Socket socket = serverSocket.accept();
System.out.println("[" + LocalTime.now() + "]" + " 연결이 되었습니다.");
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream));
String inputString = bufferedReader.readLine();
System.out.println(
"[" + LocalTime.now() + "]" + "message from client: " + inputString);
OutputStream outputStream = socket.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeUTF("World");
System.out.println("[" + LocalTime.now() + "]" + " 데이터를 전송했습니다.");
dataOutputStream.close();
socket.close();
} catch (IOException e) {
System.out.println(e.getLocalizedMessage());
e.printStackTrace();
}
}
}
}
TcpClient.java
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
public class TcpClient {
public static void main(String[] args) {
try {
String serverIp = "localhost";
System.out.println("[" + LocalTime.now() + "]" + "서버에 연결합니다.");
System.out.println("[" + LocalTime.now() + "]" + "서버 IP: " + serverIp);
Socket socket = new Socket(serverIp, 8888);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello\n".getBytes(StandardCharsets.UTF_8));
outputStream.flush();
InputStream inputStream = socket.getInputStream();
DataInputStream dataInputStream = new DataInputStream(inputStream);
System.out.println("[" + LocalTime.now() + "]" + "message from server: "
+ dataInputStream.readUTF());
dataInputStream.close();
socket.close();
System.out.println("[" + LocalTime.now() + "]" + "연결이 종료되었습니다.");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}