select IO复用测试

服务器端测试代码如下:


int main()
{

    int maxfd,s_ret,i;
    int client_fd;
    int sockfd;//socket返回值
    struct sockaddr_in server_sockaddr,client_sockaddr;
    int sin_size, recvbytes;
    char ipstr[16];
    char cmd[BUFFER_SIZE];
    sin_size=sizeof(struct sockaddr);

    fd_set r_set,listen_set;//定义一个集合

    printf("\t\t****************author by sastar****************\n");
    /*建立socket连接*/
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);  

    /*设置sockaddr_in 结构体中相关参数,设置套接字属性*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    //inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr)//单独设置可接受的ip
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    //bzero(&(server_sockaddr.sin_zero), 8);
    memset(&(server_sockaddr.sin_zero),0,8);

    int j = 1;/* 使得重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    /*绑定函数bind*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)//将server_sockaddr结构体的ip和端口号公布
    {
        perror("bind 1");
        exit(1);
    }
    printf("Bind success!\n");
    /*调用listen函数*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("listening...\n");

    FD_ZERO(&r_set);
    FD_SET(sockfd,&r_set);//将socket放入可读集合中
    maxfd=sockfd+1;//描述符的最大值

    while(1)
    {
        listen_set=r_set;
        s_ret=select(maxfd,&listen_set,NULL,NULL,NULL);
        printf("%d is select detected\n", s_ret);//检测到的就绪fd数量
        if(s_ret<0)
        {
            perror("select error");
            exit(-1);
        }
        else
        {
            for(i=0;iif(FD_ISSET(i,&listen_set))//判断i是否是在可读\可写集合中
                {
                    printf("%d is ready\n", i);//哪个fd就绪
                    if(i==sockfd)
                    {
                        /*调用accept函数,等待客户端的连接*/
                        if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size)) == -1)//将客户端传入的值传入client_sockaddr,长度为struct sockaddr
                        {
                            perror("accept");
                            exit(1);
                        }
                        else
                        {
                            inet_ntop(AF_INET,(char *)&client_sockaddr.sin_addr.s_addr,ipstr,16);
                            printf("\nnew client id=%d\nip:%s\nsrc port:%d\n",client_fd,ipstr,client_sockaddr.sin_port);
                            FD_SET(client_fd,&r_set);//将描述符添加到读写集合中
                            //FD_SET(client_fd,&write_set);
                            maxfd=(maxfd>(client_fd+1)) ? maxfd : (client_fd+1);
                        }
                    }
                    else
                    {
                        memset(cmd,0,sizeof(cmd));
                        if ((recvbytes = recv(i,cmd,sizeof(cmd),0)) == -1)//接受客户端发送的命令
                        {
                            perror("recv");
                            close(i);
                            FD_CLR(i,&r_set);//将描述符从读写集合中清除
                        }
                        //printf("%s\n", buf);
                        if(strncmp(cmd,"servls",6)==0)
                        {
                            lsfile(i,cmd);
                        }
                        else if(strncmp(cmd,"down ",5)==0)
                        {
                            sendfile(i,cmd);
                        }
                        else if(strncmp(cmd,"up ",3)==0)
                        {
                            recvfile(i,cmd);
                        }
                        else if(strncmp(cmd,"exit",4)==0)
                        {
                            close(i);
                            FD_CLR(i,&r_set);//将描述符从读写集合中清除
                        }
                    }
                }
                else{
                    printf("%d is not ready\n", i);//未就绪的是哪些fd
                }
            }
        }
    }
    close(sockfd);
    exit(0);
}

在测试过程中,发现每次客户端connect时,select总是检测到fd为3的文件描述符就绪。如下图:左侧上下两个终端窗口为客户端,右侧终端窗口为服务器端
select IO复用测试_第1张图片
可以看到描述符4/5是新连接的客户端,那3是什么呢?每次有新的客户端连接select就会检测到。于是通过查看服务器进程的fd(命令:lsof -p <服务器PID号>),如下图:
select IO复用测试_第2张图片
可以看到,FD那一列中FD0、1、2是系统自带的stdin、stdout、stderr描述符,FD3(即每次新的客户端连接会触发该FD)是LISTEN描述符,端口号是上述代码中设置的,FD4、FD5是新的客户端与服务器建立的文件描述符(通过accept函数),以后的客户端与服务器的数据传输都通过FD4/FD5传输。所以每次客户端传数据到服务器,都是FD4或者FD5被select检测到,如下图:
select IO复用测试_第3张图片
可以看到此时打印的消息是“FD5 is ready”。

你可能感兴趣的:(Linux)