프로그래밍/Network

[네트워크] TCP 멀티플렉싱 이해하기

Jay Tech 2017. 4. 12. 21:09
반응형

멀티플렉싱이란?


하나의 통신 채널을 통해서 둘 이상의 데이터를 전송하는데 사용되는 기술이다.

기본적으로 멀티플렉싱이란 여러 개를 하나로 묶어 다중화 시킨다는 뜻이다. 코드에서 볼 것은 여러 개의 channel을 하나로 묶어서 사용하는 것인데, 이 방법을 입출력에 적용한다. 여기서 입출력 버퍼를 사용하게 된다. 

Blocking mode로 동작하는 것들과는 달리 멀티플렉싱은 Non-Blocking Mode로 동작하게 된다.




I/O blocking : I/O의 효율을 높이기 위해 디스크에서 읽어오는 data를 운영체제의 kernel 버퍼 에 저장한 후, 버퍼가 꽉 차면 프로세스의 버퍼로 옮기는데, data가 kernel 버퍼로 저장되는 동안 프로세스의(정확하게는 I/O를 요청한 thread가) blocking이 된다. 이렇게 blocking이 되면 프로 그램의 성능 저하로 이어진다. 예를 들면 ServerSocket의 accept() 메소드의 경우, client로부터 의 연결 요청이 들어올 때까지 blocking된다. 


Non-blocking : blocking의 반대인데, 차이점은 accept() 메소드는 연결의 없는 경우 곧바로 null을 return 한다는 점이다. SocketChannel class에서 지원하는 read() 메소드의 경우에도 client로부터의 입력이 없는 경우 곧 바로 null을 return 한다. 이는 configureBlocking() 메소 드를 이용해 설정이 가능하지만, 문제점이 있다. 일반적으로 ServerSocket에서는 무한 루프를 통 해서 지속적으로 client의 연결 요청은 받아들인다. 이때, blocking 모드로 설정되어있는 serverSocket은 client 연결 요청이 없는 경우 accept()에서 blocking 된다. 하지만 nonblocking으로 설정되어있다면 accept()에서 곧바로 null을 return 하고, 루프를 반복하게 되는데, 이 과정이 반복된다면, 큰 틀에서 봤을 때, CPU는 아무 일도 하지 않고 시간을 보내고 있는 셈이 되다. 즉, non-blocking의 단점은 CPU 시간을 낭비하게 될 수 있다는 점이다. JAVA에서는 이를 방지하기 위한 수단을 제공하고 있다. 그것이 Selector class이다. 이를 이용해 연결 요청이 들어 왔거나 또는 data를 읽어올 수 있는 경우에만 코드를 수행하도록 지정할 수 있다. 



Selector클래스


일종의 이벤트 리스너이다. 기존의 multi-threading 방식에서는 client 수가 증가하게 되면, 프 로그램의 성능이 급격히 낮아진다. 이를 막기 위해 Selector를 사용하여, 하나의 thread에서 다수의 동시 사용자를 처리할 수 있도록 했다. Non-blocking 모드로 설정된 channel에 Selector를 등록해 놓으면 channel은 연결 요청이 들어오거나 data가 도착한 경우에 Selector에 알리게 된다. 메시지를 받은 Selector는 어떤 기능을 사용할 수 있는지 return하게 된다. 옵션으로 사용할 수 있는 기능은 다음과 같다. 


SelectionKey.OP_READ : Channel로부터 data를 읽어올 수 있는 경우.(Value : 1) 

SelectionKey.OP._WRITE : Channel에 data를 쓸 수 있는 경우.(Value : 4) 

SelectionKey.OP_CONNECT : 연결 요청이 이뤄진 경우.(Value : 8) 

SelectionKey.OP_ACCEPT : 연결 요청이 들어온 경우.(Value : 16) 


SocketChannel/ServerSocketChannel : multiplexing을 지원하기 위한 class. 기존의 Socket class와 ServerSocket class에 대응한다고 볼 수 있다. 약간의 차이는 있지만, 기본적으로 사용 방법도 비슷하다. 이 class들은 AbstractSelectableChannel class로부터 상속되었고, non-blocking NIO를 지원하기 위해 configureBlocking() 메소드를 이용할 수 있다. 그 밖에도 AbstractSelectableChannel을 상속한 class로는 DatagramChannel, Pipe.SinkChannel, Pipe.SourceChannel이 있다.


