Bio Nio

5 篇文章 0 订阅

IO通讯时操作系统会现将IO数据拷贝到内核内存中存储,然后再拷贝到应用程序的内存。这个过程也存在一定时间消耗,且当Java程序在执行IO操作时除了会用到JVM内存外,还会用到操作系统直接内存。不关闭链接这些内存都不会释放。

 

Bio

阻塞IO,在accept和IO读写时当前线程阻塞。 

Socket clientScoket=server.accept(); //阻塞等待客户端Socket链接

取得连接后,把clientScoket封装到Runable中交给ThreadPool线程池中线程去处理读写。

clientScoket.getInputStream().read(byte[]);//取得客户端Socket输入流接收信息

clientScoket.getOutputStream().write(byte[]);//取得客户端Socket输入流接收信息

阻塞的进行IO读写操作。

但是当线程池占满时,其他链接必须等待有链接释放线程。

且当建立连接后,服务端线程执行IO读操作,但是客户端未发送消息,或是消息发送但是网络延迟,服务端线程会一直被阻塞。

BIO模式一个clientScoket对应一个服务端线程,这样高并发时会产生大量线程,且这些线程会阻塞在等待读写操作上。造成大量资源浪费。

 

Nio

只用一个线程通过Selector,就可以控制客户端Channel链接注册,状态监控,以及读写操作,实现多路复用。

此时多个客户端可以同时连接,和服务器进行IO读写操作。

因为现在是一个线程关注多个Channel,而且每个Channel的数据发送都是不连贯的。所以增加了Buffer作为缓冲区。

此线程无线循环,Selector只会监控Channel注册时绑定的事件。一般ServerSocketChannel 绑定OP_ACCEPT事件,

客户端SocketChannel绑定OP_READ。

public class NioServer {
    //通道管理器
    private Selector selector;
    
    //获取一个ServerSocket通道,并初始化通道
    public NioServer init(int port) throws IOException{
        //获取一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //必须把管道设置成非阻塞,默认是阻塞
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(port));
        //获取通道管理器
        selector=Selector.open();
        //将通道管理器与ServerSocketChannel绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,
        //只有当该事件到达时,Selector.select()会返回,否则一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        return this;
    }
    
    public void listen() throws IOException{
        System.out.println("服务器端启动成功");
        
        //使用轮询访问selector
        while(true){
            //当有注册管道的事件触发时,方法返回,否则阻塞。
            selector.select();
            
            //获取selector中的迭代器,选中项为注册的事件
            Iterator<SelectionKey> ite=selector.selectedKeys().iterator();
            
            while(ite.hasNext()){
                SelectionKey key = ite.next();
                //删除已选key,防止重复处理
                ite.remove();
                //服务端接收客户端请求连接事件
                if(key.isAcceptable()){
                    //服务端
                    ServerSocketChannel server = (ServerSocketChannel)key.channel();
                    //获得客户端连接通道
                    SocketChannel channel = server.accept();
                    //必须把管道设置成非阻塞,默认是阻塞
                    channel.configureBlocking(false);
                    //向客户端发消息
                    channel.write(ByteBuffer.wrap(new String("send message to client").getBytes()));
                    //在与客户端连接成功后,为客户端通道注册SelectionKey.OP_READ事件。
                    channel.register(selector, SelectionKey.OP_READ);
                    
                    System.out.println("客户端请求连接事件");
                }else if(key.isReadable()){//客户端有可读数据事件
                    //获取客户端传输数据可读取消息通道。
                    SocketChannel channel = (SocketChannel)key.channel();
                    //创建读取数据缓冲器
                    ByteBuffer buffer = ByteBuffer.allocate(10);
                    int read = channel.read(buffer);
                    byte[] data = buffer.array();
                    String message = new String(data);
                    
                    System.out.println("receive message from client, size:" + buffer.position() + " msg: " + message);
//                    ByteBuffer outbuffer = ByteBuffer.wrap(("server.".concat(msg)).getBytes());
//                    channel.write(outbuffer);
                }
            }
        }
    }
    
    public static void main(String[] args) throws IOException {
        new NioServer().init(9981).listen();
    }
}

虽然NIO是非阻塞的,一个线程就可以通过多路复用器完成对多个客户端的读写操作。但是当业务复杂时,推荐用ThreadPool来解决业务问题,从而解放IO线程。IO线程只负责注册+IO读操作处理业务,业务处理+IO返回操作有业务线程完成。推荐区分BossThreadPool和WorkerThreadPool。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
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.LinkedList;
import java.util.List;

