TCP 블로킹 채널
TCP네트워크 통신을 위해 사용하는 채널 :
java.nio.channel.ServerSocketChannel
java.nio.channel.SocketChannel
IO의 ServerSocket과 Socket 에 대응되는 클래스로,
IO가 버퍼를 사용하지 않고, 블로킹 입출력 방식만 지원한다면,
NIO는 버퍼를 이용하고, 블로킹과 넌블로킹 방식을 모두 지원한다.
ServerSocketChannel 생성과 연결 수락
채널 클래스들은 정적 메서드 open()으로 객체를 생성하고 필요를 다 하면 close()로 닫아준다.
기본적으로 블로킹 방식으로 동작하지만 논블로킹과 구분하기위해 명시적으로 configureBlocking(true) 메서드를 호출한다.
서버소켓채널 객체에서 bind()메서드 호출하고 매개값으로 InetSocketAddress( port number )객체를 준다.
서버소켓채널의 accept()는 클라이언트의 연결 요청이 있기까지 블로킹 돼있다가 연결 수락을 하면 소켓채널 객체를 리턴한다.
연결된 클라이언트의 IP와 포트 정보를 알고 싶다면 생성된 소켓채널의 getRemoteAddress() 메서드를 호출하여
SocketAddress를 얻으면 된다. //예제 들어가서 getRemoteAddress()의 리턴타입 확인하기
더이상 클라이언트를 위한 연결 수락이 필요 없다면 ServerSocketChannel의 close() 메서드로 닫아준다.
SocketChnnel 생성과 연결 요청
서버소켓채널과 마찬가지로 open()으로 객체를 생성하고 close()로 닫아주는데
서버 소켓 채널처럼 명시적으로 바인딩할 필요는 없다.( IO와 같다면 OS가 동적 포트번호 부여할 것 )
소켓도 configureBlocking(true) 메서드를 호출하여 명시적으로 블로킹 방식 지정
클라이언트가 서버소켓채널에 연결을 요청하려면
소켓 객체의 connect( ) 메서드에 서버 IP와 포트정보를 가진 InetSocketAddress 객체를 매개값으로 주면 된다.
SocketChnnel 데이터 통신
IO의 서버소켓과 소켓은 바이트 배열에 String변수.getByte() 리턴 값을 저장하여 write(바이트 배열)로 출력
read( 바이트배열 )로 바이트배열에 읽어온 데이터를 입력
NIO는 CharSet 객체를 생성하여 encode( 문자열 ) 메서드로 ByteBuffer 객체 리턴, write(바이트 버퍼)로 출력
read( 바이트 버퍼 )로 바이트 버퍼에 읽어온 데이터 입력
//IO와 다른 부분 주석 달기
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 51 52 53 | package networking2; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; public class ClientExample { public static void main(String[] args) { SocketChannel socketChannel = null; try { // SocketChannnel의 open()메서드로 객체 생성 socketChannel = SocketChannel.open(); // 블로킹으로 설정 : 기본 방식이 블로킹 방식이지만 명시적으로 구분해놓기 socketChannel.configureBlocking(true); System.out.println("[연결 요청]"); // connect() 메서드의 매개값으로 // 서버소켓채널의 IP와 포트 번호 데이터를 가진 InetSocketAddress 객체 주기 socketChannel.connect(new InetSocketAddress("localhost", 5001)); System.out.println("[연결 성공]"); // 채널은 바이트버퍼 사용, 스트림은 바이트배열을 써서 ByteBuffer byteBuffer = null; Charset charset = Charset.forName("UTF-8"); // Charset객체의 encode()는 문자열을 인코딩하여 ByteByffer객체 리턴 //IO에서는 문자열 객체의 getByte()로 바이트 배열을 얻는다. byteBuffer = charset.encode("Hello Server"); socketChannel.write(byteBuffer); System.out.println("[데이터 보내기 성공]"); byteBuffer = ByteBuffer.allocate(100); int byteCount = socketChannel.read(byteBuffer); byteBuffer.flip(); // IO에서는 읽어온 데이터를 바이트 배열에 넣고 // 아래 처럼 문자열 객체 생성 시에 배열과 디코딩할 문자셋을 매개값으로 준다 // message = new String(bytes, 0, readByteCount, "UTF-8") String message = charset.decode(byteBuffer).toString(); System.out.println("[데이터 받기 성공]: " + message); } catch (Exception e) { } try { socketChannel.close(); } catch (IOException e2) {} } } | cs |
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 | package networking; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; public class ServerExample { public static void main(String[] args) { ServerSocketChannel serverSocketChannel = null; try { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(true); serverSocketChannel.bind(new InetSocketAddress(5001)); while (true) { System.out.println("[연결 기다림]"); SocketChannel socketChannel = serverSocketChannel.accept(); InetSocketAddress isa = (InetSocketAddress) socketChannel.getRemoteAddress(); System.out.println("[연결 수락함] " + isa.getHostName()); ByteBuffer byteBuffer = null; Charset charset = Charset.forName("UTF-8"); byteBuffer = ByteBuffer.allocate(100); int byteCount = socketChannel.read(byteBuffer); byteBuffer.flip(); String message = charset.decode(byteBuffer).toString(); System.out.println("[데이터 받기 성공]: " + message); byteBuffer = charset.encode("Hello Client"); socketChannel.write(byteBuffer); System.out.println("[데이터(" + byteCount+ " bytes) 보내기 성공]"); } } catch (Exception e) {} try { serverSocketChannel.close(); } catch (Exception e2) {} } } | cs |
서버와 클라이언트 간의 데이터 통신에서 데이터를 읽을 때 읽어오는 데이터가 버퍼 수용량보다 크면?
>>파일에서 읽어올 때 처럼 무한루프 안에서 실행하고 read()가 -1을 리턴할 때 break;로 루프를 빠져나오면 될 것이다.
출력 결과
//클라이언트 실행이 콘솔에 뜨지 않는다. //해결방법 찾기
스레드 병렬처리
//IO기반 데이터통신과 동일
블로킹(blocking)과 인터럽트(Interrupt) (p.613 - 235 정리해서 포스팅할것)
IO의 Socket에서는 입출력 스트림의 입출력 메서드로 인해 작업 스레드가 블로킹되었을 때
다른 스레드가 작업 스레드의 interrupt() 메서드를 호출해도 블로킹 상태가 풀리지 않는다.
블로킹 상태를 풀려면 Socket의 close()를 호출하여 SocketException을 발생시켜야한다.
NIO의 SocketChannel은 입출력 메서드로인해 작업 스레드가 블로킹 되었을 때
다른 스레드가 작업 스레드의 interrupt() 메서드를 호출하면 ClosedByInterruptExeption 이 발생하고
SocketChannel은 즉시 닫히면서 블로킹 상태가 풀린다. // IO에서와 똑같이 close()메서드로 닫을 수도 있다.
동기방식이 블로킹방식, 비동기방식이 넌블로킹 ???
넌블로킹(non-blocking) 방식
자바는 블로킹 방식의 해결책으로 스레드풀과 넌블로킹 방식을 지원
넌블로킹 방식은 입출력 메서드, 연결요청/수락 메서드 실행시에도 블로킹X
클라이언트의 연결 요청이 없으면 accept()는 즉시 null을 리턴, 클라이언트가 데이터를 보내지 않으면 read()는 즉시 0을 리턴
넌블로킹 방식으로 실행되는 accept()는 연결이 될 때까지 블로킹되는 대신 무한루프 안에서 계속 반복 실행
-> CPU가 과도하게 소비되는 문제점 발생
이 때 사용하는 것이 셀렉터 (Selector)
셀렉터
이벤트 리스너 역할 //와치서비스와 비슷한듯
셀렉터 생성과 등록
선택된 키셋
채널 작업 처리
'Java > study' 카테고리의 다른 글
spring 18강 이메일 보내기 (0) | 2019.07.03 |
---|---|
java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for ~ (0) | 2019.06.13 |
NIO기반 입출력 (0) | 2019.04.24 |
스레드풀(ThreadPool) (0) | 2019.04.24 |
IO기반 네트워킹 (0) | 2019.04.22 |