I/O 模型基本说明
- I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
- Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO
- : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解
- 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
- 异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图: 【后面举例说明】
public class BasicBuffer {
public static void main(String[] args) {
//举例说明Buffer 的使用 (简单说明)
//创建一个Buffer, 大小为 5, 即可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
//向buffer 存放数据
// intBuffer.put(10);
// intBuffer.put(11);
// intBuffer.put(12);
// intBuffer.put(13);
// intBuffer.put(14);
for(int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put( i * 2);
}
//如何从buffer读取数据
//将buffer转换,读写切换(!!!)
/*
public final Buffer flip() {
limit = position; //读数据不能超过5
position = 0;
mark = -1;
return this;
}
*/
intBuffer.flip();
intBuffer.position(1);//1,2
System.out.println(intBuffer.get());
intBuffer.limit(3);
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
public void testBuffer() {
IntBuffer buffer = IntBuffer.allocate(5);
for (int i = 0; i < buffer.capacity(); i ++) {
buffer.put(i * 2);
}
buffer.flip();
if(buffer.hasRemaining()) {
buffer.get();
}
}
}
Buffer的类型
Java NIO 有以下Buffer类型他们都继承了Buffer抽象类 其本身也是抽象类
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
Buffer的重要属性:
public abstract class Buffer {
/**
* The characteristics of Spliterators that traverse and split elements
* maintained in Buffers.
*/
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
Buffer相关Api
//初始化buffer
IntBuffer intBuffer = IntBuffer.allocate(5);
//flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public void testBuffer() {
IntBuffer buffer = IntBuffer.allocate(5);
for (int i = 0; i < buffer.capacity(); i ++) {
//将值写入buffer中
buffer.put(i * 2);
}
//将写模式转化为读模式
buffer.flip();
//判断buffer中是否还有数据
if(buffer.hasRemaining()) {
//获取buffer中的数据会导致position后移
buffer.get();
}
}
/**
*clear方法将buffer的position恢复到初始值,但并没有将数据清空,再次写入数据的时候只是将原本的值覆盖
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
public interface Channel extends Closeable{}
FileChannel 类
FileChannel主要用来对本地文件进行 IO 操作,常见的方法有
public class NIOFileChannel01 {
public static void main(String[] args) throws Exception{
String str = "hello";
//创建一个输出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
//通过 fileOutputStream 获取 对应的 FileChannel
//这个 fileChannel 真实 类型是 FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
// MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
// map.put(str.getBytes());
// map.flip();
// fileChannel.write(map);
//创建一个缓冲区 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将 str 放入 byteBuffer
byteBuffer.put(str.getBytes());
//对byteBuffer 进行flip
byteBuffer.flip();
//将byteBuffer 数据写入到 fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
//FileChannel 可以通过map方法创建BUFFER
public abstract MappedByteBuffer map(MapMode mode,long position, long size)
throws IOException;
selector基本介绍
![](https://i-blog.csdnimg.cn/blog_migrate/e30ffedd274269037407f6754eda6194.png)
NIO模型
Selector 、 Channel 和 Buffer 的关系图(简单版)
关系图的说明:
输 出流 , 不能双向,但是 NIO 的 Buffer 是可以读也可以写 , 需要 flip 方法 切 换
channel 是双向的, 可以返回底层操作系统的情况, 比如Linux , 底层的操作系统
通道就是双向的.
NIO实现多人聊天
//服务端代码
public class Server {
private ServerSocketChannel servSocketChannel;
private Selector selector;
/**
* 初始化服务器
* */
public Server(){
try {
servSocketChannel = ServerSocketChannel.open();
servSocketChannel.socket().bind(new InetSocketAddress(7000));
selector = Selector.open();
//开启非阻塞模式
servSocketChannel.configureBlocking(false);
//注册serverSocketChannel
servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 监听连接
* */
public void listen() {
while (true) {
Iterator<SelectionKey> keyIterator = null;
try {
//这里我们等待1秒,如果没有事件发生, 返回
if(selector.select(1000) == 0) { //没有事件发生
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
keyIterator = selectionKeys.iterator();
if(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//客户端连接
if(key.isAcceptable()) {
SocketChannel socketChannel = servSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress() + "上线");
}
//读已就绪
if(key.isReadable()) {
readData(key);
}else{
System.out.println("等待处理");
}
}
} catch (Exception e) {
e.printStackTrace();
}
//当前的key 删除,防止重复处理
keyIterator.remove();
}
}
public void readData(SelectionKey selectionKey) {
SocketChannel channel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
int read = channel.read(byteBuffer);
while (read > 0) {
String msg = new String(byteBuffer.array());
System.out.println(msg);
//转发
sendInfoToOtherClients(msg, channel);
byteBuffer.clear();
read = channel.read(byteBuffer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendInfoToOtherClients(String msg, SocketChannel channel) {
Set<SelectionKey> keys = selector.keys();
for (SelectionKey key : keys) {
SelectableChannel targetChannel = key.channel();
if(targetChannel instanceof SocketChannel && targetChannel != channel) {
SocketChannel socketChannel = (SocketChannel) targetChannel;
ByteBuffer wrap = ByteBuffer.wrap(msg.getBytes());
try {
socketChannel.write(wrap);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Server server = new Server();
server.listen();
}
}
//客户端代码
public class Client {
private SocketChannel socketChannel;
private final String IP = "127.0.0.1";
private Selector selector;
private final int port = 7000;
private String username;
public Client() {
try {
socketChannel = SocketChannel.open(new InetSocketAddress(IP, port));
selector = Selector.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok...");
} catch (IOException e) {
e.printStackTrace();
}
}
//读取从服务器端回复的消息
public void readInfo() {
try {
int readChannels = selector.select();
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 buffer = ByteBuffer.allocate(1024);
//读取
sc.read(buffer);
//把读到的缓冲区的数据转成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove(); //删除当前的selectionKey, 防止重复操作
} else {
//System.out.println("没有可以用的通道...");
}
}catch (Exception e) {
e.printStackTrace();
}
}
//向服务器发送消息
public void sendInfo(String info) {
info = username + " 说:" + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
//启动我们客户端
Client chatClient = new Client();
//启动一个线程, 每个3秒,读取从服务器发送数据
new Thread() {
public void run() {
while (true) {
chatClient.readInfo();
try {
Thread.currentThread().sleep(3000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}