다음 시스템은 Java Nio의 기능 메커니즘과 원칙, 프로세스 등을 통한 사용에 대한 심층적 인 소개를 제공합니다.
머리말
이 기사는 주로 Java의 IO 메커니즘을 설명합니다
두 조각으로 나뉩니다.
첫 번째 부분은 멀티 스레딩에서 IO 메커니즘을 설명합니다. 두 번째 부분은 IO 메커니즘 (New IO)에 따라 CPU 자원의 폐기물을 최적화하는 방법을 설명합니다.
에코 서버
단일 스레드 아래에 소켓 메커니즘을 소개 할 필요가 없습니다. 모르는 경우 너무 많은 스레드에서 정보를 확인할 수 있습니다. 소켓을 사용하면 어떻게됩니까?
우리는 가장 간단한 에코 서버를 사용하여 모든 사람이 이해하도록 돕습니다.
먼저 멀티 스레딩에서 서버와 클라이언트의 워크 플로우 다이어그램을 살펴 보겠습니다.
여러 고객이 동시에 서버에 요청을 보내는 것을 볼 수 있습니다.
서버는 여러 스레드가 해당 클라이언트와 일치 할 수 있도록 측정했습니다.
각 스레드는 클라이언트 요청 만 완료합니다
원칙이 끝나면 어떻게 구현되는지 봅시다.
여기에 간단한 서버를 썼습니다
스레드 풀 기술을 사용하여 스레드를 만듭니다 (특정 코드 함수에 대해 주석을 달았습니다).
public class myserver {private static executorService executorService = executor.newCachedThreadPool (); // 스레드 풀 생성 개인 정적 클래스 핸드 레그는 실행 가능 {// 새 클라이언트 요청이 있으면 소켓 클라이언트를 처리하기 위해이 스레드를 작성하십시오. // 클라이언트 생성 public handlemsg (Socket Client) {// 매개 변수 구조 바인딩 this.client = client; } @override public void run () {bufferedReader bufferedReader = null; // 문자 캐시를 만듭니다. 입력 스트림 인쇄기 PrintWriter = null; // 문자 작성 스트림 만들기 {bufferedReader = new bufferedReader (new inputStreamReader (client.getInputStream ()); // 클라이언트의 입력 스트림을 가져옵니다 PrintWriter = new PrintWriter (client.getOutputStream (), true); // 클라이언트의 출력 스트림을 가져 오십시오. True는 String inputline = null을 새로 고치는 것입니다. long a = system.currenttimeMillis (); while ((inputline = bufferedReader.Readline ())! = null) {printwriter.println (inputline); } long b = System.CurrentTimeMillis (); System.out.println ( "이 스레드는 :"+(ba)+"seconds!"); } catch (ioexception e) {e.printstacktrace (); } 마침내 {try {bufferedReader.close (); printwriter.close (); client.close (); } catch (ioexception e) {e.printstacktrace (); }}}} public static void main (string [] args)은 ioexception {// 서버의 기본 스레드는 루프 서버 server에서 클라이언트 요청을 듣는 데 사용됩니다. // 포트 8686 소켓 클라이언트 = null; while (true) {// loop listen client = server.accept (); // 서버는 클라이언트 요청 System.out.println (client.getRemoTesocketAdDress ()+"클라이언트 연결이 성공했습니다!")에리스됩니다. ExecutorService.submit (New Handlemsg (클라이언트)); // 처리를 위해 스레드 풀을 통해 클라이언트 요청을 HandlMSG 스레드에 넣습니다}}}위의 코드에서는 클래스를 사용하여 간단한 Echo 서버를 작성하여 Dead 루프를 사용하여 기본 스레드에서 포트 청취를 가능하게합니다.
간단한 클라이언트
서버를 사용하면 액세스하고 문자열 데이터를 보낼 수 있습니다. 서버의 기능은 이러한 문자열을 반환하고 시간이 걸리는 스레드를 인쇄하는 것입니다.
서버에 응답하기 위해 간단한 클라이언트를 작성해 봅시다.
public class myclient {public static void main (String [] args)은 ioexception {socket client = null; Printwriter printwriter = null; BufferedReader bufferedReader = null; try {client = new Socket (); client.connect (new inetSocketAddress ( "localhost", 8686)); printwriter = new printwriter (client.getoutPutStream (), true); printwriter.println ( "hello"); printwriter.flush (); bufferedReader = new bufferedReader (new inputStreamReader (client.getInputStream ())); // 서버 및 output System.out.println에서 반환 한 정보를 읽습니다 ( "서버의 정보는 다음과 같습니다."+bufferedReader.Readline ()); } catch (ioexception e) {e.printstacktrace (); } 마침내 {printwriter.close (); bufferedReader.close (); client.close (); }}}코드에서 문자 스트림을 사용하여 hello 문자열을 보냅니다. 코드가 정상이면 서버는 헬로 데이터를 반환하고 설정 한 로그 정보를 인쇄합니다.
Echo 서버 결과 표시
실행합시다 :
1. 서버를 열고 루프 청취를 활성화하십시오.
2. 클라이언트 열기 :
클라이언트가 반환 결과를 인쇄한다는 것을 알 수 있습니다.
3. 서버 로그 확인 :
매우 좋습니다. 간단한 멀티 스레드 소켓 프로그래밍이 구현됩니다
그러나 그것에 대해 생각하십시오 :
클라이언트가 요청하는 경우 IO 서버에 IO를 추가하면 수면을 추가하십시오.
각 요청을 10 초 동안 서버 스레드를 점유하게하십시오.
그런 다음 많은 클라이언트 요청이 있으며 각각 오랫동안
그러면 서버의 통일 기능이 크게 줄어 듭니다
서버에 많은 작업이 있기 때문이 아니라 서비스 스레드가 IO를 기다리고 있기 때문입니다 (수락, 읽기 및 쓰기가 모두 차단되기 때문에)
고속 CPU가 비효율적 인 네트워크 IO를 기다리게하는 것은 매우 무급입니다.
지금은 무엇을해야합니까?
니오
새로운 IO는 위의 문제를 성공적으로 해결했습니다. 어떻게 해결 했습니까?
클라이언트 요청을 처리하기위한 IO의 최소 단위는 스레드입니다.
Nio는 스레드보다 한 레벨이 작은 장치를 사용합니다 : 채널 (채널)
모든 수신, 읽기, 쓰기 및 기타 작업을 완료하기 위해 NIO에서 하나의 스레드 만 필요하다고 말할 수 있습니다.
NIO를 배우려면 먼저 세 가지 핵심 사항을 이해해야합니다.
선택기, 선택기
버퍼, 버퍼
채널, 채널
블로거는 재능이 없기 때문에 그의 인상을 깊게하기 위해 추악한 그림을 그렸습니다. ^
TCP에서 또 다른 NIO 워크 플로우 다이어그램을주세요 (라인을 그리기가 매우 어렵습니다 ...)
대략 이해하고 단계별로 가자
완충기
우선, 버퍼가 무엇인지 알아야합니다.
데이터 상호 작용은 더 이상 IO 메커니즘과 같은 스트림을 사용하지 않습니다
대신 버퍼 (버퍼)를 사용하십시오.
블로거는 사진이 가장 쉽게 이해하기 쉽다고 생각합니다.
그래서...
버퍼가 전체 워크 플로의 위치를 볼 수 있습니다.
실제 것을 살펴 보겠습니다. 위 그림의 특정 코드는 다음과 같습니다.
1. 먼저 바이트로 버퍼에 공간을 할당하십시오
바이트 버퍼 바이트 버퍼 = 바이트 버퍼. ALLOCATE (1024);
바이트 버퍼 객체를 만들고 메모리 크기를 지정하십시오
2. 버퍼에 데이터 쓰기 :
1). 채널에서 버퍼로의 데이터 : 채널 (Bytebuffer); 2). 클라이언트에서 버퍼로의 데이터 : Bytebuffer.put (...);
3. 버퍼에서 데이터 읽기 :
1). 버퍼에서 채널로의 데이터 : Channel.Write (바이 테버); 2). 버퍼에서 서버로의 데이터 : bytebuffer.get (...);
선택자
선택기는 NIO의 핵심이며 채널의 관리자입니다.
select () 차단 메소드를 실행하여 채널이 준비되었는지 확인하십시오.
데이터를 읽을 수 있으면이 메소드의 반환 값은 선택키 수입니다.
따라서 서버는 일반적으로 channl이 준비 될 때까지 select () 메소드 Dead 루프를 실행하고 작동하기 시작합니다.
각 채널은 이벤트를 선택기에 바인딩 한 다음 선택 키 객체를 생성합니다.
주목해야 할 것은 다음과 같습니다.
채널 및 선택기가 바인딩되면 채널은 비 차단 모드에 있어야합니다.
FilEchannel은 소켓 채널이 아니기 때문에 비 블로킹 모드로 전환 할 수 없으므로 Filechannel은 선택기와 이벤트를 바인딩 할 수 없습니다.
NIO에는 4 가지 유형의 이벤트가 있습니다.
1. selectionkey.op_connect : 연결 이벤트
2.SelectionKey.op_accept : 이벤트를받습니다
3. selectionkey.op_read : 이벤트를 읽습니다
4.selectionkey.op_write : 쓰기 이벤트
채널
총 4 개의 채널이 있습니다.
Filechannel : IO 파일 스트림에서 작용합니다
DatagramChannel : UDP 프로토콜에서 작동합니다
Socketchannel : TCP 프로토콜에서 작동합니다
serversocketchannel : TCP 프로토콜에 작용합니다
이 기사는 일반적으로 사용되는 TCP 프로토콜을 통해 NIO를 설명합니다.
Serversocketchannel을 예로 들어 보겠습니다.
Serversocketchannel 채널을 엽니 다
serversocketchannel serversocketchannel = serversocketchannel.open ();
Serversocketchannel 채널을 닫습니다.
serversocketchannel.close ();
루핑 양말 채널 :
while (true) {socketchannel socketchannel = serversocketchannel.accept (); clientChannel.configureBlocking (false);} clientChannel.configureBlocking(false); 진술은이 채널을 비 블로킹으로 설정하는 것입니다.
선택 키
선택 키는 채널과 선택기 간의 상호 작용의 핵심 구성 요소입니다.
예를 들어, Socketchannel의 선택기를 바인딩하고 연결 이벤트로 등록하십시오.
socketchannel clientChannel = socketchannel.open (); clientChannel.configureBlocking (false); clientChannel.Connect (new inetSocketAddress (port)); clientChannel.register (selector, selectioney.op_connect);
Core는 Regrest () 메소드에 있으며 SelectionKey 객체를 반환합니다.
채널 이벤트를 감지하려면 다음 방법을 사용할 수있는 이벤트입니다.
selectionkey.isacceptable (); selectionkey.isconnectable (); selectionkey.isreadable (); selectionkey.iswritable ();
서버는 이러한 방법을 통해 폴링에서 해당 작업을 수행합니다.
물론, 채널 및 선택기에 묶인 키도 차례로 얻을 수 있습니다.
채널 채널 = selectionKey.Channel (); selector selector = selectionKey.Selector ();
채널에 이벤트를 등록 할 때 버퍼를 바인딩 할 수도 있습니다.
clientChannel.register (key.Selector (), selectionKey.op_read, bytebuffer.allocatedirect (1024));
또는 객체를 바인딩합니다.
selectionkey.attach (Object); 객체 anthorobj = selectionKey.Attachment ();
NIO의 TCP 서버
너무 많이 이야기 한 후, 우리는 가장 단순하고 가장 핵심 코드를 살펴볼 것입니다 (많은 의견을 추가하는 것은 우아하지는 않지만 모든 사람이 이해하기 편리합니다).
패키지 cn.blog.test.niotest; import java.io.ioexception; import java.net.inetSocketAddress; import java.nio.bytebuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util. // 선택기 개인 최종 최종 정적 int 포트 생성 = 8686; 개인 최종 정적 int buf_size = 10240; private void initserver ()는 ioexception {// 채널 관리자 객체 선택기 생성 this.selector = selector.open (); // 채널 객체 작성 Serversocketchannel 채널 = serversocketchannel.open (); channel.configureBlocking (false); // 채널을 비 블로킹 채널로 설정합니다 .Socket (). bind (new inetSocketAddress (port)); // 포트 8686에서 채널을 바인딩합니다. // 위의 채널 관리자 및 채널을 바인딩하고 채널에 대한 op_accept 이벤트를 등록한 후 이벤트를 등록한 후 selector.select ()가 반환됩니다 (키). 이벤트가 selector.select ()에 도달하지 않으면 selectionKey = channel.register (selector, selectkey.op_accept)를 차단합니다. while (true) {// poll selector.select (); // 이것은 차단 방법이며, 데이터가 읽을 때까지 대기하고, 리턴 값은 키 수 (여러 가지가있을 수 있음) 세트 키 = selector.selectedKeys (); // 채널에 데이터가 있으면 생성 된 키에 키를 키에 액세스하여 ITERATOR = KEYS.ITERATOR (); //이 키 컬렉션의 반복자를 가져옵니다. // 컬렉션에서 핵심 인스턴스를 가져옵니다. iterator.remove (); // 현재 키 인스턴스를 얻은 후이 요소 에서이 요소를 삭제하는 것을 잊지 마십시오. 그렇지 않으면 매우 중요합니다. 그렇지 않으면 (key.iscactable ()) {// 현재 키로 표시되는 채널이 허용 가능한 상태에 있는지 여부를 판단하면 오류가 발생합니다. } else if (key.isreadable ()) {doread (key); } else if (key.iswritable () && key.isvalid ()) {dowrite (key); } else if (key.isconnectable ()) {system.out.println ( "Connectable!"); }}}} public void doaccept (selectionKey Key)는 ioException {serversocketchannel serverChannel = (serversocketchannel) key.channel (); System.out.println ( "serversocketchannel은 루프에서 듣고 있습니다"); Socketchannel ClientChannel = ServerChanne.Accept (); clientChannel.configureBlocking (false); clientChannel.register (key.Selector (), selectionKey.op_read); } public void doread (selectionKey Key)는 ioException {socketchannel clientChannel = (Socketchannel) key.Channel (); 바이트 버퍼 바이트 버퍼 = bytebuffer.allocate (buf_size); long bytesread = clientChannel.Read (ByteBuffer); while (bytesread> 0) {bytebuffer.flip (); 바이트 [] data = bytebuffer.array (); 문자열 정보 = 새 문자열 (data) .trim (); System.out.println ( "클라이언트에서 전송 된 메시지는"+info); Bytebuffer.clear (); BYTESREAD = ClientChannel.Read (ByteBuffer); } if (bytesread == -1) {clientChannel.close (); }} public void dowrite (selectionkey 키)는 ioexception {bytebuffer bytebuffer = bytebuffer.allocate (buf_size); Bytebuffer.flip (); socketchannel clientChannel = (Socketchannel) key.channel (); while (bytebuffer.hasremaining ()) {clientChannel.write (bytebuffer); } bytebuffer.compact (); } public static void main (String [] args)은 ioexception {mynioserver mynioserver = new Mynioserver (); mynioserver.initserver (); }} 모니터 채널을 인쇄하고 서버 Socketchannel이 실행되기 시작했을 때 말했습니다.
NIO 클라이언트의 디버그와 협력하면 명확하게 찾을 수 있습니다. select () 설문 조사에 들어가기 전에
허락 이벤트의 키가 이미 있지만 Select ()는 기본적으로 호출되지 않습니다.
대신, 당신은 accept의 selectionKey를 호출하기 전에 다른 흥미로운 이벤트가 select ()에 의해 캡처 될 때까지 기다려야합니다.
그런 다음에야 서버 Socketchannel은 원형 모니터링을 수행하기 시작했습니다.
다시 말해, 선택기에서는 서버 Socketchannel이 항상 유지됩니다.
및 serverChannel.accept(); 진정으로 비동기식입니다 (channel.configureBlocking (false); initserver 메소드에서)
연결이 허용되지 않으면 Null이 반환됩니다.
Socketchannel이 성공적으로 연결된 경우이 Socketchannel은 Write (Read) 이벤트를 등록합니다.
비동기식을 설정합니다
NIO의 TCP 클라이언트
서버가 있으면 클라이언트가 있어야합니다.
실제로 서버를 완전히 이해할 수 있다면
고객의 코드는 비슷합니다
패키지 cn.blog.test.niotest; import java.io.ioexception; import java.net.inetSocketAddress; import java.nio.bytebuffer; import java.nio.channels.selectkey; import java.nio.channels.selector; import java.nio.channels.sockchetch annelle; mynioclient {private selector selector; // 선택기 개인 최종 최종 정적 int 포트 생성 = 8686; 개인 최종 정적 int buf_size = 10240; 개인 정적 바이트 버퍼 바이트 버퍼 = Bytebuffer.allocate (buf_size); private void initclient ()는 ioexception {this.selector = selector.open (); Socketchannel ClientChannel = Socketchannel.open (); clientChannel.configureBlocking (false); ClientChannel.connect (New inetSocketAddress (PORT)); clientChannel.register (selector, selectionKey.op_connect); while (true) {selector.select (); iterator <selectionKey> iterator = selector.SelectedKeys (). iterator (); while (iterator.hasnext ()) {selectionkey key = iterator.next (); iterator.remove (); if (key.isconnectable ()) {doconnect (key); } else if (key.isreadable ()) {doread (key); }}} public void doconnect (selectionKey Key)는 ioException {socketchannel clientChannel = (socketchannel) key.channel (); if (clientChannel.isconnectionPending ()) {clientChannel.FinishConnect (); } clientChannel.configureBlocking (false); 문자열 정보 = "Hello Server !!!"; Bytebuffer.clear (); ByteBuffer.put (info.getBytes ( "UTF-8")); Bytebuffer.flip (); ClientChannel.write (Bytebuffer); //clientchannel.register(key.selector(), telectionkey.op_read); clientChannel.close (); } public void doread (selectionKey Key)는 ioException {socketchannel clientChannel = (Socketchannel) key.Channel (); clientChannel.Read (ByteBuffer); 바이트 [] data = bytebuffer.array (); 문자열 msg = new String (data) .trim (); System.out.println ( "서버 보내기 메시지 :"+msg); clientChannel.close (); key.selector (). Close (); } public static void main (String [] args)은 ioexception {mynioclient mynioclient = new mynioclient (); mynioclient.initclient (); }}출력 결과
여기서 서버와 두 명의 클라이언트를 열었습니다.
다음으로, 당신은 동시에 천 명의 고객을 열 수 있습니다. CPU가 충분히 강력한 한, 서버는 막힘으로 인해 성능을 줄일 수 없습니다.
위는 Java Nio에 대한 자세한 설명입니다. 궁금한 점이 있으면 아래 메시지 영역에서 논의 할 수 있습니다.