Java NIO의 채널을 자세히 설명

채널은 java.nio의 두 번째 주요 혁신입니다. 확장도 아니고 개선도 아니지만 I / O 서비스에 대한 직접 연결을 제공하는 새롭고 훌륭한 Java I / O 예제입니다. 채널은 바이트 버퍼와 채널의 다른 쪽 엔티티 (일반적으로 파일 또는 소켓)간에 데이터를 효율적으로 전송하는 데 사용됩니다.

채널 소개

채널은 I / O 서비스에 액세스하기위한 통로입니다. I / O는 파일 I / O와 스트림 I / O의 두 가지 범주로 나눌 수 있습니다. 따라서 두 가지 유형의 채널, 즉 파일 채널과 소켓 채널이 있다는 것은 놀라운 일이 아닙니다. API에 FileChannel 클래스와 3 개의 소켓 채널 클래스 (SocketChannel, ServerSocketChannel 및 DatagramChannel)가 있음을 알 수 있습니다.

채널은 여러 가지 방법으로 만들 수 있습니다. 소켓 채널에는 새 소켓 채널을 직접 생성 할 수있는 팩토리 메서드가 있습니다. 그러나 FileChannel 객체는 열린 RandomAccessFile, FileInputStream 또는 FileOutputStream 객체에 대해 getChannel () 메서드를 호출해야만 얻을 수 있습니다. FileChannel 개체를 직접 만들 수 없습니다.

먼저 FileChannel의 사용법을 살펴 보겠습니다.

 // 创建文件输出字节流
 FileOutputStream fos = new FileOutputStream("data.txt");
 //得到文件通道
 FileChannel fc = fos.getChannel();
 //往通道写入ByteBuffer
 fc.write(ByteBuffer.wrap("Some text ".getBytes()));
 //关闭流
 fos.close();

 //随机访问文件
 RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
 //得到文件通道
 fc = raf.getChannel();
 //设置通道的文件位置 为末尾
 fc.position(fc.size()); 
 //往通道写入ByteBuffer
 fc.write(ByteBuffer.wrap("Some more".getBytes()));
 //关闭
 raf.close();

 //创建文件输入流
 FileInputStream fs = new FileInputStream("data.txt");
 //得到文件通道
 fc = fs.getChannel();
 //分配ByteBuffer空间大小
 ByteBuffer buff = ByteBuffer.allocate(BSIZE);
 //从通道中读取ByteBuffer
 fc.read(buff);
 //调用此方法为一系列通道写入或相对获取 操作做好准备
 buff.flip();
 //从ByteBuffer从依次读取字节并打印
 while (buff.hasRemaining()){
  System.out.print((char) buff.get());
 }
 fs.close();

SocketChannel을 다시 살펴 보겠습니다.

 SocketChannel sc = SocketChannel.open( );
 sc.connect (new InetSocketAddress ("somehost", someport)); 
 ServerSocketChannel ssc = ServerSocketChannel.open( ); 
 ssc.socket( ).bind (new InetSocketAddress (somelocalport)); 
 DatagramChannel dc = DatagramChannel.open( );

SocketChannel을 non-blocking 모드로 설정할 수 있으며 설정 후 비동기 모드에서 connect (), read (), write ()를 호출 할 수 있습니다. SocketChannel이 non-blocking 모드이고 이때 connect ()를 호출하면 연결이 설정되기 전에 메서드가 반환 될 수 있습니다. 연결이 설정되었는지 확인하기 위해 finishConnect () 메서드를 호출 할 수 있습니다. 이렇게 :

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){
 //wait, or do something else...
}

서버 측 사용은 동시에 많은 소켓 채널을 더 쉽게 관리 할 수 ​​있기 때문에 비 차단 소켓 채널을 고려하는 경우가 많습니다. 그러나 클라이언트 측에서 하나 이상의 비 차단 소켓 채널을 사용하는 것도 유익합니다. 예를 들어 비 차단 소켓 채널을 사용하면 GUI 프로그램이 사용자 요청에 집중하고 동시에 하나 이상의 서버와 세션을 유지할 수 있습니다. . 많은 프로그램에서 비 차단 모드가 유용합니다.

