Socket编程整理笔记(二)

【1】UNIX/Linux下主要的四种IO模型的特点

(1)阻塞式IO   :最简单、最常用;效率低

        阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。

        缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。

        前面学习的很多读写函数在调用过程中会发生阻塞。

常见:

•       读操作中的read、recv、recvfrom

•       写操作中的write、send

•       其他操作:accept、connect

例如:读阻塞

以read函数为例:

•       进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。

•       它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。

•       经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。

•       如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。

写阻塞

在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。

这时,写操作不进行任何拷贝工作,将发生阻塞。

一旦发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。

UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。

       (2)非阻塞式IO :可以处理多路IO;需要轮询,浪费CPU资源

•       当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”

•       当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。

•       应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。

•       这种模式使用中不普遍。

    例如:

  Recv函数最后一个参数写为0,为阻塞,写为MSG_DONTWAIT:表示非阻塞。

非阻塞,循环检测,是否有数据发过来,轮询消耗CPU资源。

       (3)IO多路复用 :服务器可以响应多个客户端发来的数据。

       (4)信号驱动IO :异步通知模式,需要底层驱动的支持

【2】文件描述符属性

       通过以下函数设置文件描述符的属性

       int fcntl(int fd, int cmd, long arg);

      int flag;

      flag = fcntl(0, F_GETFL); // 1.获取该文件描述符的原属性

      flag |= O_NONBLOCK;   //2. 修改对应的位

      fcntl(0, F_SETFL, flag); // 3. 写回去

【3】I/O多路复用

Ÿ  应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;

Ÿ  若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;

Ÿ  若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;

Ÿ  比较好的方法是使用I/O多路复用。其基本思想是:

•       先构造一张有关描述符的表,然后调用一个函数。

•       当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。

•       函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

select  poll  epoll

其基本流程是:

       1. 先构造一张有关文件描述符的表(集合、数组);

       2. 将你关心的文件描述符加入到这个表中;

       3. 然后调用一个函数。 select / poll

       4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候

                     该函数才返回(阻塞)。

       5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);

       6. 做对应的逻辑处理;

       注意:

       ****select函数返回之后,会自动将除了产生事件的文件描述符以外的位全部清空;

这样当你想下一次监听其它的事件的话,都被清空了,就监听不到了,所以写代码的时候,要定义一个临时的集合,所以在调用select之前要把原有的readfds付给临时tempfds。

【4】应用

       /* According to POSIX.1-2001 */

       #include <sys/select.h>

       /* According to earlier standards */

       #include <sys/time.h>

       #include <sys/types.h>

       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,

                  fd_set *exceptfds, struct timeval *timeout);

          功能:select用于监测是哪个或哪些文件描述符产生事件;

          参数:nfds:    监测的最大文件描述个数

(这里是个数,使用的时候注意,与文件中最后一次打开的文件

描述符所对应的值的关系是什么?)

                     readfds:  读事件集合; //读(用的多)

                     writefds: 写事件集合;  //NULL表示不关心

                  exceptfds:异常事件集合; 

                     timeout:      超时检测 1

                            如果不做超时检测:传 NULL

                                   select返回值:  <0 出错

                                                               >0 表示有事件产生;

                            如果设置了超时检测时间:&tv

                                   select返回值:

                                                        <0 出错

                                                        >0 表示有事件产生;   

                                                        ==0 表示超时时间已到;            

                                  

            struct timeval {

               long    tv_sec;         /* seconds */

               long    tv_usec;        /* microseconds */

           };

       void FD_CLR(int fd, fd_set *set);  //将set集合中的fd清除掉

       int  FD_ISSET(int fd, fd_set *set); //判断fd是否存在于set集合中

       void FD_SET(int fd, fd_set *set); //将fd加入到集合中

       void FD_ZERO(fd_set *set);   //清空集合

       Select特点

       1. 一个进程最多只能监听1024个文件描述符 (千级别)

       2. select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源);

       3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(一个进程0~4G,0~3G是用户态,3G~4G是内核态,拷贝是非常耗时的);

       poll特点

    1. 优化文件描述符个数的限制;(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组的大小为1,如果想监听100个,那么这个结构体数组的大小就为100,由程序员自己来决定)

       2. poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

       3. poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);

    参数:

    struct pollfd *fds

    关心的文件描述符数组struct pollfd fds[N];

       nfds:个数

       timeout: 超时检测

       毫秒级的:如果填1000,1秒

        如果-1,阻塞

                             

应用举例:

    我想检测是键盘事件(标准输入 文件描述如为0 ),

还是鼠标事件(文件描述符是/dev/input/mouse1);   

1. 创建一个结构体数组

              struct pollfd fds[2];

2. 将你关心的文件描述符加入到结构体成员中

    struct pollfd {

       int   fd;         // 关心的文件描述符;

       short events;     // 关心的事件,读

       short revents;    // 如果产生事件,则会自动填充该成员的值(当文件描述符产生事件之后,这个函数会自动的把读事件或者写事件填充到这个结构体revents的成员里面.如果产生读事件,就给revents一个读事件,如果写事件,一样就给revents一个写事件。)

              };

              // 键盘

              fds[0].fd = 0;

              fds[0].events = POLLIN; (POLLIN表示读事件)  

              //鼠标

              fds[1].fd = mouse1_fd;

              fds[1].events = POLLIN;

    把关心的文件描述符添加到集合当中,把关心的事件也要添加进来。      

3. 调用poll函数

              如果返回表示有事件产生;

              poll(fds,2,1000)

4. 判断具体是哪个文件描述符产生了事件

       if(fds[0].revents == POLLIN)

       {          ....

       }

epoll(未完)

性能更好,知识点可发散,待整理后更新

      

      

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值