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
메서드가 호출됩니다.
비동기 서버의 주요 동작 과정
- 서버 소켓 채널 생성 및 바인딩:
AsynchronousServerSocketChannel
을 생성하고 포트에 바인딩합니다. - 클라이언트 연결 수락:
accept()
메서드를 호출하여 클라이언트의 연결 요청을 비동기적으로 수락합니다. 수락이 완료되면CompletionHandler
의completed
메서드가 호출됩니다. - 데이터 읽기 및 쓰기: 클라이언트와의 데이터 송수신을
read()
및write()
메서드를 사용하여 비동기적으로 처리합니다. 각 작업이 완료될 때마다CompletionHandler
의completed
메서드가 호출됩니다. - 연결 유지 및 종료: 데이터 송수신이 완료되면 연결을 유지하거나 종료할 수 있습니다. 예제에서는 데이터를 계속해서 주고받는 형태로 구현되었습니다.
결과
비동기 소켓 채널을 이용한 에코 서버는 여러 클라이언트의 요청을 비동기적으로 처리할 수 있어 높은 성능을 제공합니다. 각 클라이언트의 요청은 별도의 스레드 없이 콜백 메서드로 처리되므로, 스레드 관리의 부담이 줄어들고 더 많은 클라이언트를 처리할 수 있습니다.
결론
이번 포스트에서는 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 |