finishConnect () 메서드를 호출하여 연결 프로세스를 완료합니다.이 메서드는 언제든지 안전하게 호출 할 수 있습니다. 비 차단 모드의 SocketChannel 객체에서 finishConnect () 메서드가 호출되면 다음 상황 중 하나가 발생할 수 있습니다.

  • connect () 메서드는 아직 호출되지 않았습니다. NoConnectionPendingException이 생성됩니다.
  • 연결 설정 프로세스가 진행 중이며 아직 완료되지 않았습니다. 그러면 아무 일도 일어나지 않고 finishConnect () 메서드가 즉시 false를 반환합니다.
  • 비 차단 모드에서 connect () 메서드를 호출하면 SocketChannel이 다시 차단 모드로 전환됩니다. 그런 다음 필요한 경우 연결이 설정 될 때까지 호출 스레드가 차단되고 finishConnect () 메서드가 true 값을 반환합니다. connect ()에 대한 첫 번째 호출 또는 finishConnect ()에 대한 마지막 호출 후 연결 설정 프로세스가 완료되었습니다. 그런 다음 SocketChannel 객체의 내부 상태가 연결된 상태로 업데이트되고 finishConnect () 메서드가 true 값을 반환 한 다음 SocketChannel 객체를 사용하여 데이터를 전송할 수 있습니다.
  • 연결이 설정되었습니다. 그러면 아무 일도 일어나지 않고 finishConnect () 메서드가 참 값을 반환합니다.

소켓 채널은 스레드로부터 안전합니다. 동시 액세스 중에는 액세스를 시작하는 여러 스레드를 보호하기 위해 특별한 조치가 필요하지 않지만 한 번에 하나의 읽기 작업과 하나의 쓰기 작업 만 진행됩니다. 소켓은 패킷 지향이 아니라 스트림 지향임을 기억하십시오. 전송 된 바이트가 순서대로 도착하도록 보장 할 수 있지만 바이트 그룹화를 유지한다고 약속 할 수는 없습니다. 특정 송신자는 소켓에 20 바이트를 쓸 수 있고 수신자는 read () 메서드를 호출 할 때 3 바이트 만받습니다. 나머지 17 바이트는 여전히 전송 중입니다. 이러한 이유로 여러 비 협조 스레드가 스트림 소켓의 동일한 측면을 공유하도록하는 것은 결코 좋은 설계 선택이 아닙니다.

마지막으로 DatagramChannel을 살펴보십시오.

마지막 소켓 채널은 DatagramChannel입니다. SocketChannel이 Socket에 해당하고 ServerSocketChannel이 ServerSocket에 해당하는 것처럼 각 DatagramChannel 객체에도 연결된 DatagramSocket 객체가 있습니다. 그러나 원래 이름 지정 패턴은 여기에 적용되지 않습니다. "DatagramSocketChannel"이 약간 어색해 보이므로 간결한 이름 "DatagramChannel"이 채택됩니다.

SocketChannel이 TCP / IP와 같은 연결 지향 스트리밍 프로토콜을 시뮬레이션하는 것처럼 DatagramChannel은 UDP / IP와 같은 패킷 지향 비 연결 프로토콜을 시뮬레이션합니다.

DatagramChannel을 생성하는 패턴은 다른 소켓 채널을 생성하는 것과 동일합니다. 새 인스턴스를 생성하려면 static open () 메서드를 호출합니다. 새 DatagramChannel에는 socket () 메서드를 호출하여 얻을 수있는 피어 DatagramSocket 개체가 있습니다. DatagramChannel 개체는 서버 (수신기) 또는 클라이언트 (보낸 사람)의 역할을 할 수 있습니다. 새로 생성 된 채널이 모니터링을 담당하게하려면 먼저 채널을 포트 또는 주소 / 포트 조합에 바인딩해야합니다. DatagramChannel 바인딩은 일반 DatagramSocket 바인딩과 다르지 않습니다. 둘 다 피어 소켓 객체에 API를 위임하여 구현됩니다.

 DatagramChannel channel = DatagramChannel.open( );
 DatagramSocket socket = channel.socket( ); 
 socket.bind (new InetSocketAddress (portNumber));

DatagramChannel은 연결이 없습니다. 각 데이터 그램은 고유 한 대상 주소와 다른 데이터 그램에 의존하지 않는 데이터 페이로드가있는 독립된 엔터티입니다. 스트림 지향 소켓과 달리 DatagramChannel은 별도의 데이터 그램을 다른 대상 주소로 보낼 수 있습니다. 마찬가지로 DatagramChannel 개체는 모든 주소에서 데이터 패킷을 수신 할 수도 있습니다. 도착하는 각 데이터 그램에는 출처 (원본 주소)에 대한 정보가 포함되어 있습니다.

