目录

C++ select分析2

网上一篇博主写的select,十分透彻,参考linux下多路复用模型之Select模型
本文主要记录了select一些用法和自己写的一个用select的服务器端例子。

select函数的定义和参数的含义

首先,我们来看看select函数的定义和参数的含义:

1
int select( int nfds, fd_set FAR* readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout)

参数含义:
1. nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
2. readfds:(可选)指针,指向一组等待可读性检查的套接口。
3. writefds:(可选)指针,指向一组等待可写性检查的套接口。
4. exceptfds:(可选)指针,指向一组等待错误检查的套接口。
5. timeout:select()最多等待时间,对阻塞操作则为NULL。

返回值:
select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError获取相应错误代码。
1. 当返回为-1时,所有描述符集清0。
2. 当返回为0时,表示超时。
3. 当返回为正数时,表示已经准备好的描述符数。
select()返回后,在3个描述符集里,依旧是1的位就是准备好的描述符。这也就是为什么,每次用select后都要用FD_ISSET的原因。
select函数实现I/O多路复用,可以用来监视多个描述符,之后我们调用FD_ISSET函数确定具体是哪一个描述符准备好了。
那怎样才算准备好了呢?《unix环境高级编程》中,提到:

  1. 若对读集中的一个描述符进行的read操作不会阻塞,则认为此描述符是准备好的。
  2. 若对写集中的一个描述符进行的write操作不会阻塞,则认为此描述符是准备好的。
  3. 若对异常条件集中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。
  4. 对于读、写和异常条件,普通文件的文件描述符总是认为准备好的。

操作select函数,还需要以下几个函数配合。

  1. void FD_CLR(int fd, fd_set *set) // 清除set集合中描述符fd
  2. int FD_ISSET(int fd, fd_set *set) //判断set集合中描述符fd是否准备好
  3. void FD_SET(int fd, fd_set *set) //将描述符fd添加进集合set(其实是将某一位置1)。
  4. void FD_ZERO(fd_set *set) //将set集全部清除

接下来看一个服务器端运用select函数的例子。

从下面的程序可以看出,服务器端调用函数FD_SET( sockfd, &readfds)把套接字描述符sockfd加入读集合,调用select函数监听了socket套接字是否准备好。当客户端有连接请求时,select会返回一个正数,然后调用FD_ISSET(sockfd,&readfds)判断是哪一个描述符准备好。当然这里只能是socket,因为我们只向读集合添加了一个。接着调用accept接收请求,创建线程去处理客户端的请求。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*server.c*/

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

#define BUFFER_SIZE 4096
#define MAX_QUE_CONN_NM 5
#define PORT 6000
//#define MAXSOCKFD     10
#define FILE_NAME_MAX 512

void recv_mul_file(int sockfd);
void* pthread_func(void * arg);

int main(int argc,char* argv[])
{
    int sockfd;
    int sin_size = sizeof(struct sockaddr);
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */

    /*建立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);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);//将内存块(字符串)的前n个字节清零

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    /*绑定函数bind*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");

    /*调用listen函数*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    recv_mul_file(sockfd);
    close(sockfd);
    return 0;
}

void recv_mul_file(int sockfd)
{
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET( sockfd, &readfds);

    pthread_t tid;
    struct sockaddr_in client_sockaddr;
    int client_fd, sin_size = sizeof(struct sockaddr);
    int MAXSOCKFD = sockfd;

    while(1)
    {
        if(select(MAXSOCKFD + 1,&readfds,NULL,NULL,NULL) > 0)
        {
            if(FD_ISSET(sockfd,&readfds)>0)
            {
                if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, (socklen_t *)&sin_size)) == -1)
                {
                    perror("accept");
                    exit(1);
                }
                pthread_create(&tid, NULL, pthread_func, &client_fd);
            }
            FD_SET( client_fd, &readfds);
            MAXSOCKFD = (MAXSOCKFD > client_fd? MAXSOCKFD:client_fd);
        }
    }
}

void* pthread_func(void * arg)
{
    //recv file imformation
    int client_fd;

    char buff[BUFFER_SIZE];
    char filename[FILE_NAME_MAX];
    int count;
    bzero(buff,BUFFER_SIZE);
    client_fd = *(int *)arg;
    printf("recv from client,client_fd = %d\n",client_fd);
    count=recv(client_fd,buff,BUFFER_SIZE,0);   //把接受到到字符放在长度为BUFFER_SIZE的buff地址上,接收成功返回接收到到字节数目
    if(count<0)
    {
        perror("recv");
        exit(1);
    }
    strncpy(filename,buff,strlen(buff)>FILE_NAME_MAX?FILE_NAME_MAX:strlen(buff));//把filename地址上的内容复制到地址buff上,第三个参数表明复制多少个字节

    printf("Preparing recv file : %s\n",filename );

    //recv file
    FILE *fd=fopen(filename,"wb+"); //告诉函数库,打开的是一个二进制到可写文件,地址在指针filename
    if(NULL==fd)
    {
        perror("open");
        exit(1);
    }
    bzero(buff,BUFFER_SIZE); //缓冲区清0

    int length=0;
    while(length=recv(client_fd,buff,BUFFER_SIZE,0)) //这里是分包接收,每次接收4096个字节
    {
        if(length<0)
        {
            perror("recv");
            exit(1);
        }
        int writelen=fwrite(buff,sizeof(char),length,fd);//把从buff接收到的字符写入(二进制)文件中
        if(writelen<length)
        {
            perror("write");
            exit(1);
        }
        bzero(buff,BUFFER_SIZE); //每次写完缓冲清0,准备下一次的数据的接收
    }
    printf("Receieved file:%s finished!\n",filename );
    fclose(fd);
    close(client_fd);
    return 0;
}