网络IO总结

前言

    对网络IO的学习已经有挺长的时间了,不过直到现在我也不敢说面试的时候能够网络IO给面试官讲的明明白白,我为了写这篇文章,分别看了网络模型、Reactor、Netty原理以及select、poll和epoll的区别。
    本文偏向于广的方向上,部分可能讲解的不是很细致,网上的专题有很多,各位请自便。

网络模型

同步阻塞IO(BIO)

    BIO是最容易理解的网络IO模型,有两种模式,分别为单线程BIO和多线程BIO。单线程BIO其实就是阻塞等待网络请求,当收到一个请求后立马执行业务,业务完成后再去接收下一个请求。
Java代码如下:

ServerSocket serverSocket = new ServerSocket(8080);
while(true){
	// accept方法是阻塞式监听
	Socket socket = serverSocket.accept();
	// 模拟业务处理
	Thread.sleep(1000);
	
	socket.close();
}

而多线程的BIO在一定程度上提高了并发能力,在开启的线程中进行业务处理,以下是最潦草的多线程BIO。

	Socket socket = serverSocket.accept();
	new Thread(() -> {
		// 模拟业务处理
		Thread.sleep(1000);
		
		socket.close();
	})
	socket.close();

上面这个代表一个请求对应一个线程去处理,这样的话,如果遇到100个请求同时访问就会创建100个线程去处理,线程疯狂的上下文切换…所以用线程池的多线程BIO能够避免这个问题,但还是无法改变它作为阻塞IO的本质。

同步非阻塞IO(NIO)

    在这里先阐述一下阻塞和非阻塞IO的概念,其实简单的说就是调用了某个方法后,程序还会不会继续执行的问题。对于阻塞IO,调用方法后,程序卡在这个位置,而非阻塞不会。可能有些人不太明白这其中对于效率来说有什么差别,简单的说就是多线程中通过wait和notify(signal)唤醒的问题,阻塞IO需要唤醒,而非阻塞IO不需要。总而言之就是可以节省阻塞操作带来的消耗吧。
    Java中的NIO就是同步非阻塞的,这一点可以通过以下的代码看出来:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// 绑定监听端口
serverSocketChannel.bind(new InetSocketAddress(port));

Selector selector = Selector.open();
// serverSocketChannel只关心连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
	// 这里就体现了非阻塞的特点,select方法会立马返回结果,和ServerSocket的accept方法一样
	if (selector.select(1000) == 0){
    	//没有网络请求
        continue;
    }
    <SelectionKey> iterator = selector.selectedKeys().iterator();
	while (iterator.hasNext()){
		SelectionKey selectionKey = iterator.next();
		if (selectionKey.isAcceptable()){
        	SocketChannel channel = serverSocketChannel.accept();
			channel.configureBlocking(false);
			channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        } else if (selectionKey.isReadable()){
			SocketChannel channel = (SocketChannel) selectionKey.channel();
            selectionKey.cancel();
	        ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
		    channel.read(buffer);
	        System.out.println(new String(buffer.array()));
			channel.close();
        }
		iterator.remove();
	}
}

这其实也就是Reactor单线程模型,不过Reactor放到后面再说。另外,也满足多路复用的概念。
另外,Java的NIO是零拷贝的,而零拷贝的意思就是避免了网络请求发送的数据在内核态和用户态之间的拷贝与切换。

异步非阻塞IO(AIO)

    AIO因为需要有操作系统的支持,而主要被用作服务器的Linux操作系统并不支持,但是Windows却先一步提出了高效的AIO模型,叫做"完成端口"。不过用起来很复杂,我用过一次都快要吐了。
    我在这里就只提一下异步的概念好了,相比NIO区别就是操作系统在接收到网络请求之后,由操作系统去调用相应的处理程序来完成。我们在NIO中用到的Selector都是需要JVM去主动遍历所有的网络请求,而不是操作系统去做这件事。再简单点说,操作系统没有将接收到的网络数据从内核态拷贝到用户态中,所以不是异步。
另外,异步还是不异步,每个人的定义都不一样,各位结合上下文和自己的知识体系去理解和描述就行了。

Reactor模型

Reactor网络模型分为单线程模型,多线程模型和主从Reactor模型。拿上面NIO的代码来说吧,当key是可读(isReadable)时,我输出了接收到的byteBuffer内容,其实就可以看作是业务处理吧。也就是说,我把业务处理放在了selector的遍历中,这就是单线程Reactor。而多线程Reactor就是开个线程池,当Key可读时,就把key对应的socketChannel扔进线程池里去,不再由selector去处理业务。

if(key.isReadable()){
	SocketChannel channel = (SocketChannel) selectionKey.channel()
	// 假设我在上面创建了一个线程池叫threadPool
	threadPool.execute(() -> {
		// 模拟业务处理
		Thread.sleep(1000);
		channel.close();
	});
}

而主从Reactor就更复杂了,看官们可以看这篇reactor模式:主从式reactor

select、poll和epoll

    selecct、poll和epoll都是c语言的网络模型,作为一个学Java的,多了解一下似乎没什么不妥。
select和NIO的原理是一样的,select是监听了多个文件修饰符(fd),而它只能够知道自己监听的这些fd中,有网络IO事件,但不知道是哪几个发生了,所以它需要将全部的fd都遍历一遍,时间复杂度O(n)。如何监听多个fd?申请了一个数组,将要监听的fd放到了数组中,而因为数组空间有大小限制,所以poll就诞生了。本身也是O(n)的时间复杂度,但是因为用链表注册fd,所以没有大小上限的影响。
epoll是Linux中的网络模型,Linux会把哪个流发上了怎样的IO事件通知我们(用户态),所以epoll是事件驱动的。但它依然没有做到主动将数据从内核态转到用户态中。

总结

    目前我的知识水平也就能写成这样了,后面随着知识的扩展,会再来更新的。下次一定系列了解一下QAQ。
如果有什么说的不对的地方,还请各位不吝赐教。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值