[JAVA] 소켓 프로그래밍 3번

2024. 7. 8. 12:28·자바

Java 소켓 프로그래밍: 비동기 소켓 채널

소개

이전 포스트에서는 멀티스레딩을 이용한 서버 구현을 다루었습니다. 이번 포스트에서는 비동기 소켓 채널(Asynchronous Socket Channel)을 이용하여 더 효율적이고 고성능의 네트워크 애플리케이션을 작성하는 방법을 설명하겠습니다.

비동기 소켓 채널이란?

비동기 소켓 채널은 Java NIO (New Input/Output) 라이브러리의 일부로, 비동기적으로 데이터를 읽고 쓸 수 있게 해줍니다. 이를 통해 블로킹 I/O의 한계를 극복하고, 고성능 네트워크 애플리케이션을 작성할 수 있습니다.

비동기 소켓 채널의 동작 원리

비동기 소켓 채널은 작업이 완료될 때 호출되는 콜백 메서드를 사용합니다. 이를 통해 I/O 작업이 완료될 때까지 스레드가 블로킹되지 않고 다른 작업을 수행할 수 있습니다.

비동기 에코 서버 예제

서버 코드

먼저 비동기 소켓 채널을 이용한 서버 코드를 작성해보겠습니다.

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AsyncEchoServer {
    public static void main(String[] args) {
        int port = 12345;
        try {
            // 비동기 서버 소켓 채널 생성
            AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(port));

            System.out.println("Async Echo server is listening on port " + port);

            // 클라이언트의 연결 수락
            serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
                @Override
                public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
                    // 다음 클라이언트의 연결 수락
                    serverSocketChannel.accept(null, this);

                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 클라이언트로부터 데이터 읽기
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            // 클라이언트로 데이터 쓰기
                            socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer buffer) {
                                    if (buffer.hasRemaining()) {
                                        socketChannel.write(buffer, buffer, this);
                                    } else {
                                        buffer.clear();
                                        socketChannel.read(buffer, buffer, this);
                                    }
                                }

                                @Override
                                public void failed(Throwable exc, ByteBuffer buffer) {
                                    try {
                                        socketChannel.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer buffer) {
                            try {
                                socketChannel.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }

                @Override
                public void failed(Throwable exc, Void attachment) {
                    System.out.println("Failed to accept a connection.");
                    exc.printStackTrace();
                }
            });

            // 서버가 종료되지 않도록 대기
            Thread.currentThread().join();

        } catch (IOException | InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

클라이언트 코드

클라이언트 코드는 이전 예제와 동일하게 사용할 수 있습니다. 비동기 서버와 통신할 때 클라이언트 코드에 변경 사항은 없습니다.

import java.io.*;
import java.net.*;

public class EchoClient {
    public static void main(String[] args) {
        String hostname = "localhost";
        int port = 12345;

        try (Socket socket = new Socket(hostname, port)) {
            OutputStream output = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);

            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));

            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            String text;

            System.out.println("Connected to the echo server. Type your messages:");
            while ((text = consoleReader.readLine()) != null) {
                writer.println(text);
                String response = reader.readLine();
                System.out.println(response);
            }
        } catch (UnknownHostException ex) {
            System.out.println("Server not found: " + ex.getMessage());
        } catch (IOException ex) {
            System.out.println("I/O error: " + ex.getMessage());
        }
    }
}

코드 설명

AsyncEchoServer

  • AsynchronousServerSocketChannel: 비동기 서버 소켓 채널을 생성하고 특정 포트에 바인딩합니다.
  • CompletionHandler: 비동기 작업의 완료를 처리하는 핸들러입니다. 클라이언트 연결 수락, 데이터 읽기 및 쓰기에 사용됩니다.
  • ByteBuffer: 데이터를 읽고 쓰기 위한 버퍼입니다.
  • accept(): 클라이언트의 연결을 비동기적으로 수락합니다. 완료되면 completed 메서드가 호출됩니다.
  • read(): 클라이언트로부터 데이터를 비동기적으로 읽습니다. 완료되면 completed 메서드가 호출됩니다.
  • write(): 클라이언트에게 데이터를 비동기적으로 씁니다. 완료되면 completed 메서드가 호출됩니다.

