[Java] Java 기초 - Networking

Hyunjun Kim·2025년 4월 13일
0

Data_Engineering

목록 보기
27/153

16. 네트워킹(Networking)

네트워크의 기초 개념, Retrofit 라이브러리를 이용한 Open API 조회, TCP 소켓 프로그래밍의 기초를 학습한다.

네트워크는 너무 방대하고 어렵기 때문에 프로그래밍 하기 어렵다고 한다.. 실제로 네트워킹 프로그래밍을 직접 하는 개발자가 많지 않으니 꼭 필요한 개념들만 알아보자.

16.1 네트워크 기본 개념

네트워킹(Networking)

  • 두 대이상의 컴퓨터를 케이블 또는 인터넷으로 연결하여 네트워크를 구성하는 것을 말한다.

16.1.1 클라이언트 서버

클라이언트(Client) / 서버(Server) (컴퓨터간의 관계를 역할로 구분)

  • 서버 : 서비스를 제공하는 컴퓨터, 요청에 응답하는 컴퓨터
  • 클라이언트 : 서비스를 사용하게 되는 컴퓨터, 요청을 하는 컴퓨터

16.1.2 IP 주소

인터넷 상에서 컴퓨터를 구별하는데 사용되는 고유한 값으로 인터넷에 연결이 되어있는 모든 컴퓨터는 IP 주소를 갖는다.

윈도우즈 에서는 명령 프롬프트, MacOS 의 경우 terminal을 실행하고
"ipconfig"를 입력하면 컴퓨터의 IP 주소를 확인할 수 있다!

16.1.3 URL(Uniform Resource Locator)

URL은 인터넷에 존재하는 여러 서버들이 제공하는 자원에 접근할 수 있는 주소를 표현하기 위한 것이다. (우리가 네이버, 구글을 검색할 때도 URL을 이용하여 접근을 한다)

1) URL의 형식

'프로토콜://호스트명:포트번호/경로명/파일명?쿼리스트링#참조' 의 형식을 띄고 있다.
→ 프로토콜은 복수의 컴퓨터 사이에서 데이터 통신을 원활하게 하기 위해 필요한 통신 규약을 의미한다. 대표적인 예시로는 Http가 있다.

2) 포트번호

컴퓨터에서 어떤 프로세스가 통신에 사용할 통로다.
포트(port)는 보통 항구나 공항을 의미함. 컴퓨터에서도 비슷하게 포트는 외부의 다른 장비와 접촉하기 위한 플러그 역할을 한다.
포트번호는 어떤 프로그램에 접속 할 것인지를 식별하는 역할을 한다. 아무것도 입력하지 않으면 기본값은 http는 80번, https는 443 포트.

API(Application Programming Interface) 란?
응용 프로그램에서 사용할 수 있도록 운영체제나 프로그래밍 언어가 제공하는 기능을 제어 할 수 있게 해주는 인터페이스를 의미한다. client-server 관점에서 API는 요청과 응답의 형식에 대한 약속이다.

16.2 Retrofit 라이브러리를 활용하여 API 호출하기

16.2.1 Retrofit를 사용하는 이유

  • Retrofit을 사용하지 않고 원시적인 방법으로 구현을 하려면, 소켓 관리, JSON 파싱, 캐싱 등 구현해야할 부분이 너무 많다.
  • Retrofit 라이브러리를 사용하게 될 경우 복잡한 구현들을 감소시켜준다.
  • 각종 에러처리를 쉽게 해결할 수 있으며 원시적인 방법에 비해 가독성도 훌륭하다.
  • 실제로 프로젝트나 프로그램을 만들 때 공공 데이터 API를 많이 활용하곤 한다. 그래서 이번에는 Retrofit 라이브러리를 활용해서 JSON으로 구조화된 API를 호출하는 실습을 해보자.

16.2.2 Retrofit 라이브러리 실습 준비 - 라이브러리

Retrofit을 사용하기 위해서는 아래의 3가지 라이브러리가 필요하다. build.gradle 파일의 dependencies{} 코드블럭에 붙여넣기 해야함.

다른 곳에 구현된 코드를 라이브러리(library)라고 한다. 라이브러리는 빌드도구인 Gradle를 통해서 import 된다. 즉, 우리가 Retrofit 라이브러리를 사용한다고 했으니 필요한 라이브러리를 import하는 것이다.

1) 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'

2) 새로운 코드를 적용

단축키(Mac)
Command + Shift + O
단축키(윈도우)
Ctrl + Shift + O

  • 단축키 혹은 오른쪽의 이모티콘(Load Gradle Changes)를 눌러서 적용시킬 수 있음. IDE가 해당 라이브러리를 다운로드 받고 프로젝트에 곧 적용을 시켜준다.

