示例解析
package com.leolee.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
/**
* @ClassName ScatteringAndGatheringTest
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/20
* @Version V1.0
**/
public class ScatteringAndGatheringTest {
public static void test () throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(8899);
serverSocketChannel.socket().bind(inetSocketAddress);
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 2 + 3 + 4;
ByteBuffer[] byteBuffers = new ByteBuffer[3];
byteBuffers[0] = ByteBuffer.allocate(2);
byteBuffers[1] = ByteBuffer.allocate(3);
byteBuffers[2] = ByteBuffer.allocate(4);
while (true) {
int bytesRead = 0;
while (bytesRead < messageLength) {
long r = socketChannel.read(byteBuffers);
bytesRead += r;
System.out.println("bytesRead:" + bytesRead);
System.out.println("after read,show every buffer:");
Arrays.asList(byteBuffers).stream()
.map(buffer -> "position:" + buffer.position() + ",limit:" + buffer.limit() + ",capacity:" + buffer.capacity())
.forEach(System.out::println);
}
Arrays.asList(byteBuffers).forEach(byteBuffer -> {
byteBuffer.flip();
});
long bytesWrite = 0;
while (bytesWrite < messageLength) {
long w = socketChannel.write(byteBuffers);
bytesWrite += w;
}
Arrays.asList(byteBuffers).forEach(byteBuffer -> {
byteBuffer.clear();
});
System.out.println("bytesRead:" + bytesRead + ",bytesWrite:" + bytesWrite + ",messageLength:" + messageLength);
}
}
public static void main(String[] args) throws IOException {
ScatteringAndGatheringTest.test();
}
}
代码解读:
- 通过开启一个 NIO 的服务端 socket 通道作为一个接收客户端发送 socket 数据的接收器,运行后进入死循环模拟服务器不退出
- 定义了 messageLength 作为消息接收的限制长,以及一个 buffer 数组,这个buffer数组包含三个 buffer,大小分别是 2、3、4
- socket 数据接收器启动之后,会将接收到的数据读取并原封不动的写回客户端
运行结果:
启动程序,并且通过 telnet 命令来模拟请求
telnet localhost 8899,连接成功后,输入 abcd12345
服务端打印如下:
bytesRead:1
after read,show every buffer:
position:1,limit:2,capacity:2
position:0,limit:3,capacity:3
position:0,limit:4,capacity:4
bytesRead:2
after read,show every buffer:
position:2,limit:2,capacity:2
position:0,limit:3,capacity:3
position:0,limit:4,capacity:4
bytesRead:3
after read,show every buffer:
position:2,limit:2,capacity:2
position:1,limit:3,capacity:3
position:0,limit:4,capacity:4
bytesRead:4
after read,show every buffer:
position:2,limit:2,capacity:2
position:2,limit:3,capacity:3
position:0,limit:4,capacity:4
bytesRead:5
after read,show every buffer:
position:2,limit:2,capacity:2
position:3,limit:3,capacity:3
position:0,limit:4,capacity:4
bytesRead:6
after read,show every buffer:
position:2,limit:2,capacity:2
position:3,limit:3,capacity:3
position:1,limit:4,capacity:4
bytesRead:7
after read,show every buffer:
position:2,limit:2,capacity:2
position:3,limit:3,capacity:3
position:2,limit:4,capacity:4
bytesRead:8
after read,show every buffer:
position:2,limit:2,capacity:2
position:3,limit:3,capacity:3
position:3,limit:4,capacity:4
bytesRead:9
after read,show every buffer:
position:2,limit:2,capacity:2
position:3,limit:3,capacity:3
position:4,limit:4,capacity:4
bytesRead:9,bytesWrite:9,messageLength:9
可以看到 telnet 发送的9个长度的消息,被顺序的存入了三个数组中,当一个 buffer 满了之后,才会存入下一个 buffer,这就是Scattering,之后服务端又将所有数据返回给客户端,如下:
这就是 Gathering。
理解
对于 NIO 中这样的有趣设计,其实是很有必要的。比如,当在使用自定义协议的时候,不同大小的有序 buffer 序列(buffer 数组)可以轻而易举的告诉开发者,每一个 buffer 是代表什么信息,并不需要特别的解析。