select函数

学习目标

  • 了解select函数的各参数的作用,能够在程序设计中正确使用select函数
  • 使用select函数,编写一个简单socket服务器程序,可支持多客户端连接

1、select函数机制

select函数允许程序同时在等待多个底层文件描述符输入的到达,并且只有在一个或多个等待文件描述符的事件发生时或设置等待时间超时的时候阻塞返回。

2、select函数的原型

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,      //----->①
                  fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);                                //------>②
int  FD_ISSET(int fd, fd_set *set);                              //------>③
void FD_SET(int fd, fd_set *set);                                //------>④
void FD_ZERO(fd_set *set);                                       //------>⑤

①nfds::指定需要测试的文件描述符数目,测试文件描述符范围从0 ~ nfds-1

 readfds、wirtefds、exceptfds: 指定监测的读、写、异常等文件描述符集合。如当其监控的readfds的文件描述符集合中有文件描述符可读,则select阻塞返回

 timeout: 设置阻塞等待的超时时间,或此参数为空,则代表这个调用会永久阻塞,直到有监控文件描述符返回。timeout对应结构体类型如下:

 struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

②FD_CLR:在传入文件描述集合中清除由参数fd传递的文件描述符

③FD_ISSET:判断由参数传递的fd文件描述符是否在传入的文件描述符集合中

④FD_SET:和FD_CLR函数相反,在传入文件描述集合中设置由参数fd传递的文件描述符。文件描述集合作为参数传入select中,select监控fd文件描述符

⑤FD_ZERO:将传入文件描述符集合清空

注意:fd_set结构中可以容纳的文件描述符的最大数目由常量FD_SETSIZE指定

3、使用select,编写支持多客户端连接的简单socket服务器

        编写一个支持多客户连接的服务器,当有客户端连接和断开连接时,打印客户端的IP和端口,当接收客户端发送消息时,将接收到消息转成大写字母再返回给客户端。其大概原理是,select监测相关套接字,当判断是服务器套接字有数据可读时,说明有客户端连接到达,调用accept函数接收客户端连接,并将其加入到监控字符集中。当判断是已连接的客户端套接字有数据可读时,再进一步判断是否是断开连接请求,如是断开连接请求则关闭该套接字,并将其从监控字符集中删除,如果不是,则响应数据处理请求。

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define SERVER_PORT        8080
#define MAXLINE            4096
#define INET_ADDRSTRLEN    16

int main(int argc, char **argv)
{
    int server_sockfd, client_sockfd, tmp_fd;
    int ret, addrlen, ready_fd_nums, bytes, i;
    
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];          /* #define INET_ADDRSTRLEN 16 */
    
    fd_set server_fd_set, tmp_fd_set;
    
    struct sockaddr_in server_addr, client_addr;
    
    /* 创建一个服务器套接字 */
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(server_sockfd == -1)
    {
        printf("create a socket failed...\n");
        exit(EXIT_FAILURE);
    }
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);
    
    //绑定服务器本地地址
    addrlen = sizeof(server_addr);
    ret = bind(server_sockfd, (struct sockaddr *)&server_addr, addrlen);
    if(ret == -1)
    {
        printf("bind server address failed...\n");
        exit(EXIT_FAILURE);
    }
    
    /* 默认最大128 */
    listen(server_sockfd, 20);
    
    /* 初始化select函数使用的fd_set文件描述符集合 */
    FD_ZERO(&server_fd_set);
    FD_SET(server_sockfd, &server_fd_set);
    
    while(1)
    {
        tmp_fd_set = server_fd_set;
        
        ready_fd_nums = select(FD_SETSIZE, &tmp_fd_set, NULL, NULL, NULL);
        for(tmp_fd = 0; tmp_fd < FD_SETSIZE; tmp_fd++)
        {
            if(FD_ISSET(tmp_fd, &tmp_fd_set))
            {
                /* 服务器端套接字 */
                if(tmp_fd == server_sockfd)
                {
                    addrlen = sizeof(client_addr);
                    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &addrlen);
                    FD_SET(client_sockfd, &server_fd_set);
                    printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));
                }
                else
                {
                    /* 获取接收缓冲区字节数 */
                    ioctl(tmp_fd, FIONREAD, &bytes);
                    
                    if(bytes== 0)
                    {
                        /* 关闭连时的处理 */
                        close(tmp_fd);
                        FD_CLR(tmp_fd, &server_fd_set);
                        printf("disconnect from %s at PORT %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));
                    }
                    else
                    {
                        /* 接收客户端发送数据处理,转成大写,再返回 */
                        if(bytes > MAXLINE)
                            bytes = MAXLINE;
                        read(tmp_fd, buf, bytes);
                        for(i = 0; i < bytes; i++)
                            buf[i] = toupper(buf[i]);
                        write(tmp_fd, buf, bytes);
                    }
                }
            }
        }
    }
    close(server_sockfd);
    exit(EXIT_SUCCESS);
    
}

使用5个网络调试助手连接,运行效果如下图:

posted on 2020-07-19 18:12  quinoa  阅读(334)  评论(0编辑  收藏  举报