16.2.3 Retrofit 라이브러리 실습 준비 - API

가져올 데이터는 reqres.in 사이트에 위치한 JSON 데이터다.

  • LIST USERS로 된 데이터를 사용한다

오른쪽창에서 JSON 데이터가 어떻게 들어가 있는지 확인할 수 있다. API를 호출하려면, api가 위치한 페이지 URL을 확인해야한다.

맨 위에 Request라고 되어있는 부분을 보면 우리가 찾을 데이터는 /api/users?page=2에 있다는 것을 알 수 있다. 전체 URL은 https://reqres.in/api/users?page=2 이다. 이 주소는 다음과 같은 의미를 가진다

  • 통신 프로토콜은 https 를 사용한다.
  • GET 은 HTTP METHOD중 하나로, 해당하는 데이터를 가져온다.
  • 요청을 하는 도메인(홈페이지 또는 서비스의 주소)은 reqres.in 이다.
  • 요청을 하는 API의 위치는 /api/users 다.
  • 요청을 하는 API에 전달할 데이터는 page=2 이다. API의 의도는 해당 API의 2페이지를 줘! 라는 의미이다.

쿼리스트링(Query String)
→ url 주소에 ? 는 쿼리스트링을 작성하겠다는 신호다. 쿼리스트링은 사용자가 API에 조회할 때 조건으로 줄 데이터를 입력하는 방법중 하나이다.

16.2.4 Retrofit 라이브러리 실습

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/



16.3 TCP 소켓 프로그래밍

16.3.1 TCP와 UDP 개념

1) TCP/IP 프로토콜

TCP/IP 프로토콜은 시스템간의 통신을 위한 표준 프로토콜로, 프로토콜의 집합을 의미한다.
TCP와 UDP 역시 이 TCP/IP 프로토콜에 포함되어 있으며 OSI 7계층의 전송계층에 해당한다.

2) TCP

전화와 유사하다고 생각하면 된다. 데이터를 전송하기 전에 상대 컴퓨터와 연결을 한 후, 데이터를 전송한다. 이후 결과를 확인하며 만약 수신이 제대로 되지 않았을 경우 재전송 실시함.

3) UDP

UDP는 상대편과 연결하지 않고 데이터를 전송한다. 또한 데이터가 제대로 수신되었는지를 확인하지 않기 때문에 신뢰성이 형성되지 않는다.

네트웨크 학습자료 : https://opentutorials.org/course/1688/9483

16.3.2 Java의 Stream 기반 입출력

1) InputStream / OutputStream

  • 바이트 기반의 스트림으로써 최상위 클래스이며 추상 클래스다.
  • 바이트 단위 입출력 스트림을 통해서 그림, 문자 등 다양한 종류의 데이터를 전달할 수 있다.

2) InputStream 메소드

  • int read() : 스트림으로부터 바이트를 읽어서 숫자로 반환한다.
  • int read(byte[]) : byte배열의 크기만큼 정보를 읽고 몇 byte를 읽었는지 반환한다. (데이터는 임시보관)
  • void close() : 입력 스트림을 닫는다.
    (일반적으로 수행이 끝나면 닫아야 한다.)

3) OutputStream 메소드

  • void write(int value) : 스트림으로부터 1바이트를 내보낸다.
  • void write(byte[] b) : 여러 바이트를 보낼 때는 스트림으로부터 주어진 바이트 배열 b의 모든 바이트를 보낸다.

16.3.3 자바 TCP 소켓 프로그래밍

1) Socket, ServerSocket

Socket : 클라이언트와 서버가 TCP연결이 성립된 후 만들어지는 연결통로다. InputStreamOutputStream을 가지고 있으며 이 두 스트림을 통해 서버와 클라이언트 사이에 데이터를 읽고 쓸 수 있다.

ServerSocket : 서버에서 클라이언트로부터의 연결 요청을 관리하는 소켓이다.
기본적으로 리슨하는 포트를 들고 있는 객체라고 보면 됨. 리슨 상태에서 연결 요청이 들어오게 되면 (Accept라고 함), 커넥션이 생성되고, 그 이후에 Socket을 생성하여 Socket간의 통신이 이루어지는 역할을 수행한다.

2) 실제 TCP 통신 구조

  • 서버: ServerSocket 생성 후 대기 → Socket 반환
  • 클라이언트: Socket 생성 → 서버와 연결 시도

3) 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);
        }
    }
}
profile
Data Analytics Engineer 가 되

0개의 댓글