五种网络I/O模型

五种网络I/O模型

五种网络I/O模型

  • 如果使用多进程/多线程模式的话,创建进程和创建线程需要时间开销。在编写服务器客户端程序时,如果服务器性能不行而客户端太多时这种代价很大。试想如果有一种方法能够同时监听按键设备、串口设备和网络socket的事件(可读、可写、出错),一旦事件发生就通知大家,并告诉是谁的事件、以及究竟什么事件发生了那就好了,这种实现方式就叫做多路复用。

  • 在Linux下进行网络编程时,我们常常见到同步(Sync)/异步(Async)阻塞(Block)/非阻塞(Unblock)四种调用方式:

  1. 同步和异步的概念描述的是用户线程与内核的交互方式
    同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行。
    异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
  2. 阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式
    阻塞是指IO操作在没有接收完数据或者没有得到结果之前不会返回,需要彻底完成后才返回到用户空间。
    非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
  • 在Linux下进行网络编程时,服务器端编程经常需要构造高性能的IO模型,常见的IO模型有五种:
  1. 同步阻塞IO(Blocking IO):即传统的IO模型,在linux中默认情况下所有的socket都是阻塞模式。当用户进程调用了read()这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于网络IO来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除阻塞的状态,重新运行起来;几乎所有的程序员第一次接触到的网络编程都是从listen()、read()、write() 等接口开始的,这些接口都是阻塞型的,一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
    在这里插入图片描述

  2. 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK,这个可以使用ioctl()系统调用设置。这样做用户线程可以在发起IO请求后可以立即返回,如果该次读操作并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
    在这里插入图片描述

  3. IO多路复用(IO Multiplexing):IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题,此外poll、epoll都是这种模型。在该种模式下,用户首先将需要进行IO操作的socket添加到select中,然后阻等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
    在这里插入图片描述

  4. 信号驱动IO(signal driven IO):调用sigaltion系统调用,当内核中IO数据就绪时以SIGIO信号通知请求进程,请求进程再把数据从内核读入到用户空间,这一步是阻塞的。

  5. 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。“真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

  • 相比于IO多路复用模型,信号驱动IO和异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式。

  • IO复用多路复用包括 select模式poll模式epoll 模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用Java NIO异步I/O模型实现的网络聊天服务器程序的示例代码,可以接收并转发多个客户端程序之间通信的消息和数据: ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; public class ChatServer { private static Selector selector; private static ByteBuffer buffer = ByteBuffer.allocate(1024); public static void main(String[] args) { try { // 创建Selector和ServerSocketChannel selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.bind(new InetSocketAddress("localhost", 8888)); // 将ServerSocketChannel注册到Selector上,监听连接事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器已启动,等待客户端连接..."); while (true) { // 轮询已注册的Channel,等待IO事件发生 selector.select(); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isAcceptable()) { // 处理连接事件 SocketChannel channel = ((ServerSocketChannel) key.channel()).accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); System.out.println("客户端 " + channel.getRemoteAddress() + " 已连接!"); broadcastMessage("客户端 " + channel.getRemoteAddress() + " 已连接!"); } else if (key.isReadable()) { // 处理读事件 SocketChannel channel = (SocketChannel) key.channel(); buffer.clear(); int count = channel.read(buffer); if (count > 0) { buffer.flip(); String message = new String(buffer.array(), 0, count); System.out.println("客户端 " + channel.getRemoteAddress() + " 发来消息: " + message); broadcastMessage("客户端 " + channel.getRemoteAddress() + " 发来消息: " + message); } else if (count == -1) { // 客户端断开连接 channel.close(); System.out.println("客户端 " + channel.getRemoteAddress() + " 已断开连接!"); broadcastMessage("客户端 " + channel.getRemoteAddress() + " 已断开连接!"); } } } } } catch (IOException e) { e.printStackTrace(); } } // 广播消息给所有客户端 private static void broadcastMessage(String message) throws IOException { for (SelectionKey key : selector.keys()) { Channel channel = key.channel(); if (channel instanceof SocketChannel) { SocketChannel socketChannel = (SocketChannel) channel; socketChannel.write(ByteBuffer.wrap(message.getBytes())); } } } } ``` 该示例代码使用Java NIO提供的Selector、ServerSocketChannel、SocketChannel等类实现异步I/O模型。在main()方法中,创建Selector和ServerSocketChannel,并将ServerSocketChannel注册到Selector上,监听连接事件。在轮询已注册的Channel时,处理连接事件和读事件,并将读到的消息广播给所有客户端。使用ByteBuffer缓存读到的数据,并在读到换行符或达到缓存大小时进行处理。广播消息时,遍历所有已注册的Channel,并将消息写入SocketChannel中发送给客户端。 使用异步I/O模型可以提高服务器程序的并发性能和响应速度,因为异步I/O模型不需要为每个客户端请求创建一个线程,而是使用一个线程来处理多个客户端请求。此外,在处理读事件时,可以使用Java NIO提供的非阻塞I/O方式,提高程序性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值