Linux——I/O复用(select的用法)

一、I/O复用

定义:I/O 复用使得程序能同时监听多个文件描述符,这对于提高程序的性能至关重要。

网络程序在下列情况下需要使用 I/O 复用技术:

  • ◼ TCP 服务器同时要处理监听套接字和连接套接字。
  • ◼ 服务器要同时处理 TCP 请求和 UDP 请求。
  • ◼ 程序要同时处理多个套接字。
  • ◼ 客户端程序要同时处理用户输入和网络连接。
  • ◼ 服务器要同时监听多个端口。

  •     需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当 多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一 个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以 配合使用多线程或多进程等编程方法。

Linux——I/O复用(select的用法)_第1张图片

二、I/O复用的系统调用

I/O复用方法的功能:检查若干个描述符有没有关系的事件产生。

1、select

(1)在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

(2)select参数

①nfds是指定被监听的文件描述符的总数目;

   fd_set 是结构指针类型:

②readfds指向可读事件对应的文件描述符集合;

③writefds是指向可写事件对应的文件描述符集合;

④exceptfds是指向异常事件对应的文件描述符集合。

fd_set结构体定义:

Linux——I/O复用(select的用法)_第2张图片

⑤timeout是用来设置select函数的超时时间。它是一个timeval结构类型的指针,采用指针参数是因为内核将修改它,用来告诉应用程序select等待了多久。

(3)sclect成功时返回就绪(可读、可写和异常)文件描述符的总数。

  • 如果在超时时间内没有任何文件描述符就绪,select将返回0。select失败时返回-1并设置errno。
  • 如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为 EINTR。

2、文件描述符就绪条件

(1)在网络编程中,以下情况socket可读

  • ①socket内核接收缓存区中的字节数大于或等于其低水位标记SO RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
  • ②socket通信的对方关闭连接。此时对该socket的读操作将返回0。监听socket上有新的连接请求。
  • ③socket 上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

(2)在网络编程中,以下情况socket可写

  • ①socket内核发迭缓存区中的可用字节数大于或等于其低水位标记so_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
  • ②socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE 信号。
  • ③socket使用非阻塞connect连接成功或者失败(超时)-之后。
  • ④socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
  • 注意:select能处理的异常情况只有一种:socket上接收到带外数据。

3、select的用法示例

//select用法
#include
#include
#include
#include
#include
#include


#define STDIN 0

int main()
{
    int fd=STDIN;//键盘对应的描述符
    fd_set fdset;//收集描述符
    
    while(1)
    {
        FD_ZERO(&fdset);//清空集合
        FD_SET(fd,&fdset);//添加描述符(fd)到集合
        struct timeval tv={5,0};
        int n=select(fd+1,&fdset,NULL,NULL,&tv);
        if(n==-1)
        {
            printf("select err\n");//退出
        }
        else if(n==0)
        {
            printf("time out\n");//超时
        }
        else
        {
            //检测描述符上是否有数据,如果为真,就是有数据
            if(FD_ISSET(fd,&fdset))//按位与,同1为1
            {
                char buff[128]={0};
                read(fd,buff,127);
                printf("buff=%s\n",buff);
            }
        }  
    }      
}

4、使用select实现的TCP服务 

//tcp服务器端
#include
#include
#include
#include
#include
#include
#include
#include
#include


#define MAXFD 10

void fds_init(int fds[])
{
    for(int i=0;imaxfd)//找到数组中描述符的最大值
            {
                maxfd=fds[i];
            }
        }
   
        struct timeval tv={5,0};

        int n=select(maxfd+1,&fdset,NULL,NULL,&tv);//阻塞五秒钟
        if(n==-1)
        {
            printf("select err\n");
        }
        else if(n==0)
        {
            printf("time out\n");
        }
        else
        {
            for(int i=0;i
//客户端cli
#include
#include
#include
#include
#include
#include
#include

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        printf("socket err\n");
        exit(0);
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(6000);//1024以内是专用;我们用4096以上,root 
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

    int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//三次握手开始
    if(res==-1)
    {
        printf("connect failed\n");
        exit(0);
    }

    //从键盘获取数据
    while(1)
    {
        printf("input: \n");
        char buff[128]={0};
        fgets(buff,128,stdin);
        if(strncmp(buff,"end",3)==0)
        {
            break;
         }

        send(sockfd,buff,strlen(buff)-1,0);
        memset(buff,0,128);//清空
        int n=recv(sockfd,buff,127,0);
        if(n<=0)//判断服务器是否关闭
        {
            break;
        }
        printf("buff=%s\n",buff);
    }

    close(sockfd);

}

如有错误,敬请指正。

您的收藏与点赞都是对我最大的鼓励和支持!

你可能感兴趣的:(Linux,linux,网络,服务器,c#)