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 函数需要传递监视对象的信息