Java NIO与BIO

1,NIO与BIO的区别

BIO:传统的同步阻塞模型BIO是通过Socket和ServerSocket实现的,ServerSocket监听端口,Socket进行连接。

           这种情况不适合处理多个请求:

                 1,生成较多的Socket会消耗过多的本地资源,

                 2,Socket连接的速度比较慢,

                 3,BIO一般都是采取accpet获取Socket后,给一个请求分配一个线程,不管连接是否有真正的数据请求,都需要开辟一个                        新的线程,开辟过多线程会导致效率低下,栈溢出  OutOfMemory异常等。

NIOJDK1.4引入的一种新型IO。对BIO的一种改进,基于Reactor模型。一个Socket连接其实只有在一小部分情况下才会发生数据            传输IO操作,大部分时间都是空闲的,但是还是占着线程。NIO作出的改进是在连接到服务端的众多Socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。客户端的Socket在连接到服务端时就会在事件分离器注册一个IO请求事件和IO 事件处理器。在该系统的连接发生IO请求时,IO事件处理器就会启动一个线程来处理这个IO请求,不断尝试获取系统的IO使用权限,一旦成功呢,则通知这个Socket进行IO数据传输

2,Java程序实现BIO与NIO

      BIOServer

package 网络编程;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
* 类说明
* 描述:TODO
*@author wj
*@date 2018年10月19日
*/
public class ServerSocketTest2 {
	public static void main(String[] args) throws Exception {
		
		int port = 6667;
		
		ServerSocket serverSocket = new ServerSocket(port);
		System.out.println("服务器开启,等待连接...");
		while(true){
			Socket socket =serverSocket.accept();
			new Thread(new TaskSocket(socket)).start();
		}
	}
}
class TaskSocket implements Runnable{
	Socket client;
	public TaskSocket(Socket client) {
		// TODO Auto-generated constructor stub
		this.client = client;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Scanner scanner = new Scanner(client.getInputStream());
			OutputStream outputStream = client.getOutputStream();
			while(scanner.hasNext()){
				String s = scanner.nextLine();
				System.out.println("客户端"+client.getPort()+"发来了"+s);
				outputStream.write("你好啊\n".getBytes());
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

      BIOClient

package 网络编程;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;


public class ClientSocketTest2 {
	public static void main(String[] args) throws Exception {
		
		final int port = 6667;
		final String host = "127.0.0.1";
		for(int i = 0;i < 3;i++) {
			new Thread( new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					try {
						
						Socket socket = new Socket(host, port);
						System.out.println("Cliect[port:" + socket.getLocalPort() + "] 哈哈哈哈..");
						
						
				        OutputStream os = socket.getOutputStream();
				        InputStream is = socket.getInputStream();
				        
				        InputStreamReader reader = new InputStreamReader(is);
				        BufferedReader reader1 = new BufferedReader(reader);
				        
				        os.write(("你好啊"+socket+Thread.currentThread().getName()+"\n").getBytes());
				        os.flush();
				        System.out.println("Cliect[port:" + socket.getLocalPort() + "] 哈哈哈");				       
				        System.out.println("server recv:"+ reader1.readLine()); 
				        System.out.println("client send info....");
				        
				     
				        os.close();
				        is.close();
				        reader1.close();
				        socket.close();
				        
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}).start();
		}
		
		
	}
}

NIOServer

package 网络编程;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * 类说明
 * 描述:TODO
 *@author wj
 *@date 2018年10月19日
 */
public class NIOServer {
	/**
	 * 主线程选择器
	 */
     public static Selector selector = null;
     /**
      * 一个ServerSocketChannel实例
      */
     public static  ServerSocketChannel channel;
     /**
      * 线程池
      */
     static ExecutorService executorService = Executors.newFixedThreadPool(4);
     
     public static void main(String[] args) throws IOException{
    	 /**
    	  * 端口号
    	  */
    	 int port = 8989;
    	 /**
    	  * 初始化
    	  */
    	 channel = ServerSocketChannel.open();
    	 channel.bind(new InetSocketAddress(port));
    	 selector = Selector.open();
    	 channel.register(selector,SelectionKey.OP_ACCEPT);
    	 
     }
     public void start() throws IOException{
    	 
    	 while(selector.select() > 0){
    		Set<SelectionKey> keys = selector.selectedKeys();
    		Iterator<SelectionKey> iterator = keys.iterator();
    		while(iterator.hasNext()){
    			SelectionKey key = iterator.next();
    			if(key.isValid() && key.isAcceptable()) {
    				SocketChannel socketchannel = (SocketChannel)key.channel();
    				socketchannel = channel.accept();
    				SocketAddress address = socketchannel.getRemoteAddress();
                    System.out.println("客户端:"+address +" 已上线");
                    Task task = new Task(socketchannel);
    				executorService.execute(task);
    			}
    		}
    	 }
     }
}

class Task implements Runnable{
	/**
	 * 子线程选择器
	 */
	private Selector selector;
	/**
	 * SocketChannel实例
	 */
	private SocketChannel channel;
	private ByteBuffer buffer;
	public Selector getSelector() {
		return selector;
	}
	public void setSelector(Selector selector) {
		this.selector = selector;
	}
	public Task(SocketChannel channel) {
		// TODO Auto-generated constructor stub
		this.channel = channel;
		try {
			channel.configureBlocking(false);
			selector = Selector.open();
			buffer = ByteBuffer.allocate(1024);
			channel.register(selector,SelectionKey.OP_READ);
			SocketAddress address = channel.getLocalAddress();
			System.out.println("当前线程"+Thread.currentThread().getName()+"处理"+address+"的请求");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			while(selector.select() > 0){
				Set<SelectionKey> keys = selector.selectedKeys();
				Iterator<SelectionKey> iterator = keys.iterator();
				while(iterator.hasNext()){
					SelectionKey key = iterator.next();
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

NIOClient

package 网络编程;

import java.io.ByteArrayOutputStream;
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 Exception{

        for(int i = 0;i < 2;i++) {
            new Thread() {
                public void run(){
                    /**
                     * 1创建SocketChannel 实例
                     */
                    SocketChannel socketChannel = null;

                    try {
                        socketChannel = SocketChannel.open();

                        /**
                         * 连接服务器
                         */
                        socketChannel.connect(new InetSocketAddress("127.0.0.1",8989));
                        /**
                         * 写数据
                         */
                        String msg = "我是客户端!!!";
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        byteBuffer.put(msg.getBytes());
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        socketChannel.shutdownOutput();
                        /**
                         * 读数据
                         */
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        int len = 0;
                        while(true) {
                            byteBuffer.clear();
                            len = socketChannel.read(byteBuffer);
                            if(len == -1) {
                                break;
                            }
                            byteBuffer.flip();

                            while(byteBuffer.hasRemaining()){
                                bos.write(byteBuffer.get());
                            }
                            System.out.println("服务端说:"+new String(bos.toByteArray()));
                            socketChannel.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }

            }.start();

        }


    }
}

3,NIO核心部分——Buffer

      Buffer是一个缓冲区。它与通道紧密联系。通道是I/O传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。数据从Channel读到Buffer中,也可以从Buffer写到Channel中。所有数据的读写都是通过Buffer进行的。

      

 这是BUffer类的家谱

 接下来我们来了解一下这个类

public abstract class Buffer {

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

   容量(Capacity):缓冲区能够容纳的数据元素最大数量。这一容量在缓冲区创建时被设定,并且不能被改变。

   上界(Limit):缓冲区的第一个不能别读或写的元素。

   位置(position):下一个要被读或写的元素的索引。位置会自动由get()、put()方法更新。

   标记(Mark):一个备忘位置。调用mark()来设定 mark=postion 调用reset()来设定postion=mark

 

我们,来创建一个Buffer对象

使用调试

我们可以看到创建后的Buffer对象,capality(容量)为最大值100.limit也为100,pos为0.

接下来我们向里面存放数据

再次调试

可以看出pos指向了5,也就是说向里添加数据,pos会增加。

我们现在

我们知道buffer即可以读也可以写,那么它是怎样做到的呢?关键方法——filp()

public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
    }

将position置为0,limit界限置为position,于是开始从头读取数据。

public final Buffer clear() {
	position = 0;
	limit = capacity;
	mark = -1;
	return this;
    }

clear方法将这些属性还原至刚开始创建的样子,它并没有删除里面的数据,只是在写的时候会覆盖原来的数据。

mark()方法标记位置,reset()方法恢复位置。

在NIO网络编程时,通常使用channel和buffer。

       channel.read(buffer):从channel写入数据到buffer。

       channel.write(buffer):从buffer读入数据到channel。

4,NIO核心部分——Channel

channel就是通道,类似于BIO中的stream(流)。

channel和流的区别

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的;
  • 通道可以异步地读写;
  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入.

Channel的主要实现类:

    1,FileChannel:从文件中读写数据。FileChannel无法设置为非阻塞模式

    2,DatagramChannel:能通过UDP读写网络中的数据

    3,SocketChannel:能通过TCP读写网络中的数据

    4,ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器一样对于每一个新进来的连接都会创建一个SocketChannel

5,NIO核心部分——Selector

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。管理着一个被注册的通道集合的信息和他们的就绪状态。通过是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。

每一个Selector对象维护三个键的集合:

1,已注册的键的集合(Registered key set)

2,已选择的键的集合(Selected key set)

3,已取消的键的集合(Cancelled key set)

打开selector,将channel注册到选择器上。

SelectionKey:选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回一个表示这种病注册关系的标记。选择键包含了两个 比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

一个selectionKey对象包含两个以整数形式进行编码的比特掩码:一个用于指示哪些通道/选择器组合体所关心的操作(interest集合),另一个表示通道准备好要执行的操作(ready集合)。当前的interest集合可以通过调用interestOps()方法来获取。最初,这应该是通道被注册时传进来的值。

选择过程:(来源:《Java NIO》)

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值