비동기 서버의 주요 동작 과정

  1. 서버 소켓 채널 생성 및 바인딩: AsynchronousServerSocketChannel을 생성하고 포트에 바인딩합니다.
  2. 클라이언트 연결 수락: accept() 메서드를 호출하여 클라이언트의 연결 요청을 비동기적으로 수락합니다. 수락이 완료되면 CompletionHandler의 completed 메서드가 호출됩니다.
  3. 데이터 읽기 및 쓰기: 클라이언트와의 데이터 송수신을 read() 및 write() 메서드를 사용하여 비동기적으로 처리합니다. 각 작업이 완료될 때마다 CompletionHandler의 completed 메서드가 호출됩니다.
  4. 연결 유지 및 종료: 데이터 송수신이 완료되면 연결을 유지하거나 종료할 수 있습니다. 예제에서는 데이터를 계속해서 주고받는 형태로 구현되었습니다.

결과

비동기 소켓 채널을 이용한 에코 서버는 여러 클라이언트의 요청을 비동기적으로 처리할 수 있어 높은 성능을 제공합니다. 각 클라이언트의 요청은 별도의 스레드 없이 콜백 메서드로 처리되므로, 스레드 관리의 부담이 줄어들고 더 많은 클라이언트를 처리할 수 있습니다.

결론

이번 포스트에서는 Java NIO의 비동기 소켓 채널을 이용한 고성능 네트워크 애플리케이션을 구현하는 방법을 배웠습니다. 이를 통해 높은 성능과 확장성을 갖춘 네트워크 애플리케이션을 작성할 수 있습니다. 이번 소켓 프로그래밍 시리즈를 통해 다양한 방법으로 네트워크 애플리케이션을 구현하는 방법을 배웠기를 바랍니다.

'자바' 카테고리의 다른 글

[JAVA] 자바 메서드 심화: 오버로딩과 오버라이딩  (0) 2024.07.19
[JAVA] 자바 함수(메서드)의 기초: 선언부터 사용까지  (0) 2024.07.19
[API] Python API와 Java 클라이언트를 이용한 CPK 분석 및 히스토그램 생성  (0) 2024.07.19
[JAVA] 소켓 프로그래밍 2번  (0) 2024.07.08
[JAVA] 소켓 프로그래밍 1번  (0) 2024.07.08
'자바' 카테고리의 다른 글
  • [JAVA] 자바 함수(메서드)의 기초: 선언부터 사용까지
  • [API] Python API와 Java 클라이언트를 이용한 CPK 분석 및 히스토그램 생성
  • [JAVA] 소켓 프로그래밍 2번
  • [JAVA] 소켓 프로그래밍 1번
뚤떡이
뚤떡이
프로그래밍을 알아가며 저와 함께 성장하는 블로그 입니다
  • 뚤떡이
    뚤떡이의 발개벗긴개발
    뚤떡이
  • 전체
    오늘
    어제
    • 분류 전체보기 (73)
      • Docker (2)
      • 자바 (29)
        • start (10)
        • calcu (4)
        • process (7)
      • 플러터 (1)
      • 알고리즘 (12)
        • 개념 (2)
        • 구현 (5)
        • 백준 (0)
        • 프로그래머스 (5)
      • 이클립스 (4)
        • 초기설정 (4)
      • SQL (5)
      • IT 잡동사니 (10)
      • 개발 관련 (10)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    크롬 원격 데스크톱
    GLPI
    요구사항 정의서
    시놀로지
    #번역
    개발일지 #프로젝트후기 #gpt4 #번역 #자동화 #python #electron
    #electron
    #python
    #프로젝트
    synology
    윈도우 크롬 원격
    #apachesuperset #시놀로지 #db2 #데이터시각화 #기술블로그
    스팸메일 #메일보안 #gophish #it보안 #오픈소스
    크롬 원격
    #python #개발일지 #번역 #메모리최적화 #websocket #redis #비동기프로그래밍
    #개발일지
    #자동화
    IT자산관리
    MAC 크롬 원격
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
뚤떡이
[JAVA] 소켓 프로그래밍 3번
상단으로

티스토리툴바