1. Java Channel ( java.nio.channels )
- 소켓, 파일, 하드웨어 장치 등에 연결할 수 있는 연결점(nexus) 입니다.
이 중에서, 소켓 (socketChannel) 에 대해서 알아보겠습니다.
SocketChannel 에는 ServerSocketChannel 과 SocketChannel 두가지 종류가 있습니다.
- 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 |