Java NIO 全称java non-blocking IO,是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的
NIO相关类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写。
NIO有三大核心部分:Channel(通道),Buffer(缓存区),Selector(选择器)
NIO是面向缓冲区,或者面向块编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完成写入。这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50个或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
HTTP 2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP 1.1大
实例说明NIO的Buffer
package com.netty.nio;
import java.nio.IntBuffer;
public class BasicBuffer {
public static void main(String[] args) {
//举例说明Buffer的使用(简单说明)
//创建一个Buffer,大小为5,即可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
//向buffer 存放数据
for (int i=0;i< intBuffer.capacity();i++){
intBuffer.put(i*2);
}
//如何从buffer读数据
//将buffer转换,读写切换
intBuffer.flip();
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成一个容器对象(含数组),该对象提供了一直组方法,可以更轻松地使用内存块,缓冲区对象设置了一些机制,能跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer
对于Java中的基本数据类型(boolean除外),都有一个Buffer类型与之相对应,最常用的自然是ByteBuffer类(二进制数据)
NIO的通道类似于利流,但有些区别如下:
BIO中的Stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作。
Channel在NIO中是一个接口——public interface Channel extends Closeable{}
常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel和SocketChannel。【ServerSocketChannel类似ServerSocket,SocketChannel类似Socket】
FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写
FileChannel主要用来对本地文件进行IO操作,常见的方法有
要求:
使用前面学习后的ByteBuffer(缓冲)和FileChannel(通道),将“hello world”写入到file01.txt中
文件不存在就创建
代码演示
package com.netty.nio;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel01 {
public static void main(String[] args) throws IOException {
String str="hello world";
//创建一个输出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
//通过fileOutputStream获取对应的FileChannel
//这个fileChannel真实类型是FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将str放入byteBuffer
byteBuffer.put(str.getBytes());
//切换到写入模式
byteBuffer.flip();
//将缓冲区的数据写到通道中
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
要求:
使用ByteBuffer(缓冲)和FileChannel(通道),将file01.txt中的数据读入到程序
package com.netty.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannelFile02 {
public static void main(String[] args) throws IOException {
//创建文件的输入流
File file = new File("d:\\file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通过fileinputStream获取对应的FileChannel->实际类型FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//从管道读取数据到缓冲区;
fileChannel.read(byteBuffer);
//byteBuffer的字节数据转成String
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
}
要求
使用FileChannel和方法read,write完成文件的拷贝
拷贝一个文件 1.txt
package com.netty.nio;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannelFIle03 {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel inputChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel outChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true){//循环读取
byteBuffer.clear();//清空
int read = inputChannel.read(byteBuffer);
System.out.println("read="+ read);
if (read==-1){
break;
}
byteBuffer.flip();
outChannel.write(byteBuffer);
}
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
要求
package com.netty.nio;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class NIOFileChannelFile04 {
public static void main(String[] args) throws IOException {
//创建相关流
FileInputStream fileInputStream = new FileInputStream("Koala.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("a.jpg");
//获取各个流对应的fileChannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//从目标通道中复制数据到当前通道
destCh.transferFrom(sourceCh,0, sourceCh.size());
//关闭相关通道和流
sourceCh.close();
destCh.close();
fileInputStream.close();
fileOutputStream.close();
}
}
ByteBuffer支持类型化的put和get。put放入的是什么数据类型,get就是应该使用相应的数据类型来取出,否则可能有BufferUnderFlowException异常。
package com.netty.nio;
import java.nio.ByteBuffer;
public class NIOByteBufferPutGet {
public static void main(String[] args) {
//创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
//类型化方式放入数据
buffer.putInt(100);
buffer.putLong(8L);
buffer.putChar('尚');
buffer.putShort((short)4);
buffer.flip();
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
System.out.println(buffer.getChar());
//抛出java.nio.BufferUnderflowException异常
//System.out.println(buffer.getLong());
System.out.println(buffer.getShort());
}}
可以将一个普通Buffer转为只读Buffer
package com.netty.nio;
import java.nio.ByteBuffer;
public class ReadOnlyBuffer {
public static void main(String[] args) {
//创建一个buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
for (int i=0;i<64;i++){
buffer.put((byte) i);
}
//读取
buffer.flip();
//得到一个只读的Buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass());
//读取
while(readOnlyBuffer.hasRemaining()){
System.out.println(readOnlyBuffer.get());
}
readOnlyBuffer.put((byte) 100);
//ReadOnlyBufferException
}}
NIO提供了MappedByteBuffer,可以让文件直接在内存中进行修改,而如何同步到文件由NIO来完成。
package com.netty.nio;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* 说明
* 1. MappedByteBuffer 可让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次
*/
public class MappedBufferTest {
public static void main(String[] args) {
try {
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt","rw");
//获取对应的通道
FileChannel fileChannel = randomAccessFile.getChannel();
/**
* 参数 1:FileChannel.MapMode.READ_WRITE 使用的读写模式
* 参数 2:0 可以直接修改的起始位置
* 参数 3:5是映射到内存的大小(不是索引位置),即将1.txt的多少个字节映射到内存
* 可以直接修改的范围就是 0-5
* 实际类型 DirectByteBuffer
*/
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,0,10);
mappedByteBuffer.put(0,(byte) 'H');
mappedByteBuffer.put(3,(byte) '9');
mappedByteBuffer.put(5,(byte) 'Y');//IndexOutOfBoundsException
randomAccessFile.close();
System.out.println("修改成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO还支持通过多个Buffer(即Buffer数组)完成读写操作,即Scattering和Gathering
package com.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
/**
* Scattering: 将数据写入到Buffer时,可以采用Buffer数组,依次写入【分散】
* Gathering:从buffer读取数据时,可以采用buffer数组,依次读
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws IOException {
//使用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();
int messageLength =8;
//循环的读取
while (true){
int byteRead =0;
while(byteRead<messageLength){
long l = socketChannel.read(byteBuffers);
byteRead +=1;//累计读取的字节数
System.out.println("byteRead="+byteRead);
//使用流打印,看看当前的这个buffer的position和limit
Arrays.asList(byteBuffers).stream().map(buffer->"postion="+buffer.position()+",limit="+
buffer.limit()).forEach(System.out::println);
}
//将所有的buffer进行flip
Arrays.asList(byteBuffers).forEach(ByteBuffer::flip);
//将数据读出显示到客户端
long byteWrite = 0;
while (byteWrite<messageLength){
long l = socketChannel.write(byteBuffers);
byteWrite+=1;
}
//将所有的Buffer进行clear
Arrays.asList(byteBuffers).forEach(ByteBuffer::clear);
System.out.println("byteRead="+byteRead+" byteWrite="+byteWrite+", messageLength="+messageLength);
}
}
}
Selector 类是一个抽象类,常用方法和说明如下:
PS:
要求
package com.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建 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);
//循环等待客户端连接
while (true){
//这里等待1秒。如果没有事件发生,返回
if (selector.select(1000)==0){
//没有事件发生
System.out.println("服务器等待了1秒");
continue;
}
/**
* 如果返回的>0,就获取到相关的selectionKey集合
* 1.如果返回的>0,表示已经获取到关注的事件
* 2.selector.selectedKeys()返回关注事件的集合
*/
// 通过 selectionKeys 反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历selectionKeys
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
//获取SelectionKey
SelectionKey key = it.next();
//根据 key 对应的通道发生的事件是做相应处理
if (key.isAcceptable()){
//如果是OP_ACCEPT,有新的客户端连接
//该客户端生成一个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));
}
if (key.isReadable()){
//发生OP_READ
//通过key反向获取到对应channel
SocketChannel channel =(SocketChannel) key.channel();
//获取到该channel关联的Buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
System.out.println("form 客户端"+new String(byteBuffer.array()));
}
//手动从集合中移动当前的selectionKey,防止重复操作
it.remove();
}
}
}
}
package com.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
//得到一个网络通道
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,尚硅谷";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将Buffer数据写入channel
socketChannel.write(buffer);
System.in.read();
}
}
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;
SelectionKey相关方法
要求:
编写一个NIO群聊系统,实现服务端和客户端之间的数据简单通讯(非阻塞)
实现多人群聊
服务器端:可以监测用户上线,离线,并实现消息转发功能。
客户端:通过channel可以无阻塞发送消息给其它所有用户,同时可以接受其它用户发送的消息(有服务器转发得到)
Server.java
package com.netty.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class GroupChatServer {
//定义属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//构造器
//初始化工作
public GroupChatServer(){
try {
//得到选择器
selector = Selector.open();
//ServerSocketChannel注册
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
listenChannel.configureBlocking(false);
//将该listenChannel注册到Selector
listenChannel.register(selector,SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
}
}
//监听
public void listen(){
try {
//循环处理
while (true){
int count = selector.select();
if (count>0){
//有事件处理
//遍历得到selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//取出selectionKey
SelectionKey key = iterator.next();
//监听到accept
if (key.isAcceptable()){
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
//将该sc注册到selector
sc.register(selector,SelectionKey.OP_READ);
//提示
System.out.println(sc.getRemoteAddress()+" 上线");
}
if (key.isReadable()){
//通到发送read事件,即通道是可读的态度。
//处理读(专门)
readData(key);
}
iterator.remove();
}
}else {
System.out.println("等待.....");
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//发生异常处理...
}
}
//读取客户端消息
private void readData(SelectionKey key){
//定义一个SocketChannel
SocketChannel channel = null;
try {
//得到channel
channel =(SocketChannel)key.channel();
//创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
//根据count的值做处理
if (count>0){
//把缓存区的数据转为字符串
String msg = new String(buffer.array()).trim();
//输出该消息
System.out.println("form 客户端:"+msg);
//向其它的客户端转发消息(要去掉自己),专门写一个方法来处理
sendInfoToClients(msg,channel);
}
}catch (IOException e){
try {
System.out.println(channel.getRemoteAddress()+" 离线了..");
//取消注册
key.cancel();
//关闭通道
channel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
//转发消息给其它客户(通道)
private void sendInfoToClients(String msg,SocketChannel self) throws IOException {
System.out.println("服务器转发消息中...");
//遍历 所有注册到selector上的SocketChannel,并排除self
for (SelectionKey key:selector.keys()) {
//通过key 取出对应的SocketChannel
Channel targetChannel = key.channel();
//排除自己
if (targetChannel instanceof SocketChannel&&targetChannel!=self){
//转型
SocketChannel dest =(SocketChannel) targetChannel;
//将msg存储到buffer
ByteBuffer buffer =ByteBuffer.wrap(msg.getBytes());
//将buffer的数据写入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建服务器对象
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}
client.java
package com.feng.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
public class GroupChatClient {
//定义相关的属性
private final String HOST = "127.0.0.1";//服务器的ip
private final int PORT = 6667;//服务器端口
private Selector selector;
private SocketChannel socketChannel;
private String username;
//构造器,完成初始化
public GroupChatClient() throws IOException {
selector = Selector.open();
//连接服务器
socketChannel = socketChannel.open(new InetSocketAddress(HOST,PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//注册selector到socketChannel
socketChannel.register(selector, SelectionKey.OP_READ);
//得到username
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username+" is ok");
}
//向服务器发生消息
public void sendInfo(String info){
info = username+" 说: "+info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e){
e.printStackTrace();
}
}
//读取从服务器回复的消息
public void readInfo(){
try {
int readChannels =selector.select(2000);
if (readChannels>0){//有可以用的通道
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()){
//得到相关的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一个Buffer
ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
//从通道读取数据到缓冲区
sc.read(byteBuffer);
//把读到的数据转为字符串
String msg = new String(byteBuffer.array());
System.out.println(msg.trim());
}
iterator.remove();
}
}else {
//System.out.println("没有可以用的通道...");
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
//启动
GroupChatClient chatClient = new GroupChatClient();
//启动一个线程,每个3秒,读取从服务器发送数据
new Thread(){
@Override
public void run() {
while (true){
chatClient.readInfo();
try{
sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}.start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}
Java传统IO和网络编程的代码
File file = new File("text.txt");RandomAccessFile raf = new RandomAccessFile(file,"rw");byte[] arr = new byte[(int)file.length()];raf.read(arr);Socket socket = new ServerSocket(8080).accept();socket.getOutputStream().write(arr);
linux 2.1版本 提供了sendFile函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文件切换
示意图和小结
ps:零拷贝从操作系统角度,是没有cpu拷贝
这里其实有一次cpu拷贝kernel buffer->socket buffer 但是,拷贝的信息很少,比如length,offset,消耗低,可以忽略。
PS:
使用传统的IO方法传递一个大文件
package com.netty.nio.zerocopy;import java.io.DataInputStream;import java.net.ServerSocket;import java.net.Socket;public class OldIOServer { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(7001); while (true) { Socket socket = serverSocket.accept(); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); try { byte[] byteArray = new byte[4096]; while (true) { int readCount = dataInputStream.read(byteArray, 0, byteArray.length); if (-1 == readCount) { break; } } } catch (Exception ex) { ex.printStackTrace(); } } }}
package com.netty.nio.zerocopy;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
public class OldIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 7001);
String fileName = "protoc-3.6.1-win32.zip";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
使用NIO零拷贝方式传递(transferTO)一个大文件
package com.netty.nio.zerocopy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* 服务器端
*/
public class NewIOServer {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(address);
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (-1!=readCount){
try {
readCount = socketChannel.read(byteBuffer);
}catch (Exception e){
e.printStackTrace();
}
//倒带 position = 0 mark作废
byteBuffer.rewind();
}
}
}
}
package com.netty.nio.zerocopy;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
/**
* 客户端
*/
public class NewIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",7001));
String filename="protoc-3.6.1-win32.zip";
//得到文件channel
FileChannel fileChannel = new FileInputStream(filename).getChannel();
//得到开始时间
long startTime = System.currentTimeMillis();
//在linux下,一次transferTo方法就可以完成传输
//在window下,一次调用transferTo只能传送8m,就需要分段传输文件,而且要注意传输时的位置
//transferTo 底层使用到零拷贝
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送的总的字节数="+transferCount+",耗时:"+(System.currentTimeMillis()-startTime));
//关闭
fileChannel.close();
}
}
BIO | NIO | AIO | |
---|---|---|---|
IO模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |