14. 채널 04. 소켓 채널

04. 소켓 채널

소켓 채널은 파일 채널과 구별되는 몇 가지 다른 특징이 있다. 예를 들어, 비블록킹 모드가 지원되는 점이나 SelectableChannel을 상속해서 Selector와 함께 멀티플렉스 IO가 가능하다는 점 등을 들 수 있다. 하지만 이 두 채널은 근본적으로 목적과 사용처가 다르므로 구체적인 비교는 하지 않겠다. 소켓 채널의 출발점으로 JDK 1.4 이전의 소켓을 이용한 네트워크 프로그래밍에서 어떤 점이 문제였는지 그리고 NIO에서는 이것이 어떻게 해결됐는지를 간단히 알아보고 계속 진행하겠다.

기존의 소켓이나 IO를 이용한 네트워크 프로그래밍의 최대 단점은 블록킹된다는 점이었다. 클라이언트의 커넥션 요구를 받아들이는 서버소켓의 accept() 메소드부터 시작해서 클라이언트와 연결된 소켓에서 데이터를 읽어들이는 부분까지 블록킹되는 부분이 너무 많았다. 따라서 비블록킹이 지원되지 않는 기술로 비블록킹 효과를 내기 위한 여러 가지 방법 중 많은 부분에서 가장 이점이 많은 멀티스레드 모델을 이용하게 되었다. 하지만 이 모델은 클라이언트당 스레드 한 개를 사용해야 하므로 자원을 비 효율적으로 사용하게 되고 많은 클라이언트들이 접속해서 사용해야 될 스레드의 개수가 많아지면 스레드 컨텍스트 스위칭 등의 이유로 급격한 성능 저하를 가져왔다.

하지만 이제 더 이상 이런 방식으로 대량의 클라이언트를 처리 해야하는 서버를 만들지 않아도 된다. 바로 NIO에서 비블록킹 소켓 채널이 도입되고 이 소켓채널과 함께 멀티플렉스 IO를 지원하는 Selector가 도입되었기 떄문이다. 우선 여기에서는 소켓 채널들 각각에 대해서 알아보자.

소켓 채널은 ByteChannel과 ScatteringByteChannel, GatheringByteChannel 인터페이스들을 구현하고 있다. 따라서 소켓 채널은 읽기, 쓰기가 모두 가능하며 효율적인 읽기, 쓰기를 위한 Scatter/Gather도 이용할 수 있다. 하지만 서버소켓채널은 그렇지 않다. 서버소켓채널은 그 스스로 어떤 데이터도 전달할 수 없고 기존의 서버소켓과 마찬가지로 서버에 접속하려고 시도하는 클라이언트들을 처리하는 데에만 사용된다.

1. 비블록킹 모드

모든 소켓채널들은 비블록킹 모드로 동작할 수 있다. 소켓채널을 비블록킹 모드로 설정하는 방법은 간단하다. 바로 모든 소켓채널의 수퍼클래스인 SelectableChannel에서 소켓채널을 비블록킹 모드로 설정하는 메소드를 이용하면 된다.

SelectableChannel API

public abstract class SelectableChannel extends AbstractChannel implements {
    public abstract void configureBlocking(boolean block) throws IOException;
    public abstract boolean isBlocking();
    public abstract Object blockingLock();
}

SelectableChannel은 크게 두 가지 기능이 있는데, Selector와 함께 멀티플렉스 IO 작동을 하기 위해 소켓채널을 셀렉터에 등록하는 것과 관련된 기능이다. 그리고 다른 한가지는 소켓채널의 블록킹 모드 설정에 관련된 기능이다. 셀렉터와 관련된 것은 15장 셀렉터에서 자세히 다룰 것이고 이곳에서 블록킹 모드를 설정하는 것에 관련된 부분만을 살펴볼 것이다. 위의 API는 SelectableChannel 중에서 채널의 모드를 설정하는 것에 관련된 부분만을 나타낸 것이다. 모든 소켓채널들은 기본적으로 블록킹 또는 비블록킹 모드 중 한가지 모드인 상태로 존재하는데, 기본적으로 아무런 설정을 해주지 않으면 블록킹 모드가 된다. 위의 configureBlocking() 메소드의 파라미터 값으로 True를 설정하면 블록킹 모드, False를 설정하면 비블록킹 모드로 설정된다.

