IO多路复用---select详解及示例

一、IO多路复用
I/O复用使得程序能同时监听多个文件描述符,这使得程序的性能得到了很大程度的提高。
Linux下实现I/O复用的系统调用主要有select、poll、epoll。(这里注意:epoll是Linux系统所特有的),但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

二、select系统调用
select系统调用的用途:在一段时间内,监听文件描述符上的可读、可写、和异常时间的发生。本文主要介绍select系统调用API。

三、select API及主要函数说明

#include
int select(int nfds,fd_set* resdfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

参数说明:

  • nfds参数指定被监听的文件描述符的总量。通常被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
  • readfds、writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。
    这三个参数都是fd_set结构指针类型,fd_set结构体实际上仅包含一个整数数组,该数组的每一个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE来指定,这就限制了select能同时处理的文件名描述符的总量。
//主要使用以下函数来访问fd_set结构体的位
#include
FD_ZERO(fd_set *fdset);    //清除fdset的所有位
FD_SET(int fd,fd_set *fdset);      //设置feset的位fd也就是将新建立连接的fd加入到fdset里
FD_CLR(int fd,fd_set *fdset);     //清除fdset的位fd即就是删除fdset里面对应的fd
int FD_ISSET(int fd,fd_set *fdset);   //检测fdset的位fd是否被设置即就是判断指定描述符是否在集合中
  • timeout参数是用来设置select函数的超时时间,是一个timeval结构类型的指针,采用指针是因为内核将修改它以告诉应用程序select等待了多久。
struct timeval
{
   
     long tv.sec;  //秒数
     long tv.usec; //毫秒
}

由此可见,如果给timeout变量的tv.sec和tv.usec都传递0,则select将立即返回。如果给timeval传NULL,则select将一直阻塞,直到某个文件描述符就绪。

  1、select成功时返回就绪的文件描述符的总数;
  2、如果在超时时间内没有任何文件描述符就绪,select将返回0;
  3、select失败时返回-1并设置error。如果在select等待时期,程序接收到信号,则立刻返回-1,并设置error为EINTR。

四、select系统调用原理

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

  • 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

    一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

  • 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

    当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
    
  • 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

五、select示例:Client向Server发送消息,Server接收消息并原样发送给Client,Client再把消息输出到终端。

  • 网络中的地址结构
struct sockaddr_in
{
   
      int sin_family;//指代协议族,在socket编程中只能是AF_INET
      short sin_port://端口号
      struct in_addr sin_addr;//IP地址
}
struct in_addr
{
   
        u_int32_t  s_addr;
}
  • 主机字节序和网络字节序
    主机:有大端模式也有小端模式
    网络:大端模式
#include 
   unsigned long int   htonl( unsigned long int hostlong);//将主机的无符号长整形数转换成网络字节顺序。
   unsigned long short htons(unsigned long short hostlong) //把unsigned short类型从主机序转换到网络序
   unsigned long short ntohs(unsigned long short hostlong) //把unsigned short类型从网络序转换到主机序
   unsigned long int   ntohl(unsigned long int hostlong) //把unsigned long类型从网络序转换到主机序

server:

//myserver
#include  //sockaddr_in
#include   //socket
#include  //socket
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define BUFFER_SIZE 1024   //select需要通过宏定义来确定最大可以监听的文件描述符数量

struct PACKRT_HEAD
{
   
    /* data */
    int length;
};

class Server
{
   
private:
    

你可能感兴趣的:(网络编程--IO多路复用,c++,指针,linux)