public class ServerSocketThreadPool{
	private static final int MAX_THREAD = Runtime.getRuntime().availableProcessors();
	private ThreadPool pool = new ThreadPool(MAX_THREAD);

	private static int PORT_NUMBER = 1234;

	public static void main(String[] args) throws Exception {
		new ServerSocketThreadPool().go();

	}

	public void go() throws Exception {
		int port = PORT_NUMBER;
		System.out.println("Listenning on port:" + port);
		// 创建通道 ServerSocketChannel
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 绑定监听端口
		serverSocketChannel.socket().bind(new InetSocketAddress(port));
		// 设置为非阻塞方式
		serverSocketChannel.configureBlocking(false);
		// 创建选择器
		Selector selector = Selector.open();

		// 通道注册到选择器
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		while (true) {
			// 一直阻塞,直到有数据请求
			int n = selector.select();
			if (n == 0) {
				continue;
			}
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			while (it.hasNext()) {
				SelectionKey key = it.next();
				if (key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel) key.channel();
					SocketChannel socket = server.accept();
					registerChannel(selector,socket, SelectionKey.OP_READ);
					sayHello(socket);
				}
				if (key.isReadable()) {
					readDataFromSocket(key);
				}
				it.remove();
			}

		}

	}
	
	public void registerChannel(Selector selector,SelectableChannel channel,int ops)throws Exception{
		if(channel==null){
			return;
		}
		channel.configureBlocking(false);
		channel.register(selector, ops);
		
	}
	
	public void sayHello(SocketChannel socket) throws Exception{
		ByteBuffer buffer=ByteBuffer.allocate(1024);
		buffer.clear();
		buffer.put("hello client".getBytes());
		buffer.flip();
			socket.write(buffer);
	}

	public void readDataFromSocket(SelectionKey key) throws Exception {
		WorkThread thread=pool.getWork();
		if(thread==null){
			return;
		}
		thread.serviceChannel(key);
	}

	private class ThreadPool {
		List idle=new LinkedList();
		

		public ThreadPool(int poolSize) {
			for(int i=0;i<poolSize;i++){
				WorkThread thread=new WorkThread(this);
				thread.setName("worker"+(i+1));
				thread.start();
				idle.add(thread);
			}

		}
		public WorkThread getWork(){
			WorkThread thread=null;
			synchronized (idle) {
				if(idle.size()>0){
					thread=(WorkThread) idle.remove(0);
					
				}
			}
			return thread;
		}

		public void returnWorker(WorkThread workThread) {
			synchronized (idle) {
				idle.add(workThread);
			}
		}

	}

	private class WorkThread extends Thread {
		private ByteBuffer buffer = ByteBuffer.allocate(1024);
		private ThreadPool pool;
		private SelectionKey key;

		public WorkThread(ThreadPool pool) {
			this.pool = pool;
		}

		public synchronized void run() {
			System.out.println(this.getName() + " is ready");
			while (true) {
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
					this.interrupt();
				}
				if (key == null) {
					continue;
				}
				System.out.println(this.getName() + " has been awaken");
				try{
					drainChannel(key);
				}catch(Exception e){
					System.out.println("caught '"+e+"' closing channel");
					try{
						key.channel().close();
					}catch(IOException ioe){
						ioe.printStackTrace();
					}
					key.selector().wakeup();
				}
				key=null;
				this.pool.returnWorker(this);

			}

		}
		synchronized void serviceChannel(SelectionKey key){
			this.key=key;
			key.interestOps(key.interestOps()&(~SelectionKey.OP_READ));
			this.notify();
		}
		
		void drainChannel(SelectionKey key)throws Exception{
			SocketChannel channel=(SocketChannel) key.channel();
			buffer.clear();
			int count;
			while((count=channel.read(buffer))>0){
				buffer.flip();
				/*while(buffer.hasRemaining()){
					channel.write(buffer);
				}*/
				byte[] bytes;
				bytes=new byte[count];
				buffer.get(bytes);
				System.out.println(new String(bytes));
				buffer.clear();
			}
			if(count<0){
				channel.close();
				return;
			}
			key.interestOps(key.interestOps()|SelectionKey.OP_READ);
			key.selector().wakeup();			
		}

	}

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值