Socket学习系列之什么是IO?怎么处理IO

[TOC]

# 一、什么是IO

unix世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO、管道、终端,对我们来说,一切都是文件,一切都是流。

在信息 交换的过程中,我们都是对这些流进行数据的收发操作,简称为I/O操作(input and output)

往流中读出数据,系统调用read,写入数据,系统调用write。

不过话说回来了 ,计算机里有这么多的流,我怎么知道要操作哪个流呢?对,就是文件描述符,即通常所说的fd,一个fd就是一个整数,所以,对这个整数的操作,就是对这个文件(流)的操作。我们创建一个socket,通过系统调用会返回一个文件描述符,那么剩下对socket的操作就会转化为对这个描述符的操作。

# 二、IO中的可读/可写事件

可读事件
  1. socket内核接收缓冲区中的可用字节数大于或等于其低水位SO_RCVLOWAT;
  2. socket通信的对方关闭了连接,这个时候在缓冲区里有个文件结束符EOF,此时读操作将返回0;
  3. 监听socket的backlog队列有已经完成三次握手的连接请求,可以调用accept;
  4. socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误。
可写事件
  1. socket的内核发送缓冲区的可用字节数大于或等于其低水位SO_SNDLOWAIT
  2. socket的写端被关闭,继续写会收到SIGPIPE信号
  3. 非阻塞模式下,connect返回之后,发起连接成功或失败
  4. socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误

# 三、同步与异步的区别

同步

同步就是调用者进行调用后,在没有得到结果之前,该调用一直不会返回,但是一旦调用返回,就得到了返回值,同步就是指调用者主动等待调用结果。也就是必须一件一件事做,等前一件做完了才能做下一件事。

异步

异步是调用者执行调用之后直接返回,调用者没有得到具体的调用结果。等到有返回值时,由被调用者通过状态,通知来通知调用者。

同步和异步关注的是消息通信机制,所以二者在消息通信机制上有所不同,一个是调用者检查调用结果是否就绪,一个是被调用者通知调用者结果就绪

# 四、阻塞IO 与非阻塞IO的区别

阻塞IO

当调用read时,如果没有数据收到,那么线程或者进程就会被挂起,陷入阻塞状态。(线程或者进程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)

非阻塞IO

非阻塞IO通过fcntl(POSIX)或ioctl(Unix)设为非阻塞模式。这时,当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,如EWOULDBLOCK。

这样是不会阻塞线程了,但是你还是要不断的轮询来读取或写入。

# 五、什么是IO多路复用

通常IO操作都是阻塞的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。

I/O多路复用就是通过提供一种机制,让一个进程/线程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。

我们使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果文件描述符读或写就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

I/O多路复用与利用多进程和多线程技术来提升连接数的实现 相比,IO多路复用技术的最大优势是系统开销小,系统不必创建过多的进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll。但select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

总结:IO多路复用是指内核提供一种机制让进程可以指定一个或者多个IO条件,一旦发现进程指定的IO条件准备完毕,它可以给该进程一个通知 。

# 六、附: 单进程/线程处理IO、多进程/线程处理IO、IO多路复用处理IO 对比浅显易懂的例子

  1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。

  2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。

  3. 第三种选择,你站在讲台上等,谁解答完谁举手。

    这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等

    此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型。

    Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。

    此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。

# 文章参考

发表评论

电子邮件地址不会被公开。 必填项已用*标注