Hello, Freakin world!

[Java] ServerSocket(Channel) 동기방식의 accept 동작 방식 본문

프로그래밍 언어/Java

[Java] ServerSocket(Channel) 동기방식의 accept 동작 방식

johnna_endure 2020. 3. 19. 11:38

오해

동기 방식의 IO 작업에서 서버소켓은 한 클라이언트와 통신 중일때, 다른 클라이언트와 통신할 수 없다.

그래서 나는 서버소켓에서 accept()를 호출하면 서버소켓과 클라이언트 소켓이 서로 1:1로 연결되는 이미지로 이해했는데, 이는 2퍼센트 부족한 이해였다.

 

 

부족했던 2퍼센트 때문에 어떻게 동기방식의 서버가 다수의 클라이언트의 요청을 처리하게 만들지 상당히 고민했었는데, 몇몇 예제들을 살펴보니 내가 accept()의 동작 방식을 완전히 잘못 이해하고 있다는 것을 깨달았다. 

 

 

추정

이 그림은 동작테스트를 통해 내가 추정하는 모델이다. 

 

클라이언트에게 공개되는 entry port가 존재한다. accept는 entry port로 접근하는 클라이언트 소켓을 내부에서 자동 지정된 포트 번호의 소켓에 매핑해주는 것 같다. 매핑이 끝나면 accept는 다시 연결가능한 상태가 되고, 다음 클라이언트를 내부 소켓에 매핑한다. 

 

연결된 후, 클라이언트에서 remote의 주소를 찍으면 둘 다 entry port가 찍힌다. 

그렇다고 하나의 포트에 두 클라이언트 모두를 연결하는 건 말이 안된다. (내가 모르는 low level의 미지의 기술이 있는걸까?)

아마도 클라이언트가 서버에 연결된 내부 소켓을 알 필요는 없기 때문에, 이렇게 구현한 것 같다.

 

다음은 예제 코드다.

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress("localhost", 9000));
        Thread server = new Thread(() -> {
            while(true) {
                try {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("accept 호출됨");
                    CompletableFuture.runAsync(() -> writeAndDelay(socketChannel)) ;
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        server.start();

        SocketChannel socketChannel1 = SocketChannel.open(new InetSocketAddress("localhost", 9000));
        SocketChannel socketChannel2 = SocketChannel.open(new InetSocketAddress("localhost", 9000));
        print(socketChannel1);
        print(socketChannel2);
    }
    public static void print(SocketChannel socketChannel) {
        ByteBuffer buffer = ByteBuffer.allocate(100);
            int reads = 0;
            try {
                System.out.println("[클라이언트] 연결여부 : " + socketChannel.isConnected());
                System.out.println("[클라이언트] " + socketChannel.getRemoteAddress());
                reads = socketChannel.read(buffer);
            } catch (IOException e) {
                e.printStackTrace();
        }
        buffer.flip();
        byte[] dst = new byte[reads];
        buffer.get(dst);
        System.out.println(new String(dst));
    }

    public static void writeAndDelay(SocketChannel socketChannel){
        try {
            System.out.println("[서버] " + socketChannel.getRemoteAddress());
            Thread.sleep(2000);
            socketChannel.write(ByteBuffer.wrap("hello world!".getBytes()));
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }
}
Comments