谢谢3y同学的例子(juejin.cn/post/684490…)
- buffer
- 两核心方法 get() put() ,四核心属性:0 <= mark <= position <= limit <= capacity
- 待续...
- channel
- FileChannel:文件通道,用于文件的读和写
- DatagramChannel:用于 UDP 连接的接收和发送
- SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
- ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
- 主要配合buffer使用。
- 待续...
- Selector
- 选择器或者叫多路复用器主要是用来搭配Socket相关的网络IO使用。
- 待续...
需要注意的小点
- 非直接缓存与直接缓存的区别及使用场景
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//非直接
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);//直接
flip()
方法调用的作用(读模式),buffer四大属性的变化
- 客户端
public class NoBlockClient {
public static void main(String[] args) throws Exception {
// 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
// 1.1切换成非阻塞模式
socketChannel.configureBlocking(false);
// 2. 发送一张图片给服务端
FileChannel fileChannel = FileChannel.open(Paths.get("C:\\nio\\a.jpg"), StandardOpenOption.READ);
// 3.建立buffer
ByteBuffer buffer = ByteBuffer.allocate(1024*512);
// 4.读取本地文件(图片),发送到服务器
while (fileChannel.read(buffer) != -1) {
// 在读之前都要切换成读模式
buffer.flip();
System.out.println(buffer.limit());
Thread.sleep(500);
socketChannel.write(buffer);
// 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}
// 5. 关闭流
fileChannel.close();
socketChannel.close();
}
}
- 服务端
public class NoBlockServer {
public static void main(String[] args) throws Exception {
// 1.获取通道
ServerSocketChannel server = ServerSocketChannel.open();
// 2.切换成非阻塞模式
server.configureBlocking(false);
// 3. 绑定连接
server.bind(new InetSocketAddress(9999));
// 4. 获取选择器
Selector selector = Selector.open();
// 4.1将通道注册到选择器上,指定接收“监听通道”事件
server.register(selector, SelectionKey.OP_ACCEPT);
// 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
while (selector.select() > 0) {
// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 7. 获取已“就绪”的事件,(不同的事件做不同的事)
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
// 8. 获取客户端的链接
SocketChannel client = server.accept();
// 8.1 切换成非阻塞状态
client.configureBlocking(false);
// 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) { // 读事件就绪
// 9. 获取当前选择器读就绪状态的通道
SocketChannel client = (SocketChannel) selectionKey.channel();
// 9.1读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
// 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
FileChannel outChannel = FileChannel.open(Paths.get("C:\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
while (client.read(buffer) > 0) {
// 在读之前都要切换成读模式
buffer.flip();
System.out.println("server:"+buffer.limit());
outChannel.write(buffer);
// 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}
}
// 接收事件就绪
// 10. 取消选择键(已经处理过的事件,就应该取消掉了)
iterator.remove();
}
}
}
}
运行结果如下,会发现图片大小变小了。
System.out.println(buffer.limit())
;
while (client.read(buffer) > 0) {
// 在读之前都要切换成读模式
buffer.flip();
System.out.println("server:"+buffer.limit());
outChannel.write(buffer);
// 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}
由于同时有大量的tcp传输,采用的也是非阻塞模式,因此我们最后写到文件中的内容可能会被覆盖掉,也就导致了最后的图片大小只有512kb。解决办法是采用append方式追加:
FileChannel outChannel = FileChannel.open(Paths.get("C:\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE,StandardOpenOption.APPEND);
然后通过以下方法读取:
while (client.read(buffer) > 0) {
// 在读之前都要切换成读模式
buffer.flip();
while (buffer.hasRemaining())//当前位置和极限位置之间是否有元素
{
outChannel.write(buffer);
}
buffer.clear();
}
未完待续...