배경 지식
동기, 비동기식, 차단, 비 차단
우선, 이러한 개념은 혼동하기가 매우 쉽지만 NIO에 관여하므로 요약하겠습니다.
동기화 : API 통화가 반환되면 발신자는 작업 결과 (실제로 얼마나 많은 바이트 수를 읽고 쓰는지)를 알고 있습니다.
비동기식 : 동기화와 비교하여 발신자는 API 호출이 반환 될 때 작동 결과를 알지 못하며 콜백은 나중에 결과에 알립니다.
차단 : 읽을 데이터가 없거나 모든 데이터를 작성할 수없는 경우 현재 스레드 대기를 중단하십시오.
비 블로킹 : 읽을 때 읽을 수있는 데이터만큼 읽을 수 있습니다. 글을 쓸 때, 당신은 당신이 작성할 수있는 데이터만큼 글을 쓰고 반환 할 수 있습니다.
I/O 운영의 경우 Oracle의 공식 웹 사이트의 문서에 따르면 동기화 및 비동기 구분 표준은 "발신자가 I/O 작업이 완료 될 때까지 기다려야하는지 여부"입니다. 이 "I/O 작동이 완료되기를 기다리는 것"은 데이터를 읽거나 모든 데이터를 작성해야한다는 것을 의미하는 것이 아니라 TCP/IP 프로토콜 스택 버퍼와 JVM 버퍼 사이에 데이터가 전송되는 시간과 같이 I/O 작동이 실제로 수행 될 때 발신자가 대기 해야하는지 여부를 의미합니다.
따라서 일반적으로 사용되는 read () 및 write () 메소드는 동기 I/O입니다. 동기 I/O는 차단 및 비 블로킹의 두 가지 모드로 나뉩니다. 비 블로킹 모드 인 경우 읽기 할 데이터가 없음을 감지하면 직접 반환되며 I/O 작동이 실제로 수행되지 않습니다.
요약하면, Java에는 실제로 세 가지 메커니즘이 있습니다 : 동기 차단 I/O, 동기 비 블로킹 I/O 및 비동기 I/O. 우리가 아래에서 이야기하는 것은 처음 두 것입니다. JDK1.7은 NIO.2라고하는 비동기 I/O를 도입하기 시작했습니다.
전통적인 IO
우리는 새로운 기술의 출현에는 항상 개선과 개선이 동반되며 Javanio의 출현도 알고 있습니다.
전통적인 I/O는 I/O를 차단하고 있으며 주요 문제는 시스템 자원의 낭비입니다. 예를 들어, TCP 연결의 데이터를 읽으려면 read () inputStream 메소드를 호출하여 현재 스레드가 일시 중단되고 데이터가 도착할 때까지 깨어나지 않습니다. 스레드는 데이터 도착 기간 동안 메모리 리소스 (스토리지 스레드 스택)를 차지하지만 아무것도 수행하지 않습니다. 이것이 똥이 아닌 구덩이를 점령하고 말하는 말입니다. 다른 연결의 데이터를 읽으려면 다른 스레드를 시작해야합니다. 동시 연결이 많지 않은 경우 괜찮을 수 있지만 연결 횟수가 특정 척도에 도달하면 많은 수의 스레드에 의해 메모리 자원이 소비됩니다. 반면에 스레드 스위칭은 프로그램 카운터 및 레지스터의 값과 같은 프로세서의 상태를 변경해야하므로 많은 스레드 사이를 전환하는 것도 리소스 낭비입니다.
기술 개발을 통해 현대 운영 체제는 이러한 자원 낭비를 피할 수있는 새로운 I/O 메커니즘을 제공합니다. 이를 바탕으로 Javanio는 태어 났으며 NIO의 대표적인 특징은 비 차단 I/O입니다. 그 후 즉시 비 블로킹 I/O를 사용하면 문제가 해결 될 수 없다는 것을 알았습니다. 비 블로킹 모드에서는 데이터를 읽지 않으면 read () 메소드가 즉시 반환되기 때문입니다. 우리는 데이터가 언제 도착할 것인지 알지 못하므로 Read () 메소드를 계속 호출하여 다시 시도 할 수 있습니다. 이것은 분명히 CPU 자원의 낭비입니다. 다음으로부터, 우리는 선택기 구성 요소 가이 문제를 해결하기 위해 태어났다는 것을 알 수 있습니다.
Javanio Core 구성 요소
1. 채널
개념
Javanio의 모든 I/O 작업은 스트림 작업이 스트림 객체를 기반으로하는 것처럼 채널 객체를 기반으로하므로 먼저 채널이 무엇인지 이해해야합니다. 다음 내용은 JDK1.8의 문서에서 발췌됩니다.
Achannel은 하나의 Ormory Directive I/OOPERATION, FEXEMPLEREAD ORWRITING에서 수행 할 수있는 AFILE, AnnetworkSocket, Oroprogram 구성 요소에 대한 부록 연결을 나타냅니다.
위의 컨텐츠에서 채널은 파일, 네트워크 소켓 등이 될 수있는 특정 엔터티와의 연결을 나타냅니다. 즉, 채널은 Javanio가 제공하는 브리지로서 프로그램이 운영 체제의 기본 I/O 서비스와 상호 작용할 수 있습니다.
채널은 매우 기본적이고 추상적 인 설명이며, 다른 I/O 서비스와 상호 작용하고, 다른 I/O 운영을 수행하며, 다른 구현을 구현합니다. 따라서 특정 제품에는 Filechannel, Socketchannel 등이 포함됩니다.
채널은 사용될 때 스트림과 유사합니다. 버퍼로 데이터를 읽거나 버퍼의 채널에 데이터를 쓰기 할 수 있습니다.
물론 차이점도 있습니다.이 차이는 주로 다음 두 가지 점에 반영됩니다.
채널은 읽고 쓸 수 있지만 스트림은 일방 통행입니다 (따라서 입력 스트림 및 outputstream으로 나뉩니다)
채널에는 비 블로킹 I/O 모드가 있습니다
성취하다
Javanio에서 가장 일반적으로 사용되는 채널 구현은 다음과 같습니다. 전통적인 I/O 운영 클래스에 하나씩 일치한다는 것을 알 수 있습니다.
Filechannel : 파일을 읽고 쓰십시오
DatagramChannel : UDP 프로토콜 네트워크 통신
Socketchannel : TCP 프로토콜 네트워크 통신
serversocketchannel : TCP 연결을 듣습니다
2. 버퍼
NIO에 사용되는 버퍼는 간단한 바이트 어레이가 아니라 캡슐화 된 버퍼 클래스입니다. 그것이 제공하는 API를 통해 데이터를 유연하게 조작 할 수 있습니다. 자세히 살펴 보겠습니다.
Java Basic 유형에 해당하는 Nio는 바이 테 버퍼, 샤 버퍼, intbuffer 등과 같은 다양한 버퍼 유형을 제공합니다. 차이점은 버퍼의 단위 길이가 읽기 및 쓰기시 (해당 유형 변수의 단위로 읽기 및 쓰기)가 다르다는 것입니다.
버퍼에는 3 가지 매우 중요한 변수가 있습니다. 그들은 버퍼의 작동 메커니즘, 즉
용량 (총 용량)
위치 (포인터의 현재 위치)
한계 (읽기/쓰기 경계 위치)
버퍼의 작동 방법은 C의 문자 어레이와 매우 유사합니다. 유추적으로, 용량은 배열의 총 길이이며, 위치는 문자를 읽고 쓰기위한 첨자 변수이며, 한계는 결말 문자의 위치입니다. 버퍼의 시작시 3 개의 변수의 상황은 다음과 같습니다.
버퍼를 읽고 쓰는 과정에서 위치는 뒤로 이동하며 제한은 위치 이동의 경계입니다. 버퍼에 쓸 때 제한이 용량 크기로 설정되어야하고 버퍼로 읽을 때는 한계가 데이터의 실제 종료 위치로 설정되어야한다고 상상하기 어렵지 않습니다. (참고 : 채널에 버퍼 데이터를 쓰는 것은 버퍼 읽기 작업이며, 채널에서 버퍼로의 데이터를 읽는 것은 버퍼 쓰기 작업입니다)
버퍼에서 작업을 읽고 쓰기 전에 버퍼 클래스에서 제공 한 일부 보조 메소드를 호출하여 주로 다음과 같이 위치 및 제한 값을 올바르게 설정할 수 있습니다.
Flip () : 위치 값으로 한계를 설정 한 다음 버퍼를 읽기 전에 위치를 0으로 설정하십시오.
Rewind () : Just Set Position 0. 일반적으로 버퍼 데이터를 다시 읽기 전에 호출됩니다. 예를 들어, 동일한 버퍼의 데이터를 읽고 여러 채널에 쓸 때 사용됩니다.
CLEAR () : 초기 상태로 돌아가십시오. 즉, 제한은 용량과 동일하고 위치를 0으로 설정합니다. 쓰기 전에 버퍼를 호출하십시오.
compact () : 읽지 않은 데이터 (위치와 제한 사이의 데이터)를 버퍼의 시작으로 이동 하고이 데이터의 끝에서 위치를 다음 위치로 설정하십시오. 실제로, 그러한 데이터 조각을 버퍼에 다시 작성하는 것과 같습니다.
그런 다음 예제를 살펴보고 Filechannel을 사용하여 텍스트 파일을 읽고 쓰고이 예제를 사용하여 채널의 읽을 수 있고 쓰기 쉬운 특성과 버퍼의 기본 사용을 확인하십시오 (Filechannel은 비 블로킹 모드로 설정할 수 없음).
filechannel 채널 = 새로운 RandomAccessFile ( "test.txt", "rw"). getChannel (); channel.Position (channel.size ()); // 파일 포인터를 끝으로 이동 (쓸데) 바이 테버 버터 바이트 버퍼 = 바이트 버퍼를 Alltocate (20); // bufferbytebuffer.put ( "hello,"hello, "hello," World!/n ".getBytes (StandardCharsets.utf_8)); // buffer-> chant charSetDecoder decoder = Standardcharsets.utf_8.newdecoder (); // 모든 데이터를 읽습니다. bytebuffer.clear (); while (channel.read (bytebuffer)! = -1 || bytebuffer.position ()> 0) {bytebuffer.flip (); // decode charbuffer.clear (); decoder.decode (bytebuffer, charbuffer, false); system.out.print (charbuffer.flip ()); bytebuffer ()); 데이터가 남아있을 수 있습니다} channel.close ();이 예에서는 Bytebuffer가 채널 읽기 및 쓰기를위한 데이터 버퍼이며 Charbuffer는 디코딩 된 문자를 저장하는 데 사용됩니다. Clear () 및 Flip ()의 사용은 위에서 언급 한 바와 같이입니다. 마지막 compact () 메소드는 charbuffer의 크기가 디코딩 된 데이터 바이 테 버퍼를 수용하기에 충분히 충분 하더라도이 compact ()도 필수적입니다. 일반적으로 사용되는 중국자의 UTF-8 인코딩은 3 바이트를 차지하기 때문에 중간 절단에서 발생할 확률이 높기 때문입니다. 아래 그림을 참조하십시오.
디코더가 버퍼 끝에서 0xe4를 읽으면 유니 코드에 매핑 할 수 없습니다. Decode () 메소드의 세 번째 매개 변수 인 False는 디코더가 UNMAppable 바이트와 후속 데이터를 추가 데이터로 처리하도록하는 데 사용됩니다. 따라서 Decode () 메소드가 여기서 중지되고 위치는 0xe4 위치로 돌아갑니다. 이러한 방식으로, "medium"이라는 단어로 인코딩 된 첫 번째 바이트는 버퍼에 남겨져 있으며, 전면에 압축되어 올바른 후속 시퀀스 데이터와 함께 스 플라이싱해야합니다. 문자 인코딩과 관련하여 " ANSI, 유니 코드, BMP, UTF 및 기타 인코딩 개념의 설명 " 을 참조하십시오.
예제의 charsetdecoder 인 BTW는 또한 Javanio의 새로운 기능이므로 조금 발견해야합니다. NIO 작업은 버퍼 지향적입니다 (전통적인 I/O는 스트림 지향적입니다).
이 시점에서 채널 및 버퍼의 기본 사용에 대해 배웠습니다. 다음으로 스레드가 여러 채널을 관리하는 중요한 구성 요소에 대해 이야기 할 것입니다.
3. Selector
선택기는 무엇입니까?
선택기는 각 채널의 상태 (또는 이벤트)를 수집하는 데 사용되는 특수 구성 요소입니다. 우리는 먼저 채널을 선택기에 등록하고 우리가 관심있는 이벤트를 설정 한 다음 select () 메소드를 호출하여 이벤트가 발생할 때까지 조용히 기다릴 수 있습니다.
채널에는 다음과 같은 4 가지 이벤트가 있습니다.
수락 : 허용 가능한 연결이 있습니다
연결 : 성공적으로 연결하십시오
읽기 : 읽을 데이터가 있습니다
쓰기 : 데이터를 작성할 수 있습니다
선택기를 사용하는 이유
위에서 언급했듯이 차단 I/O를 사용하는 경우 멀티 스레드 (메모리 폐기물)를 사용해야하며, 비 블로킹 I/O를 사용하는 경우 지속적으로 다시 시도해야합니다 (CPU 소비). 선택기의 출현은이 창피한 문제를 해결합니다. 비 블로킹 모드에서 선택기를 통해 스레드는 준비된 채널에서만 작동하며 맹목적으로 시도 할 필요가 없습니다. 예를 들어, 모든 채널에서 데이터에 도달하지 않으면 읽기 이벤트가 발생하지 않으며 Select () 메소드에 스레드가 중단되어 CPU 리소스를 포기합니다.
사용 방법
아래와 같이, 선택기를 만들고 채널을 등록하십시오.
참고 : 채널을 선택기에 등록하려면 먼저 채널을 비 블로킹 모드로 설정해야합니다. 그렇지 않으면 예외가 발생됩니다.
selector selector = selector.open (); channer.configureBlocking (false); selectionKey = channel.register (selector, selectkey.op_read);
레지스터 () 메소드의 두 번째 매개 변수는 "관심 세트"라고하며, 이는 당신이 우려하는 이벤트 세트입니다. 여러 이벤트에 관심이있는 경우 "Bital 또는 Operator"와 분리하십시오 (예 :
selectionkey.op_read | selectionkey.op_write
이 글쓰기 방법은 익숙하지 않습니다. 비트 작업을 지원하는 프로그래밍 언어로 재생됩니다. 정수 변수를 사용하면 여러 상태를 식별 할 수 있습니다. 어떻게 끝났습니까? 실제로는 매우 간단합니다. 예를 들어, 먼저 사전 정의 된 일부 상수가 있으며 그 값 (이진)은 다음과 같습니다.
1 값의 비트가 모두 비틀 거리기 때문에 비트를 수행 한 후 얻은 값은 모호성이 없으며 변수가 계산되는 변수를 반대로 추론 할 수 있습니다. 판단하는 방법, 그렇습니다. "비트 및"작동입니다. 예를 들어, 이제 0011의 상태 세트 변수 값이 있습니다. 세트에 op_read 상태가 포함되어 있는지 여부를 결정하기 위해 "0011 & op_read"의 값이 1 또는 0인지 확인하면됩니다.
그런 다음 register () 메소드는이 등록에 대한 정보를 포함하는 선택 키 객체를 반환하며 등록 정보를 통해 등록 정보를 수정할 수도 있습니다. 아래의 전체 예에서, select () 이후에 선택키 컬렉션을 얻어 State가있는 채널도 받는다는 것을 알 수 있습니다.
완전한 예
개념과 이론적 인 것들이 설명되었습니다 (실제로, 여기에 글을 쓰고 나서 나는 많은 글을 쓰지 않았다는 것을 알았습니다. 완전한 예를 살펴 보겠습니다.
이 예제는 Javanio를 사용하여 단일 스레드 서버를 구현합니다. 기능은 매우 간단합니다. 클라이언트 연결을 듣습니다. 연결이 설정되면 클라이언트의 메시지를 읽고 클라이언트에게 메시지에 응답합니다.
메시지의 끝을 식별하기 위해 문자 '/0'(값 0의 바이트)을 사용한다는 점에 유의해야합니다.
단일 스레드 서버
public class nioserver {public static void main (string [] args)은 ioexception {// selectorselector selector = selector.open을 만듭니다. Listenchannel.bind (New InetSocketAddress (9999)); listenchannel.configureBlocking (false); // 선택기에 등록 (수락 이벤트 듣기) listenchannel.register (selector, selectioney.op_accept); // 버퍼 생물 바이 테버 버퍼 생성 = bytebuffer.altocate (100); while (true) {selector.select (); // 이벤트가 청취 될 때까지 블록 iterator <selectionkey> keyiter = selector.selector.selectedkeys (). iterator (); iterator ()를 통해 선택한 채널 이벤트에 액세스하는 동안 (keyiter.hasnext ()) {cheelkey key = key); = ((serversocketchannel) key.channel ()). accept (); channer.configureBlocking (false); channer.register (selector, selectionkey.op_read); "" + channel.getEremoteadDress () + "!"); 스트림의 끝은 TCP 연결이 연결이 끊어 졌음을 나타내므로 // 채널을 닫거나 읽기 이벤트를 듣는 것이 필요합니다. // 그렇지 않으면 (((socketchannel) key.channel ()). 읽기 (buffer) == -1) {key.channel (). close (); while (buffer.hasremaining ()) {byte b = buffer.get (); if (b == 0) {// /0system.out.println () 클라이언트 메시지의 끝에서 (// 응답 클라이언트 buffer.clear (); buffer.put ( "hello, client!/0". {((socketchannel) key.channel ()). 쓰기 (buffer);}} else {system.out.print ((char) b);}}} // 처리 된 이벤트의 경우 keyiter.remove ();}}}}을 수동으로 제거해야합니다.고객
이 클라이언트는 순전히 테스트에 사용됩니다. 덜 어렵게 만들기 위해 전통적인 작문 방법을 사용하고 코드는 매우 짧습니다.
테스트에서 더 엄격 해야하는 경우 서버의 응답 시간을 계산하기 위해 많은 클라이언트를 동시에 실행하고 연결이 설정된 직후에 데이터를 보내지 않아 서버에서 비 블로킹 I/O의 장점을 완전히 재생할 수 있습니다.
public class client {public static void main (string [] args)은 예외 {socket socket = new Socket ( "localhost", 9999); inputStream is = socket.getInputStream (); outputStream os = socket.getOutputStream (); // 서버로 보낸다 ( "hello, server!/0"); b; while ((b = is.read ())! = 0) {system.out.print ((char) b);} system.out.println (); socket.close ();}}요약
위의 것은 Java의 NIO 코어 구성 요소에 대한 빠른 이해에 관한 것입니다. 모든 사람에게 도움이되기를 바랍니다. 관심있는 친구는이 웹 사이트의 다른 관련 내용을 계속 참조 할 수 있습니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다!