바인딩되지 않은 DatagramChannel은 여전히 ​​데이터 패킷을 수신 할 수 있습니다. 기본 소켓이 생성되면 동적으로 생성 된 포트 번호가 할당됩니다. 바인딩 동작을 사용하려면 채널과 연결된 포트가 특정 값으로 설정되어야합니다 (이 프로세스에는 보안 검사 또는 기타 확인이 포함될 수 있음). 채널이 바인딩되었는지 여부에 관계없이 전송 된 모든 패킷에는 DatagramChannel의 소스 주소 (포트 번호 포함)가 포함됩니다. Unbound DatagramChannel은 해당 포트로 전송 된 패킷, 일반적으로 채널 앞뒤로 전송되는 패킷을 수신 할 수 있습니다. 바인딩 된 채널은 바인딩 된 잘 알려진 포트로 전송 된 패킷을 수신합니다. 실제 데이터 송수신은 send () 및 receive () 메소드를 통해 이루어집니다.

참고 : 제공 한 ByteBuffer에 수신중인 데이터 패킷을 저장할 충분한 여유 공간이없는 경우 채워지지 않은 바이트는 자동으로 삭제됩니다.

분산 / 수집

채널은 분산 / 수집 (벡터 I / O라고도 함)이라는 중요한 새 기능을 제공합니다. 여러 버퍼에서 간단한 I / O 작업을 실현하는 것을 의미합니다. 쓰기 작업의 경우 데이터는 여러 버퍼에서 순차적으로 추출되고 (수집이라고 함) 채널을 따라 전송됩니다. 버퍼 자체에는 수집 할 수있는 기능이 필요하지 않습니다 (일반적으로이 기능이 없습니다). 수집 프로세스의 효과는 데이터를 보내기 전에 모든 버퍼의 내용이 연결되어 큰 버퍼에 저장되는 것과 같습니다. 읽기 작업의 경우 채널에서 읽은 데이터는 여러 버퍼에 순차적으로 분산 (분산이라고 함)되어 채널의 데이터 또는 버퍼의 최대 공간이 사용될 때까지 각 버퍼를 채 웁니다.

Scatter / gather는 전송 된 데이터를 별도로 처리해야하는 상황에서 자주 사용됩니다. 예를 들어, 메시지 헤더와 메시지 본문으로 구성된 메시지를 전송할 때 메시지 본문과 메시지 헤더를 서로 다른 버퍼로 분산시킬 수 있습니다. 메시지 헤더 및 메시지 본문을 편리하게 처리 할 수 ​​있습니다.

분산 읽기는 데이터를 한 채널에서 여러 버퍼로 읽는 것을 의미합니다. 다음 그림에 설명되어 있습니다.

코드 예는 다음과 같습니다.

ByteBuffer header = ByteBuffer.allocateDirect (10); 
ByteBuffer body = ByteBuffer.allocateDirect (80); 
ByteBuffer [] buffers = { header, body }; 
int bytesRead = channel.read (buffers);

쓰기 수집은 여러 버퍼의 데이터를 동일한 채널에 쓰는 것을 의미합니다. 다음 그림에 설명되어 있습니다.

코드 예는 다음과 같습니다.

 ByteBuffer header = ByteBuffer.allocateDirect (10); 
 ByteBuffer body = ByteBuffer.allocateDirect (80); 
 ByteBuffer [] buffers = { header, body }; 
 channel.write(bufferArray);

적절하게 사용하면 Scatter / Gather는 매우 강력한 도구가 될 수 있습니다. 이를 통해 운영 체제가 어려운 작업을 완료하도록 맡길 수 있습니다. 읽은 데이터를 여러 버킷으로 분리하거나 다른 데이터 블록을 전체로 병합합니다. 운영 체제가 이러한 유형의 작업을 수행하도록 고도로 최적화 되었기 때문에 이것은 엄청난 성과입니다. 데이터를 앞뒤로 이동하는 작업을 절약하고 버퍼 복사를 방지하고 작성 및 디버그에 필요한 코드 양을 줄입니다. 기본적으로 데이터 컨테이너 참조를 제공하여 데이터를 결합하므로 서로 다른 조합에 따라 여러 버퍼 배열 참조를 작성하고 다양한 데이터 블록을 서로 다른 방식으로 결합 할 수 있습니다. 다음 예는이 점을 잘 보여줍니다.

public class GatheringTest {
 private static final String DEMOGRAPHIC = "output.txt";
 public static void main (String [] argv) throws Exception {
  int reps = 10;
  if (argv.length > 0) {
   reps = Integer.parseInt(argv[0]);
  }
  FileOutputStream fos = new FileOutputStream(DEMOGRAPHIC);
  GatheringByteChannel gatherChannel = fos.getChannel();

  ByteBuffer[] bs = utterBS(reps);

  while (gatherChannel.write(bs) > 0) {
   // 不做操作,让通道把数据输出到文件写完
  }
  System.out.println("Mindshare paradigms synergized to " + DEMOGRAPHIC);
  fos.close();
 }
 private static String [] col1 = { "Aggregate", "Enable", "Leverage",
          "Facilitate", "Synergize", "Repurpose",
          "Strategize", "Reinvent", "Harness"
         };

