在IO执行有两个阶段很重要:html
当用户进程调用了recvfrom这个系统调用,内核就开始了io的第一个阶段:等待数据准备。若是数据还没准备好(好比尚未收到一个完整的udp包),这时候内核要等待足够的数据到来,而在用户进程这边,进程会被阻塞。当内核等到数据准备好,进程将数据从内核中拷贝到用户空间,而后内核返回结果,用户进程才解除block状态,从新运行起来。数组
BIO在IO执行的两个阶段都被阻塞了。bash
示例代码:socket
public static void server(){
ServerSocket serverSocket = null;
InputStream in = null;
try
{
serverSocket = new ServerSocket(8080);
int recvMsgSize = 0;
byte[] recvBuf = new byte[1024];
while(true){
Socket clntSocket = serverSocket.accept();
SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
System.out.println("Handling client at "+clientAddress);
in = clntSocket.getInputStream();
while((recvMsgSize=in.read(recvBuf))!=-1){
byte[] temp = new byte[recvMsgSize];
System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
System.out.println(new String(temp));
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally{
try{
if(serverSocket!=null){
serverSocket.close();
}
if(in!=null){
in.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
复制代码
NIO是一种同步非阻塞的IO模型。同步指线程不断轮询IO事件是否就绪,非阻塞是指线程在等待IO的时候,能够作其余任务。同步的核心是Selector,Selector代替了线程自己轮询IO事件,避免了阻塞同时减小了没必要要的线程消耗;非阻塞的核心是通道和缓冲区,当IO事件就绪时,能够经过写入缓冲区,保证IO的成功,无需线程阻塞式地等待。学习
当用户进程调用recvfrom时,系统不会阻塞用户进程,若是数据还没准备好,系统马上返回一个ewouldblock错误给进程,用户进程知道数据还没准备好,用户进程就能够去作其余事了。进程轮询内核查看数据是否准备好。直到数据准备好了,而且收到用户进程的system call,进程复制数据报,而后返回。ui
基本上,全部的IO在NIO中都从一个Channel开始。数据能够从Channel读到Buffer中,也能够从Buffer写到Channel。spa
Buffer经过如下几个变量来保存这个数据的当前位置状态:线程
capacity:缓冲区数组的总长度
position:下一个要操做的数据元素的位置
limit:缓冲区数组中不可操做的下一个元素的位置
mark:用于记录当前position的前一个位置或者默认是-1
0 <= mark <= position <= limit <= capacity复制代码
开始时Buffer的position为0,limit为capacity,程序写入数据到缓冲区,position日后移。当Buffer写入数据结束以后,调用flip()方法以后(此时limit=position,position=0),Buffer为输出数据作好准备;当Buffer输出数据结束以后,调用clear()方法(此时limit=capacity,position=0),clear()方法不是清空Buffer的数据,它仅仅将position置为0,将limit置为capacity,为再次向Buffer装入数据作准备;3d
Buffer的使用:code
Buffer一些重要方法:
示例代码:
public static void client(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
复制代码
Channel和Selector配合使用,必须先将Channel注册到Selector上,经过register()方法来实现。Channel.register()方法会返回一个SelectionKey对象。这个对象表明了注册到该Selector的通道。
一旦向Selector注册了一个或多个通道,就能够调用select()方法。
select()方法返回的int值表示有多少通道已经就绪。
参考资料:疯狂Java讲义