소켓의 멀티플렉싱을 하는 이유는 하나의 소켓으로 여러 클라이언트와의 입출력을 가능하게 하기 위함이다. 이를 위해서는 입출력에 대해 어떤 클라이언트와 통신 할 것인지 구분해야 하는데 이것이 Selector 클래스가 하는 역할이다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class serverLauncher {
 
    public static void main(String[] args) {
        ServerSocketChannel serverSocketChannel = null;
        SocketChannel client = null;
        ServerSocket serverSocket = null;
        Selector selector = null;
 
        int port = 10789;
        String ip = "[아이피]";
 
        try {
 
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
 
            serverSocketChannel.configureBlocking(false);
 
            serverSocket = serverSocketChannel.socket();
            serverSocket.bind(new InetSocketAddress(ip, port));
 
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 
            System.out.println("now expecting Connection...");
 
            while (true) {
        
                selector.select(); 
                Iterator iterator = selector.selectedKeys().iterator();
 
                while (iterator.hasNext()) {
                    SelectionKey key = (SelectionKey) iterator.next();
 
                    if (key.isAcceptable()) {
                        client = ((ServerSocketChannel) key.channel()).accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_READ);
                        System.out.println(client.toString());
 
                    }
 
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
}
cs


ServerSocketChannel은 기본적으로 ServerSocket과 동일한 동작을 하지만 non-blocking모드를 지원한다.

Channel 관련 클래스들은 생성자를 통해 만들어지지 않고 open이라는 정적 메서드를 이용한다.

bind를 위해서 socket()메서드를 사용한다.


line 14 : 셀렉터와 서버 소켓채널을 생성한다.


line 36 : non-blocking mode로 설정한다. false를 주면 된다.


configureBlocking

public final SelectableChannel configureBlocking(boolean block)
                                          throws IOException
Adjusts this channel's blocking mode.

If the given blocking mode is different from the current blocking mode then this method invokes the implConfigureBlocking method, while holding the appropriate locks, in order to change the mode.

Specified by:
configureBlocking in class SelectableChannel
Parameters:
block - If true then this channel will be placed in blocking mode; if false then it will be placed non-blocking mode

line 19 : 주소를 바인딩한다.


line 22 : 셀렉터에 채널과 이벤트를 등록시켜준다.


line 26 : 무한 루프를 돌며 클라이언트의 접속을 기다린다. selector.select()는 일종의 blocking 메서드이다. 클라이언트의 연결이 들어오면 selectionkey 집합이 생성된다. 


line 34 : 등록한 옵션을 체크해서 이벤트를 준다.


line 42 : selectionkey 는 이벤트가 발생했을 때 생성되기 때문에 사용한 key는 반드시 버려준다.




클라이언트쪽을 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class clientLauncher {
 
    public static void main(String[] argv) {
    
        int serverPORT = 10789;
        String serverIP = "[아이피]";
        Selector selector = null;
        SocketChannel socketChannel = null;
        
        try {
            selector = Selector.open();
            
            socketChannel = SocketChannel.open(new InetSocketAddress(serverIP, serverPORT));
            
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            
            if(socketChannel != null) {
                System.out.println(socketChannel.toString());
            }
            
            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
cs


line 13 : 소켓채널을 열어야 한다. InetSocketAddress는 아래 api에서 발췌하였다.


Class InetSocketAddress

  • All Implemented Interfaces:
    Serializable


    public class InetSocketAddress
    extends SocketAddress
    This class implements an IP Socket Address (IP address + port number) It can also be a pair (hostname + port number), in which case an attempt will be made to resolve the hostname. If resolution fails then the address is said to be unresolved but can still be used on some circumstances like connecting through a proxy.

    It provides an immutable object used by sockets for binding, connecting, or as returned values.


아이피 주소와 포트번호로를 인자로 생성된다. 생성되지 않는다면 날아가지 않고 프록시와 같은 상황에서 계속 쓰인다. 이것은 변경불가능한 객체(immutable object)를 생성한다. 


서버와 여러 클라이언트 순으로 구동시키면 연결이 되는 것을 알 수 있다. 


반응형