三大组件
- Channel & Buffer
Channel类似Stream,是读写数据的双向通道,可以从Channel将数据读入Buffer,也可以将Buffer的数据写入Channel,常见的Channel(FileChannel、DatagramChannel(udp)、SocketChannel、ServerSocketChannel)。Buffer用来缓冲读写数据,常见Buffer(ByteBuffer、ShrotBuffer、IntBuffer、LongBuffer。。。)。 - Selector
多线程模板下的缺点:内存占用高、线程上下文切换成本高、只适合连接数少的场景。
线程池模板下的缺点:阻塞模式下,线程只能处理一个Socket连接、仅适合短连接场景。
Selector模板下:Selector的作用就是配合一个线程来管理多个Channel,获取这些Channel上发生的事件,这些Channel工作在非阻塞模式下,线程不会阻塞在一个Channel上,适合连接数特别多,但流量低的场景。调用Selector的select()会阻塞直到Channel发生了读写就绪事件,并交给线程来处理。 - ByteBuffer
try(FileChannel channel = new FileInputStream(data.txt).getChannel()) {
// 缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
while(true) {
// 从channel读取数据,向buffer写入
int len = channel.read(buffer);
if(len == -1) {
break;
}
//切换至读模式
buffer.flip();
// 是否还有剩余未读数据
while(buffer.hasRemaining()) {
byte b = buffer.get();
System.out.println((char) b);
}
//切换至写模式
buffer.clear(); // compact()
}
}
结构:capaticy(buffer容量)、position(写入位置,指针)、limit(写入限制)
- 常用方法
分配空间:allocate()
写入数据:channel.read(buffer), buffer.put()
读取数据:channel.write(buffer), buffer.get()
rewind()、mark()、reset()、get(i) - 字符串与ByteBuffer转换
// 字符串转ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
buffer.put("hello".getBytes());
// 使用Charset,会直接切换到读模式
ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello")
// wrap
ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes());
// 转String
String str = StandardCharsets.UTF_8.decode(buffer).toString()
- 黏包半包
数据在传输过程中由于某种原因进行了重新组合
发送:
Hello,worldn
I’m zhangsann
How are youn
接收:(黏包半包) 缓冲区大小问题
Hello,worldnI’m zhangsannHo
w are youn
private static void split(ByteBuffer source) {
// 读模式
source.flip();
for (int i = 0; i < source.limit(); i++) {
// 匹配到完整信息
if (source.get(i) == 'n') {
// 获取完整信息长度
int length = i + 1 - source.position();
ByteBuffer target = ByteBuffer.allocate(length);
for (int j = 0; j < length; j++) {
// 插入数据,更新position位置
target.put(source.get());
}
}
}
// 将半包状态下未读完数据移到前面
source.compact();
}



