ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 블로킹 TCP 서버/클라이언트 작성하기
    JAVA/NIO2 2014. 3. 11. 00:20

    1. 서버 작성하기


    - 새 서버 소캣 채널 생성하기

    스트림 지향 리스닝 소캣을 위한 서택가능 채널을 생성

    java.nio.channels.ServerSocketChannel 클래스 에서 제공하며, 멀티스레드에서 사용해더 안전하다.


    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    

    *새로 생성한 서버 소켓 채널은 바이딩되지도 연결되지도 않았음을 염두해 둬야 한다.


    서버 소켓이 이미 열려있는지 또느 서버 소켓을 성공적으로 열었는지 ServerSocketChannel.isOpen() 메서드를 호출해서 확인 할수 있다.


    if(serverSocketChannel.isOpen()){
          ...
    }
    

    - 블로킹 설정하기

    서버 소켓 채널을 성공적으로 열었따면 블로킹을 설정하자 boolean 값을 인자로 받는 ServerSocketChannel.configureBlocking() 매소드를 호출해서 블로킹을 지정한다. true를 저달하면 블로킹을 사용하는 것이며, false를 전달하면 논블로킹을 사용하는 것이다.


    serverSocketChannel.configureBlocking(true);
    

    * 이 매소드는 셀렉터(selector)를 통해 멀티플렉스되는 채널인 SelectableChannel 객체를 반환한다. 논블로킹 모드를 사용한다면 매우 유용하지만, 블로킹모드를 사용하는 것으로 무시하고 넘어간다.


    - 서버 소켓 채널 옵션 설정하기

    이부분은 Option이다. 지정해야 하는 옵션은 없지만(기본 값을 사용할 수 있다.) 옵션 지정을 보여주기 위해 몇가지 옵션을 명시적으로 지정해보겠다.


    serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 4 * 1024);
    serverSocketChannel.setOption(StandardSocketOptions.SO_REUEADDR, true);
    

    또한 상속되 메소드 supportedOptions()를 호출해서 서버 소켓 채널에서 지원하는 옵션을 알아낼 수 있다.


    Set<SocketOption<?>> options = severSocketChannel.supportedOptions();
    for(SocketOption<?> option : options )System.out.println(option);
    

    - 서버 소켓 채널 바인딩하기

    ServerSocketChannel.bind() 메소드를 호출해서 서버 소켓 채널 바인딩을 하자.

    다음 소스는 로컬호스트(127.0.0.1) 포트 8876에서 유입되는 연결을 기다릴 것이다.


    final int DEFAULT_PORT = 8876;
    final String IP = "127.0.0.1";
    serverSocketChannel.bind(new InetSocketAddress(IP, DEFAULT_PORT ));
    

    서버 소캣 채널을 바인딩하는 다른 방법은 IP 주소를 정하지 않고 포트 번호만 InetSocketAddress 객체에 생성하는 것이다. 이 경우 IP 주소는 와일드카드(*) 주소가 되며, 포트 번호는 지정한 값이된다. 와일드카드 주소는 바인딩 작업에만 사용할수 있는 특별한 IP 주소로 보통은 'any'를 뜻한다.


    final int DEFAULT_PORT = 8876;
    serverSocketChannel.bind(new InetSocketAddress(DEFAULT_PORT ));
    


    * IP 와일드카드 주소를 사용하려면 개별 IP 주소를 갖는 네트워크 인터페이스가 여러 개 장착되 환경에서 발생할 수 있는 바람직하지 않는 충돌을 피할 수 있어야 한다. 이런 경우 이 무제를 해결할지 확시할 수 없다면 와일드 카드를 사용하는 대신 지정된 네트워크 주소에 소켓을 바인딩 하는 것이 좋다.


    추가로 소캣을 바인딩하는 주소와 대기연결의 최대 갯를 인자로 받는 bind() 메소드가 하나 더 있다.


    public abstract ServerSocketChannel bind(SocketAddress local, int pc) throws IOException;
    


    bind() 매서드에 null을 전달하면 로컬 주소도 자동으로 할당된다. NetworkChannel 인터페이스에서 상속 받은 ServerSocketChannel.getLocalAddress() 메소드를 호출해서 바인딩된 로컬 주소를 알아 낼수도 있다. 만약 서버 소켓 채널이 아직 바인딩 되지 않았다면 이 메소드는 null을 반환한다.


    System.out.println(serverSocketChannel.getLocalAddress());
    

    - 연결 수락

    블로킹 모드임으로 연결을 수락하면 새로운 연결을 이용할 수 있거나, I/O 에러가 발생할 때까지 애플리케이션은 불록 될 것이다.

    ServerSocketChannel.accept() 매소드를 호출해서 새로운 여결을 수락할 수 있다.

    이 메소드는 새로운 연결을 위한 클라이언트 소켓 채널을 반환 한다. 이는 SocketChannel클래스의 인스턴스로 스트림 지향 연결 소켓을 우한 선택가능한 채널을 표현한다.


    SocketChannel socketChannel = serverSocketChannel.accept();
    

    *바인딩 되지 않은 서버소켓 채널에 대하 accept() 메소드를 호출하면 NotYetBoundException 예외가 발생한다.


    새로운 연결을 수락했으면, SocketChannel.getRemoteAddress() 메서드를 호출해서 원격지 주소를 알아낼 수 있다. 

    System.out.println("Incoming connection from: " + socketChannel.getRemoteAddress());
    


    - 데이터 전송하기

    서버와 클라이언트는 바이트 배열이나 표준 자바파일 I/O 와 함께 스트림을 사용해 매핑된 다양한 종류의 데이터 패킷을 전송하거나 수신할 수 있다.


    전송(송신/수신) 구현은 유연성이 있으며, 관점에 따라 구현 과정이 다양하다.  다음 예제는 바이트 버퍼를 사용하였다.


    ByteBuffer buffer = ByteBufer.allocatedDirect(1024);
      ...
    while (socketChannel.read(buffer) != -1){
      buffer.flip();
      socketChannel.write(buffer);
      if(buffer.hasRemaining()){
        buffer.compact();
      }else{
        buffer.clear();
      }
    

    SocketChannel 클래스는 바이트 버퍼의 read()/wirte() 메소드를 제공한다.


    이 채널에서 바이트 시퀀스를 주어진 버퍼로 읽어 들인다. 이때 읽어 들인 바이트 수를 반환한다. (0을 반환 할수도 있다.) 채널이 스트럼의 끝에 도달하면 -1을 반환한다.

    public abstract int read(ByteBuffer dst) throws IOException;
    public final long read(ByteBuffer[] dsts) throws IOException;
    public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
    


    주어진 버퍼에서 이 채널로 바이트 시퀀스를 쓴다. 이들 메소드ㅡ 쓴 바이트 수를 반환 한다. 이 값은 0이 될 수도 있다.


    public abstract int write(ByteBuffer src) throws IOException;
    public final long write(ByteBuffer[] srcs) throws IOException;
    public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
    

    * 버퍼대신 스트림 사용하기

    채널은 버퍼와 궁합이 매우 좋지만, 버퍼 대신 스트림을 사용하기로 했다면 다음과 같이 사용 할 수 있다.


    InputStream in = socketChannel.socket().getInputStream();
    OutputStream out = socketChannel.socket().getOutputStream();
    


    - I/O를 위한 연결 종료하기

    새 메소드 SocketChannel.shutdownInput()이나 SocketChannel.shutdownOutput()를 호출해서 채널을 닫지 않고 I/O를 위한 연결을 종료 할수 있다.입력(또는 읽)을 종료하면서 스트림의 끝을 가리키는 -1을 반환해서 그 이후의 읽기 시도를 모두 거부한다. 출력(또는 쓰기)를 위한 연결을 종료하면 CloseChannelEception 예외를 던지게 해서 이후의 모든 쓰기 시도를 거부한다.


    //읽기를 위한 연결을 종료한다.
    socketChannel.shutdownInput();
    //쓰기를 위한 연결을 종료 한다.
    socketChannel.shutdownOutput();
    

    채널을 닫지 않고 읽기/쓰기 시도를 거절하고 싶을 때 이들 메소드는 매우 유용하다. 연결이 현재 I/O에 대해 종료 됐는지는 다음 코드로 알수 있다.


    boolean inputdown = socketChannel.socket().isInputShutdown();
    boolean outputdown = socketChannel.socket().isOutputShutdown();
    


    - 채널 닫기

    채널이 쏠모 없게 되면 채널을 반드시 닫아야 한다. 이를 위해 SocketChannel.close() 메소드나 ServerSocketChannel.close() 메소드를 호출할 수 있다.


    SocketChannel.close();
    ServerSocketChannel.close() ;
    

    2. 클라이언트 작성하기


    - 새 소켓 채널 생성하기

     상동

    - 블로킹 모드 설정하기

     상동

    - 소켓 채널 옵션 설정하기

     상동


    - 채널의 소켓 연결하기

    소켓 채널을 연 다음에는 (선택적으로 바인딩한 이후) 원격지 주소(서버측 주소)에 연결해야 한다. 블로킹 모드 이므로 원격지 주소에 연결하면 새로운 연결을 이용할수 있거나 I/O 에러가 발생 할 때까지 애플리케이션은 블록 된다. 


    final int DEFAULT_PORT = 8876;
    final String IP = "127.0.0.1";
    serverSocketChannel.connect(new InetSocketAddress(IP, DEFAULT_PORT ));
    

    이 매소드는 연결 시도 성공을 나타내는 Boolean 을 반환한다. 또한 SocketChannel.isConnected() 메소드를 호출해서 확인할 수 있다.


    - 데이터 전송하기

    서버와 클라이언트가 연결되었으면 데이터 패킷 전송을 시작한다.

                        //transmitting data
                        socketChannel.write(helloBuffer);
    
                        while (socketChannel.read(buffer) != -1) {
    
                            buffer.flip();
    
                            charBuffer = decoder.decode(buffer);
                            System.out.println(charBuffer.toString());
    
                            if (buffer.hasRemaining()) {
                                buffer.compact();
                            } else {
                                buffer.clear();
                            }
    
                            int r = new Random().nextInt(100);
                            if (r == 50) {
                                System.out.println("50 was generated! Close the socket channel!");
                                break;
                            } else {
                                randomBuffer = ByteBuffer.wrap("Random number:".concat(String.valueOf(r)).getBytes());
                                socketChannel.write(randomBuffer);
                            }
                        }
    


    - 채널 닫기

    상동



    3. 코드


    BIOClient.java


    BIOServer.java


    'JAVA > NIO2' 카테고리의 다른 글

    [NIO.2]블로킹(BIO) 논블로킹(NIO) 차이점  (0) 2014.03.08
    [NIO.2]NetworkChannel  (0) 2014.02.14
    [NIO.2]FileChannel  (0) 2014.02.05
    [NIO.2]ByteBuffer  (0) 2014.02.04
    [NIO.2]SeekableByteChannel  (0) 2014.02.04
Designed by Tistory.