网站首页 > 技术文章 正文
关于Buffer和Channel的注意事项和细节
1、ByteBuffer支持类型化的put和get,put放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有BufferUnderflowException异常。
public class NIOByteBufferPutGet {
public static void main(String[] args) {
//创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
//类型化方式放入数据
buffer.putInt(10);
buffer.putLong(8);
buffer.putChar('我');
buffer.putShort((short) 4);
//复位
buffer.flip();
//取出
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
System.out.println(buffer.getChar());
System.out.println(buffer.getShort());
}
}
2、可以将一个普通Buffer转成只读Buffer。
public class ReadOnlyBuffer {
public static void main(String[] args) {
//创建一个buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
for(int i=0;i<64;i++){
buffer.putShort((byte)i);
}
buffer.flip();
//得到一个只读的Buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer);
//读取
while (readOnlyBuffer.hasRemaining()){
System.out.println(readOnlyBuffer.get());
}
//因为这个buffer已经是只读的,下面的操作会抛出异常ReadOnlyBufferException
readOnlyBuffer.put((byte)100);
}
}
3、NIO还提供了MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成。
/**
* 说明
* 1、MappedByteBuffer 可让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次
*/
public class MappedByteBufferTest {
public static void main(String[] args) throws Exception{
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt","rw");
//获取对应的通道
FileChannel channel = randomAccessFile.getChannel();
/**参数1:FileChannel.MapMode.READ_WRITE 使用读写模式
* 参数2:0:可以直接修改的起始位置
* 参数3: 5:是映射到内存的大小(不是索引位置),即将1.txt的多少个字节映射到内存,可以直接修改的范围就是0~5
* 实际类型DirectByteBuffer
*/
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
//修改第一个字节为H
mappedByteBuffer.put(0,(byte)'H');
//修改第四个字节为9
mappedByteBuffer.put(3,(byte)'9');
randomAccessFile.close();
System.out.println("修改成");
}
}
4、前面我们讲的读写操作,都是通过一个Buffer来完成的,NIO还支持通过多个Buffer(即Buffer数组)完成读写操作,即Scattering和Gatering
/**
* Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入【分散】
* Gatering:从buffer读取数据时,可以采用buffer数组,依次读出
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws Exception {
//使用 ServerSocketChannel 和 SocketChannel 网络
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//绑定端口到socket,并启动
serverSocketChannel.socket().bind(inetSocketAddress);
//创建buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
//等待客户端连接(telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
//假定从客户端接收8个字节
int messageLenght = 8;
//循环读取
while (true){
int byteRead = 0;
while (byteRead<messageLenght){
long read = socketChannel.read(byteBuffers);
//累计读取的字节数
byteRead +=read;
System.out.println("byteRead="+byteRead);
Arrays.asList(byteBuffers).stream().map(buffer -> "postion="+buffer.position()+",limit="+buffer.limit()).forEach(System.out::println);
}
//将所有的buffer进行flip
Arrays.asList(byteBuffers).forEach(Buffer::flip);
//将数据读出显示到客户端
long byteWrite = 0;
while (byteWrite<messageLenght){
long write = socketChannel.write(byteBuffers);
byteWrite += write;
}
//将所有的buffer进行clear
Arrays.asList(byteBuffers).forEach(Buffer::clear);
System.out.println("byteRead:="+byteRead+";byteWrite:="+byteWrite+";messageLength:="+messageLenght);
}
}
}
Selector(选择器)
1、Java的NIO,用非阻塞的IO方式,可以用一个线程处理多个客户端的连接,就会使用到Selector(选择器);
2、Selector能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求;
3、只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大的减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程;
4、避免了多个线程之间的上下文切换导致的开销;
Selector类相关方法
注意事项
1、NIO中的ServerSocketChannel功能类似ServerSocket、SocketChannel功能类似Socket;
2、selector相关方法说明
① selector.select();//阻塞
② selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回
③ selector.wakeup();//唤醒selector
④ selector.selectNow();//不阻塞,立马返还
NIO非阻塞网络编程原理分析图
NIO非阻塞网络编程相关的(Selector,SelectionKey,ServerSocketChannel和SocketChannel)关系梳理图
对上图说明
1、当客户端连接时,会通过ServerSocketChannel得到SocketChannel;
2、Selector进行监听select方法,返回有事件发生的通道的个数
3、将socketChannel注册到selector上,register(Selector sel,int ops),一个selector上可以注册多个SocketChannel;
4、注册后返回一个SelectionKey,会和该Selector关联(集合);
5、进一步得到各个SelectionKey(有事件发生时);
6、再通过SelectionKey反向获取SocketChannel,方法channel();
7、可以通过得到的channel,完成业务处理;
NIO非阻塞网络编程快速入门
案例要求
1、编写一个NIO入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞);
2、目的:理解NIO非阻塞网络编程机制
代码示例如下:
服务端
/**
* 编写一个NIO入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞)
*/
public class NIOServer {
public static void main(String[] args) throws Exception {
//创建ServerSocketChannel -> ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selector对象
Selector selector = Selector.open();
//绑定一个端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector,关心的事件是OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//这里打印的是1
System.out.println("注册后的selectionkey 数量="+selector.keys().size());
//循环等待客户端连接
while (true){
//这里我们等待3秒,如果没有事件发生,返回
if(selector.select(3000)==0){
System.out.println("服务器等待了3秒,无连接");
continue;
}
//如果返回的>0,就获取到相关的selectionKey集合
//1、如果返回的>0,表示已经获取到了关注的事件
//2、selector.selectedkeys() 返回关注事件的集合
//通过selectionKeys方向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历Set<SelectionKey>,使用迭代器遍历
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()){
//获取到Selectionkey
SelectionKey key = keyIterator.next();
//根据key对应的通道发生的事件做相应处理
//如果是OP_ACCEPT,代表有新的客户端连接进来了
if(key.isAcceptable()){
//该客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端连接成功,生成了一个socketChannel:"+socketChannel.hashCode());
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到selector,关注事件为OP_READ,同时给socketChannel关联一个Buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
// 如果不断的有客户端连接进来,在这里可以看到不断的打印出2,3,4...
System.out.println("客户端连接后,注册的selectionkey 数量="+selector.keys().size());
}
//如果发生了OP_READ读事件
if(key.isReadable()){
//通过key反向获取到对应的channel
SocketChannel channel = (SocketChannel)key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("from 客户端 "+new String(buffer.array()));
}
//手动从集合中移除当前的selectionkey,防止重复操作
keyIterator.remove();
}
}
}
}
客户端
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务端的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作...");
}
}
//如果连接成功,就发送数据
String str ="hello,NIO";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer数据写入channel
socketChannel.write(buffer);
System.in.read();
}
}
SelectionKey
1、SelectionKey,表示Selector和网络通道的注册关系,共四种
① int OP_ACCEPT:有新的网络连接可以accept,值为16
② int OP_CONNECT:代表连接已经建立,值为8
③ int OP_READ:代表读操作,值为1
④ int OP_WRITE:代表写操作,值为4
源码中:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
2、SelectionKey相关方法
ServerSocketChannel
1、serverSocketChannel在服务器端监听新的客户端Socket连接;
2、相关方法如下
SocketChannel
1、SocketChannel,网络IO通道,具体负责进行读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区;
2、相关方法如下
猜你喜欢
- 2024-10-04 Netty:数据传输载体- ByteBuf 详解
- 2024-10-04 JDK源码阅读:ByteBuffer(jdk源码阅读顺序以及优先级)
- 2024-10-04 初级java程序员面试题 项目业务逻辑问题 培训班 基础题 开发
- 2024-10-04 好文推荐:从JVM模型谈十种内存溢出及解决方法
- 2024-10-04 「系列教程」手写RPC框架(2) BIO模型替换成NIO模型学习
- 2024-10-04 详解Java NIO中的Buffer、Channel 和 Selector
- 2024-10-04 Java常见的面试题(java面试常见问题与答案)
- 2024-10-04 「系列教程」手写RPC框架(2) NIO模型学习
- 2024-10-04 什么是缓冲区溢出攻击?(缓冲区溢出攻击代名词)
- 2024-10-04 java教程、JAVA学习 |JAVA面试题大全(高级)
- 10-02基于深度学习的铸件缺陷检测_如何控制和检测铸件缺陷?有缺陷铸件如何处置?
- 10-02Linux Mint 22.1 Cinnamon Edition 搭建深度学习环境
- 10-02AWD-LSTM语言模型是如何实现的_lstm语言模型
- 10-02NVIDIA Jetson Nano 2GB 系列文章(53):TAO模型训练工具简介
- 10-02使用ONNX和Torchscript加快推理速度的测试
- 10-02tensorflow GPU环境安装踩坑日记_tensorflow配置gpu环境
- 10-02Keye-VL-1.5-8B 快手 Keye-VL— 腾讯云两卡 32GB GPU保姆级部署指南
- 10-02Gateway_gateways
- 最近发表
-
- 基于深度学习的铸件缺陷检测_如何控制和检测铸件缺陷?有缺陷铸件如何处置?
- Linux Mint 22.1 Cinnamon Edition 搭建深度学习环境
- AWD-LSTM语言模型是如何实现的_lstm语言模型
- NVIDIA Jetson Nano 2GB 系列文章(53):TAO模型训练工具简介
- 使用ONNX和Torchscript加快推理速度的测试
- tensorflow GPU环境安装踩坑日记_tensorflow配置gpu环境
- Keye-VL-1.5-8B 快手 Keye-VL— 腾讯云两卡 32GB GPU保姆级部署指南
- Gateway_gateways
- Coze开源本地部署教程_开源canopen
- 扣子开源本地部署教程 丨Coze智能体小白喂饭级指南
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)