UNP学习_I/O复用之select函数实现回射服务器

UNP学习_I/O复用之select函数实现回射服务器

1、I/O复用模型
  I/O复用是阻塞在select系统调用之上,而不是阻塞在真正的I/O系统调用之上。
UNP学习_I/O复用之select函数实现回射服务器_第1张图片

2、函数声明

#<sys/select.h>
#<sys/time.h>

int select(int maxfdpl, 
			fd_set* readset, //读事件集合
			fd_set* writeset, //写事件集合
			fd_set* exceptset, //异常事件集合
			const struct timeval *timeout
			);

struct timeval {
	long tv_sec;//秒
	long tv_usec;//微秒
};

                       //返回值:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
                       //具体函数介绍参考UNP-P127

3、select实现的tcp回射服务器

// File Name: select_server.c
// Author: AlexanderGan
// Created Time: Mon 20 Jul 2020 08:32:13 PM CST

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc, char* argv[]){
    if(argc < 2)
    {
        printf("eg: ./a.out IP port\n");
    }
    int port = atoi(argv[1]);

    struct sockaddr_in serv_addr, client_addr;
    socklen_t serv_len = sizeof(serv_addr);
    socklen_t cli_len = sizeof(client_addr);
    
    //创建套接字
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    printf("lfd = %d\n",lfd);
    
    //初始化服务器
    memset(&serv_addr,0,serv_len);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);
    
    //端口复用(防止在一端处于TIME_WAIT状态还未释放端口导致端口被占用的问题)
    int flag = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

    //绑定IP和端口
    if(bind(lfd,(struct sockaddr*)&serv_addr,serv_len) < 0){
        perror("bind error");
        exit(1);
    }
    
    //设置同时监听的最大个数
    listen(lfd,36);
    printf("Start accept !\n");
    //设定最大文件描述符
    int maxfd = lfd;
    //文件描述符读集合
    fd_set reads,temp;
    //初始化读fd_set
    FD_ZERO(&reads);
    FD_SET(lfd,&reads);
    
    while(1){
        temp = reads;
        int ret = select(maxfd+1,&temp,NULL,NULL,NULL);
        if(ret == -1)
        {
            perror("select error");
            exit(1);
        }
        //客户端发起了新的连接
        if(FD_ISSET(lfd,&temp)){
            //接受连接,accept不阻塞
            int cfd = accept(lfd,(struct sockaddr*)&client_addr,&cli_len);
            if(cfd == -1){
                perror("accept error");
                exit(1);
            }
            
            //接受成功后将客户端地址和端口打印出来                   
            char ip[64];//存放ip表达式       
            printf("new client IP:%s, Port: %d\n",
            inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,sizeof(ip)),
            ntohs(client_addr.sin_port));
            
            //将cfd加入到待检测的读集合
            FD_SET(cfd,&reads);
            
            //更新最大文件描述符
            maxfd = maxfd < cfd ? cfd:maxfd;
        }
        
        //已经连接的客户端有数据到达
        for(int t = lfd+1; t <= maxfd; ++t){
            if(FD_ISSET(t,&temp)){
                char buf[1024] = {0};
                int len = recv(t,buf,sizeof(buf),0);
                if(len == -1){
                    perror("recv error");
                    exit(1);
                }
                else if(len == 0)
                {
                    printf("客户端已经断开了连接\n");
                    close(t);
                    FD_CLR(t,&reads);
                }
                else{
                    //正常接收数据
                    printf("recv buf is : %s\n",buf);
                    send(t,buf,strlen(buf)+1,0);
                }
            }
        }
    }
    close(lfd);
    return 0;
} 

4、select函数的优缺点

select优点:
select的最大优势是可以等待多个描述符就绪。
如:在调用recv函数之前,先调用select函数,如果系统没有可读数据那么select函数就会阻塞在这里。当系统存在可读或可写数据时,select函数返回,就可以调用recv函数接 收数据了。
可以看出使用select模型,需要两次调用函数。第一次调用select函数第二次accept函数。使用该模式的好处是:可以等待多个套接字。

select缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select函数支持的文件描述符数量由FD_SETSIZE设置,默认值是1024。

你可能感兴趣的:(Linux网络编程)