2. 서버 소켓 채널

서버 소켓 채널의 API

public abstract class ServerSocketChannel extends AbstractSelectableChannel {
    public static ServerSocketChannel open() throws IOException;
    public abstract ServerSocket socket();
    public abstract ServerSocket accept() throws IOException;
    public final int validOps();
}

서버 소켓 채널은 기존의 서버 소켓과 기본적으로 동일한 동작을 한다. 단 서버 소켓 채널은 비블록킹 모드로 동작할 수 있다는 점이 다르다. 서버소켓채널 객체는 정적 메소드인 open() 메소드를 통해 만든다. 보통 우리가 일반적으로 사용하는 new 키워드를 이용해서 서버소켓채널을 생성할 수 없다는 점에 주으하자. 서버소켓채널을 포함한 모든 소켓채널들은 생성자를 제공하지 않는다. 따라서 앞서 언급한 정적 팩토리 메소드인 open() 메소드를 통해서만 생성할 수 있다.

로컬 호스트 IP로 서버 소켓 채널을 바인드 하는 코드 템플릿

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();

InetAddress ia = InetAddress.getLocalHost();
//InetAddress ia = InetAddress.getByName("192.168.0.2");
Int Port = 8080;
InetSocketAddress isa = new InetSocketAddress(ia, port);
serverSocket.bind(isa);

3. 소켓 채널

네트워크 프로그래밍에 사용될 가장 일반적인 소켓채널(SocketChannel)에 대해 살펴보자. 소켓 채널 API

public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {
    //소켓채널 생성 및 소켓채널과 연관된 소켓을 구하기 위한 메소드들
    public static SocketChannel open() throws IOException;
    public static SocketChannel open(SocketAddress remote) throws IOException;
    public abstract Socket socket();

    //소켓채널의 연결에 관련된 메소드들
    public abstract boolean connect(SocketAddress remote) throws IOException;
    public abstract boolean isConnectionPending();
    public abstract boolean finishConnect() throws IOException;
    public final int validOps();
}

read(), write() 메소드를 제외한 메소드들을 기재했다. 소켓 채널에서의 read(), write() 메소드를 사용하는 것은 앞서 Scatter/Gather와 파일채널에서의 사용 방법과 그 동작 방식이 동일하다.

소켓 채널은 AbstractSelectableChannel을 상속한다. 따라서 SelectableChannel이 제공해주는 비블록킹 모드의 사용이 가능하다. 또한 ByteChannel, ScatteringByteChannel, GatheringByteChannel을 구현하고 있는데, ByteChannel의 특징인 읽기, 쓰기를 모두 지원하는 양방향성과 ScatteringByteChannel, GatheringByteChannel의 특징인 운영체제의 네이티브 코드의 도움을 받는 효율적인 읽기, 쓰기가 지원된다.

4. 데이터그램 채널

서버 소켓과 서버 소켓 채널, 소켓과 소켓채널과의 관계처럼 데이터그램 채널도 데이터그램 소켓과 연관 관계가 있다. DatagramSocketChannel은 이름이 너무 길어 DatagramChannel로 되었다고 한다. 앞서 봤던 소켓 채널들은 TCP/IP 같은 커넥션 기반의 스트림 프로토콜 모델인데 반해, 데이터그램 채널은 UDP/IP 같은 패킷 기반의 프로토콜 모델이다. TCP/IP와 UDP/IP는 1장 등 기존의 네트워크를 다루던 앞선 장에서 충분히 논의되었기 때문에 더 이상의 자세한 설명은 생략한다.

public abstract class DatagramChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {
    public static DatagramChannel open() throws IOException;
    public abstract DatagramSocket socket();

    public abstract DatagramChannel connect(SocketAddress remote) throws IOException;
    public abstract boolean isConnected();
    public abstract DatagramChannel disconnect() throws IOException;

    public abstract SocketAddress receive(ByteBuffer dst) throws IOException;
    public abstract int send(ByteBuffer src, SocketAddress target) throws IOException;
}

출처 : 김성박 송지훈 공저, 『 자바 IO & NIO 네트워크 프로그래밍』, 한빛미디어(2004.9.30), 14장 인용