입출력을 설명할 때,비차단 및 비동기라는 용어는 종종 같은 의미로 사용되지만,이들 사이에는 상당한 차이가 있다. 이 문서에서는 자바에서 비 차단 및 비동기 소켓 입출력 작업 사이의 이론 및 실제 차이점을 설명합니다.
소켓은 양방향 통신을 수행하는 엔드포인트이다. 자바 소켓은 운영 체제의 해당 기능에 대한 어댑터입니다. 소켓 통신(유닉스,리눅스,맥 오에스 텐,비에스디,솔라리스,엑스 등))는 버클리 소켓에 의해 수행됩니다. 윈도우 소켓 통신은 윈도우 프로그래밍 모델을 준수하기 위해 추가 기능을 가진 버클리 소켓을 기반으로 윈속에 의해 수행된다.이 문서는 기계 번역되었으므로 어휘,구문 또는 문법에서 오류가 있을 수 있습니다
차단된 스레드—실행을 계속하기 전에 일부 조건을 기다리는 스레드입니다.
차단-반환하기 전에 요청된 작업이 수행될 때까지 호출을 대기하는 소켓의 속성입니다.
비 차단-요청 된 작업을 알 수 없는 지연 없이 완료할 수 없습니다 감지 될 때 지연 없이 반환 하는 호출을 일으키는 소켓의 속성입니다.
동기식 입출력 작업—입출력 작업이 완료될 때까지 요청 스레드가 차단되도록 하는 입출력 작업입니다.
비동기 입출력 작업—요청 스레드가 차단되지 않는 입출력 작업이므로 스레드와 입출력 작업이 동시에 실행될 수 있습니다.
:
- 비블로킹-호출을 지연 없이 반환하는 소켓의 속성
- 비동기 입출력—요청 스레드와 동시에 실행되는 입출력 작업(읽기 또는 쓰기)에 대한 속성
입출력 모델
다음 입출력 모델이 가장 일반적입니다.:
- 블로킹 입출력 모델
- 비블로킹 입출력 모델
- 신호 구동 입출력 모델
- 비동기식 입출력 모델
블로킹 입출력 모델
차단 입출력 모델에서 애플리케이션은 데이터가 커널에서 수신되고 커널 공간에서 사용자 공간으로 복사될 때까지 차단 시스템 호출을 수행합니다.
프로:
단점을 구현하는 가장 간단한 입출력 모델:
비블로킹 입출력 모델
비블로킹 입출력 모델에서 응용 프로그램은 두 응답 중 하나를 즉시 반환하는 시스템 호출을 수행합니다:
- 입출력 작업을 즉시 완료할 수 있는 경우 데이터가 반환됩니다
- 입출력 작업을 즉시 완료할 수 없는 경우 입출력 작업이 차단되거나 장치를 일시적으로 사용할 수 없음을 나타내는 오류 코드가 반환됩니다
입출력 작업을 완료하려면 응용 프로그램이 종료될 때까지 통화 중 대기(시스템 호출 반복)해야 합니다.
프로:
단점:
입출력 멀티플렉싱 모델
입출력 멀티플렉싱 모델(비 출력 멀티플렉싱 모델이라고도 함)에서 커널의 데이터 가용성과 응용 프로그램의 데이터 읽기 사이에 간격이 있을 수 있기 때문에 이 모델은 입출력 대기 시간을 도입할 수 있습니다.-차단 알림과 입출력 모델을 차단),응용 프로그램은 많은 설명자에 대한 활동을 모니터링하기 시작 차단 선택 시스템 호출을합니다. 각 설명자에 대해 특정 입출력 작업(연결,읽기 또는 쓰기,오류 발생 등)에 대한 준비 상태 알림을 요청할 수 있습니다.). 시스템 선택 호출에서 하나 이상의 설명자가 준비되었음을 반환하면 응용 프로그램은 비 차단 호출을 수행하고 커널 공간의 데이터를 사용자 공간으로 복사합니다.
프로:
단점에서 여러 설명자에 대한 입출력 작업을 수행할 수 있습니다.:
- 일부 운영 체제가 이 모델을 효율적으로 지원하는 것은 아닙니다.
신호 구동 입출력 모델
신호 구동 입출력 모델에서 애플리케이션은 비차단 호출을 하고 신호 처리기를 등록합니다. 설명자가 입출력 작업을 수행할 준비가 되면 애플리케이션에 대한 신호가 생성됩니다. 그런 다음 신호 처리기는 커널 공간의 데이터를 사용자 공간으로 복사합니다.
프로:
- 응용 프로그램이 차단되지 않음
- 신호는 좋은 성능을 제공 할 수 있습니다
단점:
- 일부 운영 체제가 신호를 지원하는 것은 아닙니다
비동기 입출력 모델
비동기 입출력 모델(겹쳐진 입출력 모델이라고도 함)에서 응용 프로그램은 비차단 호출을 수행하고 커널에서 백그라운드 작업을 시작합니다. 작업이 완료되면(데이터가 커널에서 수신되고 커널 공간에서 사용자 공간으로 복사됨)입출력 작업을 완료하기 위해 완료 콜백이 생성됩니다.
비동기식 입출력 모델과 신호 구동 입출력 모델의 차이점은 신호 구동 입출력의 경우 커널은 입출력 작업을 시작할 수 있는 시기를 응용 프로그램에 알려주지만 비동기식 입출력 모델의 경우 커널은 입출력 작업이 완료되는 시기를 응용 프로그램에 알려줍니다.
장점:
- 응용 프로그램이 차단되지 않은
- 이 모델을 제공할 수 있는 최고의 성능을
다:
- 가장 복잡한 I/O 모델을 구현하는
- 지 않는 모든 운영 체제가 지원이 이 모델을 효율적으로
Java I/O Api
Java IO API 를 기반으로는 스트림(InputStream,OutputStream)을 나타내는 차단 하나방향 데이터 흐름입니다.
채널 클래스는 입출력 작업(읽기 또는 쓰기)을 수행할 수 있는 엔티티(하드웨어 장치,파일,소켓,소프트웨어 컴포넌트 등)에 대한 연결을 나타냅니다.
단방향 스트림과 비교하여,채널은 양방향이다.
버퍼 클래스는 데이터를 읽고 쓰는 추가 메서드가 있는 고정 크기 데이터 컨테이너입니다. 모든 채널 데이터는 버퍼를 통해 처리되지만 직접 처리되지는 않습니다:채널로 전송되는 모든 데이터는 버퍼에 기록되고 채널에서 수신되는 모든 데이터는 버퍼로 읽혀집니다.
바이트 지향 스트림과 비교하여 채널은 블록 지향적입니다. 바이트 지향 입출력은 더 간단하지만 일부 입출력 엔티티의 경우 다소 느릴 수 있습니다. 블록 지향 입출력은 훨씬 빨라질 수 있지만 더 복잡합니다.
선택기 클래스를 사용하면 등록된 여러 선택 가능 채널 개체의 이벤트를 단일 호출로 구독할 수 있습니다. 이벤트가 도착하면 선택기 개체는 해당 이벤트 처리기에 이벤트를 전달합니다.비동기식 입출력 작업(연결,읽기 또는 쓰기,오류 처리)을 지원하는 비동기식 채널(비동기식 서채널,비동기식 서채널 등)을 기반으로 합니다.
비동기 채널은 비동기 입출력 동작을 제어하는 두 가지 메커니즘을 제공합니다. 첫 번째 메커니즘은 자바를 반환하는 것입니다.유틸리티동시.보류 중인 작업을 모델링하고 상태를 쿼리하고 결과를 얻는 데 사용할 수 있는 향후 개체입니다. 두 번째 메커니즘은 작업에 자바를 전달하는 것입니다.니오채널.이 개체는 작업이 완료되거나 실패한 후에 실행되는 처리기 메서드를 정의합니다. 두 메커니즘에 대해 제공된 애피타이저는 동일합니다.
비동기 채널은 플랫폼-독립적으로 비동기 작업을 수행하는 표준 방법을 제공합니다. 그러나 자바 소켓이 운영 체제의 기본 비동기 기능을 악용할 수 있는 양은 해당 플랫폼에 대한 지원에 따라 달라집니다.
소켓 에코 서버
위에서 언급한 대부분의 입출력 모델은 에코 서버와 클라이언트에서 구현된다. 에코 서버 및 클라이언트는 다음 알고리즘에 의해 작동합니다:클라이언트는 콘솔에서 입력 문자열을 읽고 해당 소켓에서 서버 소켓으로 바이트를 보냅니다.
서버는 해당 소켓에서 바이트를 수신하여 클라이언트 소켓으로 다시 보냅니다.
클라이언트는 소켓에서 바이트를 수신하고 콘솔에 에코 문자열을 씁니다
클라이언트가 보낸 바이트 수와 동일한 수를 받으면 서버
에서 연결이 끊어집니다.
서버가 특수 문자열을 수신하면 수신 대기 중지
여기서 문자열과 바이트 간의 변환은 명시적으로 수행됩니다.
또한 에코 서버에 대한 단순화 된 코드 만 제공됩니다. 에코 서버 및 클라이언트에 대한 전체 코드에 대한 링크는 결론에서 제공됩니다.
차단 입출력 서버
다음 예에서는 차단 입출력 모델을 에코 서버에 구현합니다.
서버 소켓.연결이 수락 될 때까지 메소드 블록을 수락합니다. 입력 스트림.입력 데이터를 사용할 수 있거나 클라이언트의 연결이 끊어 질 때까지 메서드 블록을 읽습니다. 출력 스트림.모든 출력 데이터가 기록 될 때까지 메소드 블록을 작성하십시오.
public class IoEchoServer { public static void main(String args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7000); while (active) {
Socket socket = serverSocket.accept(); // blocking InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream(); int read;
byte bytes = new byte;
while ((read = is.read(bytes)) != -1) { // blocking
os.write(bytes, 0, read); // blocking
} socket.close();
} serverSocket.close();
}
}
차단 니오 에코 서버
다음 예에서,차단 입출력 모델은 에코 서버에 구현된다.
서버소켓채널 및 소켓 채널 개체는 기본적으로 차단 모드로 구성됩니다. 서버 소켓 채널.수락 메서드는 연결을 수락하면 소켓 채널 개체를 차단하고 반환합니다. 서버 소켓.입력 데이터를 사용할 수 있거나 클라이언트의 연결이 끊어 질 때까지 메서드 블록을 읽습니다. 서버 소켓.모든 출력 데이터가 기록 될 때까지 메소드 블록을 작성하십시오.
public class NioBlockingEchoServer { public static void main(String args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 7000)); while (active) {
SocketChannel socketChannel = serverSocketChannel.accept(); // blocking ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
buffer.clear();
int read = socketChannel.read(buffer); // blocking
if (read < 0) {
break;
} buffer.flip();
socketChannel.write(buffer); // blocking
} socketChannel.close();
} serverSocketChannel.close();
}
}
비블로킹 니오 에코 서버
다음 예에서,비블로킹 입출력 모델은 에코 서버에서 구현된다.
서버소켓채널 및 소켓 채널 개체는 비차단 모드로 명시적으로 구성됩니다. 서버 소켓 채널.아직 연결이 수락되지 않았거나 그렇지 않은 경우 소켓 채널 개체가 차단되지 않고 널을 반환합니다. 서버 소켓.읽기가 차단되지 않고 사용할 수 있는 데이터가 없거나 그렇지 않은 경우 양수 바이트를 읽으면 0 을 반환합니다. 서버 소켓.소켓의 출력 버퍼에 여유 공간이 있으면 쓰기 방법이 차단되지 않습니다.
public class NioNonBlockingEchoServer { public static void main(String args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(7000)); while (active) {
SocketChannel socketChannel = serverSocketChannel.accept(); // non-blocking
if (socketChannel != null) {
socketChannel.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
buffer.clear();
int read = socketChannel.read(buffer); // non-blocking
if (read < 0) {
break;
} buffer.flip();
socketChannel.write(buffer); // can be non-blocking
} socketChannel.close();
}
} serverSocketChannel.close();
}
}
다중화 니오 에코 서버
다음 예에서,멀티플렉싱 입출력 모델은 에코 서버에 구현된다.
초기화하는 동안 비차단 모드로 구성된 여러 서버채널 개체가 선택키를 사용하여 동일한 선택기 개체에 등록됩니다.연결 수락 이벤트가 흥미 롭다는 것을 지정하는 인수.
메인 루프에서,선택기.등록된 이벤트 중 적어도 하나가 발생할 때까지 메서드 블록을 선택합니다. 그런 다음 선택기.선택한 키 메서드는 이벤트가 발생한 선택 키 개체 집합을 반환합니다. 선택 키 개체를 반복하면 입출력 이벤트(연결,수락,읽기,쓰기)가 발생하고 해당 이벤트와 연결된 소켓 개체(서버 소켓 채널,소켓 채널)를 확인할 수 있습니다.
채널이 일부 작동을 위해 준비되었다는 선택 키의 표시는 보장이 아닌 힌트입니다.
public class NioMultiplexingEchoServer { public static void main(String args) throws IOException {
final int ports = 8;
ServerSocketChannel serverSocketChannels = new ServerSocketChannel; Selector selector = Selector.open(); for (int p = 0; p < ports; p++) {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannels = serverSocketChannel;
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress("localhost", 7000 + p)); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} while (active) {
selector.select(); // blocking Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
while (keysIterator.hasNext()) {
SelectionKey key = keysIterator.next(); if (key.isAcceptable()) {
accept(selector, key);
}
if (key.isReadable()) {
keysIterator.remove();
read(selector, key);
}
if (key.isWritable()) {
keysIterator.remove();
write(key);
}
}
} for (ServerSocketChannel serverSocketChannel : serverSocketChannels) {
serverSocketChannel.close();
}
}
}
선택 키 개체가 연결 수락 이벤트가 발생했음을 나타내면 서버 소켓 채널이 만들어집니다.연결을 수락하기 위해 통화를 수락합니다(비 차단 일 수 있음). 그런 다음 새 소켓 채널 개체가 비 차단 모드로 구성되고 선택 키가 있는 동일한 선택기 개체에 등록됩니다.이제 읽기 이벤트가 흥미 롭다는 것을 지정합니다.
private static void accept(Selector selector, SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept(); // can be non-blocking
if (socketChannel != null) {
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
선택 키 개체가 읽기 이벤트가 발생했음을 나타내면 소켓채널이 됩니다.읽기 호출(비 차단 일 수 있음)은 소켓 채널 개체에서 새 바이트비퍼 개체로 데이터를 읽습니다. 그런 다음 소켓 채널 개체가 선택 키와 동일한 선택기 개체에 등록됩니다.이제 쓰기의 이벤트가 흥미로운 것을 지정하는 인수를 쓰기. 또한 이 바이트 버퍼 개체는 첨부 파일로 등록하는 동안 사용됩니다.
private static void read(Selector selector, SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer); // can be non-blocking buffer.flip();
socketChannel.register(selector, SelectionKey.OP_WRITE, buffer);
}
선택키 객체가 쓰기 이벤트가 발생했음을 나타내면 소켓 채널이 만들어집니다.호출(비차단일 수 있음)을 작성하여 선택 키에서 추출된 바이트비퍼 개체에서 소켓 채널 개체에 데이터를 작성합니다.부착 방법. 그 후,소켓채널.통화 닫기 연결을 닫습니다.
private static void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); socketChannel.write(buffer); // can be non-blocking
socketChannel.close();
}
모든 읽기 또는 쓰기 후 선택키 개체의 재사용을 방지 하기 위해 선택키 개체 집합에서 제거 됩니다. 그러나 연결 승인을 위한 선택 키 개체는 다음 유사한 작업을 수행할 수 있도록 제거되지 않습니다.다음 예제에서는 비동기 입출력 모델을 에코 서버에 구현합니다. 여기서 비동기성서버소켓 채널,비동기성소켓 채널 클래스는 완료 핸들러 메커니즘과 함께 사용됩니다.
비동기식 서버소켓채널.수락 메서드는 비동기 연결 수락 작업을 시작합니다.
public class Nio2CompletionHandlerEchoServer { public static void main(String args) throws IOException {
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(7000)); AcceptCompletionHandler acceptCompletionHandler = new AcceptCompletionHandler(serverSocketChannel);
serverSocketChannel.accept(null, acceptCompletionHandler); System.in.read();
}
}
연결이 수락되거나 작업이 실패하면 수락 완료 핸들러 클래스가 호출됩니다.이 문제를 해결하려면 다음 단계를 수행하십시오. 비동기식 읽기 작업을 비동기식 채널 개체에서 새 바이트 버퍼 개체로 시작합니다.
class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Void> { private final AsynchronousServerSocketChannel serverSocketChannel; AcceptCompletionHandler(AsynchronousServerSocketChannel serverSocketChannel) {
this.serverSocketChannel = serverSocketChannel;
} @Override
public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
serverSocketChannel.accept(null, this); // non-blocking ByteBuffer buffer = ByteBuffer.allocate(1024);
ReadCompletionHandler readCompletionHandler = new ReadCompletionHandler(socketChannel, buffer);
socketChannel.read(buffer, null, readCompletionHandler); // non-blocking
} @Override
public void failed(Throwable t, Void attachment) {
// exception handling
}
}
읽기 작업이 완료되거나 실패하면 읽기 완료 핸들러 클래스가 호출됩니다.이 문제를 해결하려면 다음 단계를 수행하십시오. 비동기 쓰기 작업을 시작합니다.
class ReadCompletionHandler implements CompletionHandler<Integer, Void> { private final AsynchronousSocketChannel socketChannel;
private final ByteBuffer buffer; ReadCompletionHandler(AsynchronousSocketChannel socketChannel, ByteBuffer buffer) {
this.socketChannel = socketChannel;
this.buffer = buffer;
} @Override
public void completed(Integer bytesRead, Void attachment) {
WriteCompletionHandler writeCompletionHandler = new WriteCompletionHandler(socketChannel);
buffer.flip();
socketChannel.write(buffer, null, writeCompletionHandler); // non-blocking
} @Override
public void failed(Throwable t, Void attachment) {
// exception handling
}
}
쓰기 작업이 완료되거나 실패하면 쓰기 완료 핸들러 클래스가 호출됩니다.닫기 방법은 연결을 닫습니다.
class WriteCompletionHandler implements CompletionHandler<Integer, Void> { private final AsynchronousSocketChannel socketChannel; WriteCompletionHandler(AsynchronousSocketChannel socketChannel) {
this.socketChannel = socketChannel;
} @Override
public void completed(Integer bytesWritten, Void attachment) {
try {
socketChannel.close();
} catch (IOException e) {
// exception handling
}
} @Override
public void failed(Throwable t, Void attachment) {
// exception handling
}
}
이 예제에서는 비동기 입출력 작업이 첨부 파일 없이 수행됩니다.
결론
소켓 통신에 대한 입출력 모델의 선택은 트래픽의 매개 변수에 따라 달라집니다. 입출력 요청이 길고 드문 경우 비동기 입출력은 일반적으로 좋은 선택입니다. 그러나 입출력 요청이 짧고 빠른 경우 커널 호출 처리 오버헤드로 인해 동기식 입출력이 훨씬 향상될 수 있습니다.
자바는 다른 운영 체제에서 소켓 입출력을 수행하는 표준 방법을 제공함에도 불구하고 실제 성능은 구현에 따라 크게 달라질 수 있습니다. 그것은 댄 케겔의 잘 알려진 기사 이러한 차이를 연구 시작할 수 있습니다.
전체 코드 예제는 깃허브 리포지토리에서 사용할 수 있습니다.