본문 바로가기
netty

1. Java Channel

by 이석준석이 2021. 4. 10.

1. Java Channel ( java.nio.channels )

  • 소켓, 파일, 하드웨어 장치 등에 연결할 수 있는 연결점(nexus) 입니다.

이 중에서, 소켓 (socketChannel) 에 대해서 알아보겠습니다.


SocketChannel 에는 ServerSocketChannelSocketChannel 두가지 종류가 있습니다.

  • ServerSocketChannel
    • 서버측에서 사용된다.
    • 서버측의 포트와 바인딩 (bind()) 한 뒤에, 연결요청 (connect()) 이 오면 SocketChannel을 생성하여 클라이언트와 통신한다.
  • SocketChannel
    • 클라이언트, 서버측에서 모두 사용되며 데이터 read/write 등의 기능을 한다.

※ 바인딩 (bind()) : "자신의 운영체제에 있는 포트를 할당하여 사용하겠다." 라는 의미로 생각하면 편할거같아요.


먼저 blocking 방식의 통신을 통해서 Channel 을 한번 사용해보겠습니다.

 

클라이언트 서버
  1. 서버는 bind() 를 통해서 자신의 OS 포트에 연결한다.
  2. accept() 를 통해서 클라이언트의 요청을 기다린다. (blocking 상태)
3. 클라이언트또한 bind() 를 통해서 자신의 OS 포트에 연결한다.  
4. connect() 를 이용하여 서버측에 연결한다.  
  5. 서버는 요청을 받은뒤에 새로운 SocketChannel 을 생성한다.
이후 서로 통신 이후 서로 통신

서버측 코드

@Slf4j
public class ServerSocketChannelStudy {
    public static void main(String[] args) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080)); // localhost:8080

        // ServerSocketChannel 이 socketChannel 생성
        final SocketChannel socketChannel = serverSocketChannel.accept();
        log.info("connected : {}", socketChannel);

        socketChannel.close();
        serverSocketChannel.close();
    }
}

 

클라이언트 측 코드

@Slf4j
public class SocketChannelStudy {
    public static void main(String[] args) throws IOException {
        final SocketChannel socketChannel = SocketChannel.open();

        socketChannel.bind(new InetSocketAddress(8181));
        socketChannel.connect(new InetSocketAddress(8080));

        socketChannel.close();
    }
}

 

서버측에서 찍히는 로그는 아래와 같다.

  • 서버의 주소 : 127.0.0.1:8080 (위의 클라이언트 측 코드에서 bind 한 주소)
  • 클라이언트의 주소 : 127.0.0.1:8181 (위의 서버측 코드에서 bind 한 주소)
connected : java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:8181]

 

서버측에서 accept(), read() 메소드에서 블로킹이 되므로 쓰레드가 낭비되는점에서 이는 리소스의 낭비가 클수밖에 없습니다.

 

※ ServerSocketchannel 은 자체적으로 여러 요청이 오는경우 Queueing 이 가능합니다.

  • serverSocketChannel.bind(주소, Queue size)

이번에는 비동기적인 방식 (non-blocking) 을 사용해보겠습니다.

해당 방식은 서버 앞단에 Selector 가 요청에 따라서 알맞는 Channel 을 반환합니다.

  • ServerSocketChannel 의 경우에는 Selector 에 등록할 때 OP_ACCEPT (요청수락 가능)
  • SocketChannel 의 경우에는 Selector 에 등록할 때 OP_READ / OP_WRITE (읽기, 쓰기 가능) 로 등록
  • ※ 등록 방식은 예를 들었을 뿐, 자기 마음대로 하면 됩니다.

해당 요청에 따라 현재 가용할 수 있는 SocketChannel 혹은 ServerSocketChannel 을 반환하고, 이를 통해 요청을 처리하는 방식입니다.

 

위의 accept(), read() 와는 반대로 블로킹이 없이 (non-blocking) 동작합니다.

클라이언트 서버
  1. Selector 를 생성합니다.
  2. Selector 에 바인딩된 ServerSocketChannel 을 등록 (OP_CONNECT) 합니다.
3. 바인딩된 클라이언트가 connect() 요청을 전송합니다.  
  4. 현재 Selector 에는 ServerSocketChannel 이 이를 받을수 있기때문에 요청을 받아 
  4.1 SocketChannel 을 생성합니다.
  4.2 Selector 에 SocketChannel 을 등록 (OP_READ/WRITE) 합니다.
5. 클라이언트가 write 요청을 전송합니다.  
  6. 현재 Selector 에는 read 할 수 있는 SocketChannel 이 있으므로, 이를 읽은뒤에 요청을 처리합니다.

 

서버측 코드

@Slf4j
public class ServerSocketChannelNioStudy {
    public static void main(String[] args) throws IOException {
        // selector 생성
        final Selector selector = Selector.open();

        // serverSocketChannel bind() 및 selector 에 등록
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        selector.select(); // 블로킹입니다. (요청은 기다려야하니까요)
        final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

        while(true) {
            while(iterator.hasNext()) {
                final SelectionKey key = iterator.next();
                if(key.isAcceptable()) {
                    final ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    final SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                } else if(key.isReadable()) {
                    final SocketChannel sc = (SocketChannel) key.channel();
                    // 읽는 과정...
                }
            }
        }
        // release resource
    }
}

 

 

'netty' 카테고리의 다른 글

3. BootStrap  (0) 2021.04.13
2. EventLoopGroup  (0) 2021.04.10