 private static String [] col2 = { "cross-platform", "best-of-breed", "frictionless",
          "ubiquitous", "extensible", "compelling",
          "mission-critical", "collaborative", "integrated"
         };

 private static String [] col3 = { "methodologies", "infomediaries", "platforms", "schemas", "mindshare", "paradigms", "functionalities", "web services", "infrastructures" };

 private static String newline = System.getProperty ("line.separator");


 private static ByteBuffer [] utterBS (int howMany) throws Exception {
  List list = new LinkedList();
  for (int i = 0; i < howMany; i++) {
   list.add(pickRandom(col1, " "));
   list.add(pickRandom(col2, " "));
   list.add(pickRandom(col3, newline));
  }
  ByteBuffer[] bufs = new ByteBuffer[list.size()];
  list.toArray(bufs);
  return (bufs);
 }
 private static Random rand = new Random( );


 /**
  * 随机生成字符
  * @param strings
  * @param suffix
  * @return
  * @throws Exception
  */
 private static ByteBuffer pickRandom (String [] strings, String suffix) throws Exception {
  String string = strings [rand.nextInt (strings.length)];
  int total = string.length() + suffix.length( );
  ByteBuffer buf = ByteBuffer.allocate (total);
  buf.put (string.getBytes ("US-ASCII"));
  buf.put (suffix.getBytes ("US-ASCII"));
  buf.flip( );
  return (buf);
 }
}

출력은 다음과 같습니다.

통합 웹 서비스 재창조
동급 최강의 플랫폼 통합
마찰없는 플랫폼 활용
확장 가능한 패러다임 용도 변경
유비쿼터스 방법론 활용
통합 방법론 용도 변경
미션 크리티컬 패러다임
촉진 설득력있는 방법론 시너지 강화
강력한 기능 재창조
확장 가능한 플랫폼 촉진

이 출력은 의미가 없지만 gather는 출력하기가 정말 쉽습니다.

파이프

java.nio.channels 패키지에는 Pipe라는 클래스가 있습니다. 일반적으로 파이프는 두 개체간에 한 방향으로 데이터를 전송하는 데 사용되는 도관입니다.
Java NIO 파이프 라인은 두 스레드 간의 단방향 데이터 연결입니다. 파이프에는 소스 채널과 싱크 채널이 있습니다. 데이터는 싱크 채널에 기록되고 소스 채널에서 읽습니다. Pipe 클래스는 루프백 메커니즘을 제공하는 한 쌍의 Channel 객체를 생성합니다. 이 두 채널의 원격 끝이 연결되어 SinkChannel 개체에 기록 된 모든 데이터가 SourceChannel 개체에 나타날 수 있습니다.

파이프를 생성하고 파이프에 데이터를 씁니다.

//通过Pipe.open()方法打开管道
Pipe pipe = Pipe.open();

//要向管道写数据,需要访问sink通道
Pipe.SinkChannel sinkChannel = pipe.sink();

//通过调用SinkChannel的write()方法,将数据写入SinkChannel
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
 sinkChannel.write(buf);
}

파이프 라인에서 데이터를 읽는 방법을 살펴보세요.

파이프 라인의 데이터를 읽으려면 소스 채널에 액세스해야합니다.

Pipe.SourceChannel sourceChannel = pipe.source();

소스 채널의 read () 메서드를 호출하여 데이터를 읽습니다.

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);

ead () 메서드에 의해 반환 된 int 값은 버퍼로 읽은 바이트 수를 알려줍니다.

이 시점에서 채널의 간단한 사용법을 마쳤습니다. 사용하려면 더 많이 연습하고 시뮬레이션에서 사용하여 언제 어떻게 사용하는지 알 수 있습니다. 다음 섹션에서는 선택자 선택자에 대해 이야기하십시오.

최근 2020 년에 수집 된 몇 가지 빈번한 인터뷰 질문 (모두 문서로 구성됨), mysql, netty, spring, thread, spring cloud, jvm, 소스 코드, 알고리즘 및 기타 자세한 설명을 포함한 많은 건조 제품이 있습니다. 자세한 학습 계획, 인터뷰 질문 분류 등 이러한 내용을 얻으려면 Q를 추가하십시오. 11604713672

추천

출처blog.csdn.net/weixin_51495453/article/details/113854862