• Linux 下select 网络模型


    select 是一个 I/O复用模型

    select 函数主要调用步骤

    1. 设置文件描述符
    2. 设置检查范围
    3. 设置超时
    4. 调用 select 函数
    5. 调用结果

    fd_set 的一些操作都由以下宏完成

    #define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)
    #define	FD_CLR(fd, fdsetp)	__FD_CLR (fd, fdsetp)
    #define	FD_ISSET(fd, fdsetp)	__FD_ISSET (fd, fdsetp)
    #define	FD_ZERO(fdsetp)		__FD_ZERO (fdsetp)
    
    • FD_SET(fd, fdsetp) 设置文件描述符
    • FD_CLR(fd, fdsetp) 移除文件描述符
    • FD_ISSET(fd, fdsetp) 若fdsetp 指向变量包含fd 则返回‘真’
    • FD_ZERO(fdsetp) 将fdsetp 所有位初始化0

    下面是select 的函数定义

    /* Check the first NFDS descriptors each in READFDS (if not NULL) for read
       readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
       (if not NULL) for exceptional conditions.  If TIMEOUT is not NULL, time out
       after waiting the interval specified therein.  Returns the number of ready
       descriptors, or -1 for errors.
    
       This function is a cancellation point and therefore not marked with
       __THROW.  
       */
    extern int select (int __nfds, fd_set *__restrict __readfds,
    		   fd_set *__restrict __writefds,
    		   fd_set *__restrict __exceptfds,
    		   struct timeval *__restrict __timeout);
    
    /* A time value that is accurate to the nearest
       microsecond but also has a range of years.  */
    struct timeval
    {
      __time_t tv_sec;		/* Seconds.  */
      __suseconds_t tv_usec;	/* Microseconds.  */
    };
    

    利用select I/O 复用服务端

    未利用Socket套接字实现服务端和客户端的模型如下:
    服务端
    1. 建立套接字 socket
    2. 绑定端口 bind
    3. 监听客户端请求状态 listen
    4. 受理连接请求 accept
    5. 响应客户端 read/write
    6. 关闭 close

    1.socket()-> 2.bind() ->3.listen() ->4.accept() -> 5.read()/write() ->6.close()

    客户端
    1. 建立套接字 socket
    2. 向服务端发送连接请求 connect
    3. 与服务端交互 write.read
    4. 关闭

    1.socket()->2.connect()->3.read()/write()->4.close()

    利用select 实现服务端的模型如下:
    1. 建立套接字
    2. 绑定端口
    3. 监听客户端请求状态
    4. 调用select模型
      • 设置文件描述符
      • 设置检查范围
      • 设置超时
      • 调用 select 函数
    5. 遍历所有文件描述符,检查是否有发生变化
      • 如果是服务端套接字发生变化,说明有新的连接,调用accept 函数受理,增加检查范围,刷新文件描述符大小
      • 如果不是服务端套接字发生变化,则相应客户端
    6. 关闭

    代码如下:

    selectServer.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <sys/select.h>
    
    #define BUF_SIZE 100
    void error_handling(char *message)
    {
        fputs(message, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    int main(int argc, char *argv[])
    {
        int serv_sock, clnt_sock;
        struct sockaddr_in serv_adr, clnt_adr;
        struct timeval timeout;
        fd_set reads, cpy_reads;
        socklen_t adr_sz;
        int fd_max, str_len, fd_num, i;
        char buf[BUF_SIZE];
        if (argc != 2)
        {
            printf("Usage : %s <port>
    ", argv[0]);
            exit(1);
        }
        serv_sock = socket(PF_INET, SOCK_STREAM, 0);
        memset(&serv_adr, 0, sizeof(serv_adr));
        serv_adr.sin_family = AF_INET;
        serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_adr.sin_port = htons(atoi(argv[1]));
    
        if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
            error_handling("bind error");
        if (listen(serv_sock, 5) == -1)
            error_handling("listen error");
    
        FD_ZERO(&reads);
        FD_SET(serv_sock, &reads); 
        fd_max = serv_sock;
    
        while (1)
        {
            cpy_reads = reads;
            timeout.tv_sec = 5;
            timeout.tv_usec = 5000;
    
            if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1) //开始监视,每次重新监听
                break;
            if (fd_num == 0)
                continue;
    
            for (i = 0; i < fd_max + 1; i++)
            {   
                if (FD_ISSET(i, &cpy_reads)) 
                {
                    // printf("FD_ISSET(i, &cpy_reads)
    ");
                    // printf("i=%d
    ",i);
                    // printf("serv_sock=%d
    ",serv_sock);
                    if (i == serv_sock) 
                    {
                        adr_sz = sizeof(clnt_adr);
                        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
    
                        FD_SET(clnt_sock, &reads); 
                        if (fd_max < clnt_sock)
                            fd_max = clnt_sock;
                        printf("Connected client: %d 
    ", clnt_sock);
                    }
                    else 
                    {
                        str_len = read(i, buf, BUF_SIZE); 
                        if (str_len == 0)
                        {
                            FD_CLR(i, &reads);
                            close(i);
                            printf("closed client: %d 
    ", i);
                        }
                        else
                        {
                            printf("Message from client: %s
    ", buf); 
                            write(i, buf, str_len);
                        }
                    }
                }
            }
        }
        close(serv_sock);
        return 0;
    }
    
    
    
    
    

    client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    #define BUF_SIZE 100
    void error_handling(char *message)
    {
        fputs(message, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    
    int main(int argc, char *argv[])
    {
        int sock;
        char message[BUF_SIZE]="Hello World";
        int str_len;
        struct sockaddr_in serv_adr;
    
        if (argc != 3)
        {
            printf("Usage : %s <IP> <port>
    ", argv[0]);
            exit(1);
        }
    
        sock = socket(PF_INET, SOCK_STREAM, 0);
        if (sock == -1)
            error_handling("socket error");
    
        memset(&serv_adr, 0, sizeof(serv_adr));
        serv_adr.sin_family = AF_INET;
        serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_adr.sin_port = htons(atoi(argv[2]));
    
        if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
            error_handling("connect error!");
        else
            puts("Connected...........");
    
      
        write(sock, message, strlen(message));
        str_len = read(sock, message, BUF_SIZE - 1);
        message[str_len] = 0;
        printf("Message from server: %s
    ", message); 
        close(sock);
        return 0;
    }
    
    
    
    

    运行结果

    Connected client: 4 
    Message from client: Hello World
    closed client: 4 
    
    Connected...........
    Message from server: Hello World
    
    

    select 优点:

    • 平台兼容性好

    select 不足之处:

    • 针对文件描述符的遍历
    • 调用select 函数需要传递监视对象的信息
  • 相关阅读:
    sscanf()
    分享:Python字符编码详解
    STL priority_queue使用
    google maps 控件controller
    Google Maps Overlays叠加层
    java JDBC配置和使用
    转:总结java的interface和abstract class
    java 多线程 之 生产者和消费者
    一个简单的marker和infowindow
    java Nested Classes
  • 原文地址:https://www.cnblogs.com/arvinhuang/p/12672843.html
Copyright © 2020-2023  润新知