本节主要讲Socket与NIO
首先Socket,他不是协议,而是计算机之间的通信技术,HTTP才是应用层协议。Socket与HTTP的关系是Socket是HTTP的底层实现,HTTP对Socket进行封装,所以我们学习Socket技术还是很有必要的
Socket底层是用C++实现的,当然最底层都是直接与硬件进行通信,这里不做探究。各个语言如:Java,C#都对Socket都有不同程度的封装。
NIO即non-blocking IO,无阻塞通信
NIO与BIO最大的不同就在于NIO是以块为单位进行传输,BIO是以字节流为单位进行传输;
什么是以块为单位进行传输?
NIO两个最重要的概念是Buffer和Channel,一个很有意思的比喻是Channel是一个通道,而Buffer是小矿车,当小矿车装满或者命令小矿车移动的时候小矿车拉着慢慢的数据在通道上行走。
Buffer(缓冲区)
一个抽象类,是所有Buffer类的爸爸,像什么IntBuffer,FloatBuffer等都继承自它,不过IntBuffer也是一个抽象类,所有xxxBuffer类都是抽象类,无法直接实例化,他们之间实例化都是用wrap方法:
int[] intArray = {0,1,2,3,4,5,6,7,8,9};
IntBuffer wrap = IntBuffer.wrap(intArray);
System.out.println(wrap);
除此之外还有一个带有两个参数的构造方法:
offset:要使用的子数组的偏移量;必须为非负且不大于 array.length。将新缓冲区的位置设置为此值。
length: 要使用的子数组的长度;必须为非负且不大于 array.length - offset。将新缓冲区的界限设置为 offset + length。
public static IntBuffer wrap(int[] array,
int offset, int length)
{
try {
return new HeapIntBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
如果我们观察Buffer,会发现他有四个重要的参数:
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
mark:设置标记,mark大于positon时重置为-1;像是爬山时候的路标
position:缓冲区的当前索引
limit:缓冲区的限制
capacity:缓冲区的容量,且limit<=capacity
跟着四个数据最直接相关的就是filp()方法了
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
比如缓冲区有10byte,只用了8byte,那么此时position在8,调用该方法之后limit变为8position变为0
还有clear()方法:清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。
申请直接缓冲区
allocateDirect:创建直接缓冲区,其操作数据不在jvm在内核中;善于保存那些一手操作系统本机IO操作影响的大量,长时间保存的数据;操作快但不安全
allocate:非直接缓冲区,其调用的是HeapBuffer方法,从名字就能看出:嗯,申请在堆上了;事实上默认的wrap就是调用heapBuffer方法,其继承关系十分奇怪,我是看不出来个啥。。。。(并没有heapBuffer方法,而是对应的如HeapIntBuffer,HeapFloatBuffer等方法)
总的来说,Buffer的子类只有ByteBuffer和CharBuffer比较常用。
Channel通道
Channel类中全是接口,这是由于通道的功能实现是要依赖于操作系统的,Channel接口只定义有哪些功能,而具体的功能实现在不同操作系统中不一样,因此,JDK通道被设计成接口类型
比如果我们常用的FileChannel
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
其基本使用·方式是这样:
FileOutputStream file =
new FileOutputStream(new File("D:\\a.txt"));
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.wrap("abcde".getBytes());
try {
//返回值就是position的位置
int write = channel.write(buffer);
//channel内部维护位置,可以重写定义position
channel.position(2);
//还原缓冲区buffer中的position
buffer.rewind();
//再次写入
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
D盘中的a.txt文件是这样子的:
其主要API如下:
write()方法是具有同步性的,即多个线程之间某一通道要进行传输的话会独占线程,其他线程进入阻塞状态
read()方法也是同步的,其返回值是:从通道当前位置向ByteBuffer缓冲区中读的字节个数;当返回-1即到达流的末端