-
블로킹 TCP 서버/클라이언트 작성하기JAVA/NIO2 2014. 3. 11. 00:20728x90
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. 코드
728x90'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