Linux c编程之IO复用机制

一、说明

  在实际网络程序中,比如服务器程序,需要使用IO复用机制来处理多个客户端的连接和数据收发。Linux系统下常用的IO复用机制有三种:select、poll、epoll。
  poll是Linux中的字符设备驱动中的一个函数。Linux 2.5.44版本后,poll被epoll取代。
  select用于监视文件描述符的变化情况
  epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
  因此,在实际应用中,尤其是大型网络程序中,都是使用epoll这种IO复用机制。

二、epoll机制

2.1 常用API

2.1.1 epoll_create()

#include <sys/epoll.h>

int epoll_create(int size);

作用:创建epoll 句柄
参数说明:
  size: 用来告知内核需要开辟支持多少个描述符的空间。主要是初始化的空间,如果用户添加的描述符超过初始空间后,内核会自动增加空间。在Linux 2.6.8版本后,该参数是被忽略的,但是要保证大于0。
返回值:
  成功返回一个非负的文件描述符。失败时,返回-1,errno将被设置。

2.1.2 epoll_ctl()

int epoll_ctl(int epfd, int op, int fd,
             struct epoll_event *event);

作用:管理描述符事件
返回值:
  成功返回一个非负的文件描述符。失败时,返回-1,errno将被设置。
参数说明:
  epfd: epoll_create返回的描述符
  op: 操作,有如下取值:
    EPOLL_CTL_ADD: 添加(注册)描述符对应的事件
    EPOLL_CTL_MOD: 修改描述符对应的事件
    EPOLL_CTL_DEL: 移除(解注册)描述符对应的事件
  event: 事件信息

epoll_event结构体如下:
   struct epoll_event {
     uint32_t events; /* Epoll events /
     epoll_data_t data; /
User data variable */
  };
events: 监听的事件,是按位设置的变量,类型如下:

  EPOLLIN: 读事件
  EPOLLOUT: 写事件
  EPOLLRDHUP (since Linux 2.6.17):对端断开连接
  EPOLLPRI:紧急数据读事件
  EPOLLERR: 错误发生事件
  EPOLLHUP: 挂起事件
  EPOLLET: 边沿触发

data: 用户数据,用于关联fd及其它相关的用户信息。data是一个联合体。
   typedef union epoll_data {
     void *ptr;
     int fd;
     uint32_t u32;
     uint64_t u64;
   } epoll_data_t;

2.1.3 epoll_wait()

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

作用: 等待发生的I/O事件
参数说明:
  epfd: epoll_create返回的描述符
  events:等待的事件数组
  maxevents: 等待的事件最大数量
  timeout: 阻塞等待有事件发生的时间,单位是毫秒。 大于0时,表示阻塞等待有事件发生的时间; -1表示一直阻塞,直到有事件发生; 0表示不管有没有事件发生,都立即返回。
返回值:
  成功时返回I/O事件的数量,在超时时返回0. 失败时,返回-1, errno将被设置

2.2 示例分析

2.2.1 最简单的例子

server_udp_epoll.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>

#define SERVER_PORT 9999

int server_udp()
{
    int ret = 0;
    int socket_fd = -1;
    int addr_len = 0;
    unsigned int value = 1;
    struct sockaddr_in server_addr;

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        printf("%s: socket failed\n", __FUNCTION__);
        goto on_error;
    }

    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
            (void *)&value, sizeof(value)) < 0) {
        printf("%s: Fail to setsockopt\n", __FUNCTION__);
        goto on_error;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    addr_len = sizeof(server_addr);
    ret = bind(socket_fd, (const struct sockaddr *)&server_addr, addr_len);
    if (ret < 0) {
        printf("%s: bind failed\n", __FUNCTION__);
        goto on_error;
    }

    return socket_fd;

on_error:

    close(socket_fd);

    return -1;
}

int main(int argc, char *argv[])
{
    int ret = -1;
    int epoll_fd = -1;
    int socket_fd = -1;
    int read_cnt = 0;
    int num = 0;
    int i = 0;
    char buf[1024] = {0};
    int addr_len = 0;
    struct sockaddr_in client_addr;
    struct epoll_event event;
    struct epoll_event events_array[10];

    epoll_fd = epoll_create(10);
    if (epoll_fd < 0) {
        printf("epoll_create failure:%s\n", strerror(errno));
        return -1;
    }

    socket_fd = server_udp();
    if (socket_fd < 0) {
        printf("%s: server_udp failed\n", __FUNCTION__);
        return -1;
    }

    event.events =  EPOLLIN;
    event.data.fd = socket_fd;
    ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event);
    if(ret < 0) {
        printf("epoll_ctl failure:%s\n", strerror(errno));
        close(epoll_fd);
        return -1;
    }

    while (1) {
        num = epoll_wait(epoll_fd, events_array, 10, -1);
        if(num < 0) {
            printf("epoll_wait failure:%s\n", strerror(errno));
            close(epoll_fd);
            break;
        } else if(num == 0) {
            printf("epoll_wait timeout!\n");
            continue;
        }

        for (i = 0; i < num; i++) {
            if(events_array[i].events == EPOLLIN) {
                memset(buf, 0, sizeof(buf));
                addr_len = sizeof(client_addr);
                ret = recvfrom(events_array[i].data.fd, buf, sizeof(buf), 0,
                                 (struct sockaddr *)&client_addr, &addr_len);
                if (ret > 0) {
                    printf("recv len:%d, data:[%s] from %s:%d\n", ret, buf,
                            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                } else if (0 == ret) {
                    printf("ret:%d\n", ret);
                } else {
                    printf("ret:%d\n", ret);
                }


            }
        }
    }

    return 0;
}

client_udp_epoll.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9999
#define CLIENT_IP "127.0.0.1"
#define CLIENT_PORT 10000

int client_udp()
{
    int ret = 0;
    int socket_fd = -1;
    int addr_len = 0;
    struct sockaddr_in client_addr;
    struct sockaddr_in server_addr;
    char buf[1024] = {"hello world"};

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        printf("%s: socket failed\n", __FUNCTION__);
        return 0;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(CLIENT_PORT);
    client_addr.sin_addr.s_addr = inet_addr(CLIENT_IP);
    addr_len = sizeof(client_addr);

    ret = bind(socket_fd, (const struct sockaddr *)&client_addr, addr_len);
    if (ret < 0) {
        printf("%s: bind failed\n", __FUNCTION__);
        close(socket_fd);
        return 0;
    }

    addr_len = sizeof(server_addr);
    ret = sendto(socket_fd, buf, strlen(buf), 0,
                     (struct sockaddr *)&server_addr, addr_len);
    if (ret > 0) {
        printf("send data: [%s] to %s:%d\n", buf, inet_ntoa(server_addr.sin_addr),
                        ntohs(server_addr.sin_port));
    } else {
        printf("ret:%d\n", ret);
    }

    close(socket_fd);

    return 0;
}

int main(int argc, char *argv[])
{
    client_udp();

    return 0;
}

Makefile:

all:
	gcc -o server server_udp_epoll.c
	gcc -o client client_udp_epoll.c
clean:
	-@rm server client

测试:

$ ./server
recv len:11, data:[hello world] from 127.0.0.1:10000
$ ./client
send data: [hello world] to 127.0.0.1:9999

2.2.2 epoll_wait超时参数timeout

A. 设置为-1,时会一直阻塞,直到有事件发生
num = epoll_wait(epoll_fd, events_array, 10, -1);
B. 设置为0时,会立即返回,不管有没有事件发生
C.设置为大于0时,阻塞等待事件发生的时间
num = epoll_wait(epoll_fd, events_array, 10, 5000); //5000ms

$ ./server
epoll_wait timeout!
epoll_wait timeout!
recv len:11, data:[hello world] from 127.0.0.1:10000
epoll_wait timeout!

$ ./client
send data: [hello world] to 127.0.0.1:9999

2.2.3 epoll回调函数的使用

server_epoll_cb.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>

#define SERVER_PORT1 9999
#define SERVER_PORT2 10000

int g_epoll_fd = -1;

int server_udp1()
{
    int ret = 0;
    int socket_fd = -1;
    int addr_len = 0;    
    unsigned int value = 1;
    struct sockaddr_in server_addr;

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        printf("%s: socket failed\n", __FUNCTION__);
        goto on_error;
    }

    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
            (void *)&value, sizeof(value)) < 0) {
        printf("%s: Fail to setsockopt\n", __FUNCTION__);
        goto on_error;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT1);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    addr_len = sizeof(server_addr);
    ret = bind(socket_fd, (const struct sockaddr *)&server_addr, addr_len);
    if (ret < 0) {
        printf("%s: bind failed\n", __FUNCTION__);
        goto on_error;
    }

    return socket_fd;

on_error:

    close(socket_fd);

    return -1;
}

int server_udp2()
{
    int ret = 0;
    int socket_fd = -1;
    int addr_len = 0;    
    unsigned int value = 1;
    struct sockaddr_in server_addr;

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        printf("%s: socket failed\n", __FUNCTION__);
        goto on_error;
    }

    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
            (void *)&value, sizeof(value)) < 0) {
        printf("%s: Fail to setsockopt\n", __FUNCTION__);
        goto on_error;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT2);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    addr_len = sizeof(server_addr);
    ret = bind(socket_fd, (const struct sockaddr *)&server_addr, addr_len);
    if (ret < 0) {
        printf("%s: bind failed\n", __FUNCTION__);
        goto on_error;
    }

    return socket_fd;

on_error:

    close(socket_fd);

    return -1;
}

typedef int (*server_callback_t)(struct epoll_event *event, int fd);

enum epoll_cb_id_e {
    EPOLL_CB_ID_SERVER1,
    EPOLL_CB_ID_SERVER2,
    EPOLL_CB_ID_MAX,
};

int server_callback_1(struct epoll_event *event, int fd)
{
    int ret = 0;
    int addr_len = 0;
    char buf[1024] = {0};
    struct sockaddr_in client_addr;

    memset(buf, 0, sizeof(buf));
    memset(&client_addr, 0, sizeof(client_addr));
    addr_len = sizeof(client_addr);
    if(event->events == EPOLLIN) {
        ret = recvfrom(fd, buf, sizeof(buf), 0,                                                             
                      (struct sockaddr *)&client_addr, &addr_len);
        if (ret > 0) {
            printf("%s: server-1 recv len:%d, data:[%s] from %s:%d\n", __FUNCTION__, ret, buf,
                    inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        } else if (0 == ret) {
            printf("ret:%d\n", ret);
        } else {
            printf("ret:%d\n", ret);
        }
    }

    return 0;
}

int server_callback_2(struct epoll_event *event, int fd)
{
    int ret = 0;
    int addr_len = 0;
    char buf[1024] = {0};
    struct sockaddr_in client_addr;

    memset(buf, 0, sizeof(buf));
    memset(&client_addr, 0, sizeof(client_addr));
    addr_len = sizeof(client_addr);
    if(event->events == EPOLLIN) {
        ret = recvfrom(fd, buf, sizeof(buf), 0,                                                             
                      (struct sockaddr *)&client_addr, &addr_len);
        if (ret > 0) {
            printf("%s: server-2 recv len:%d, data:[%s] from %s:%d\n", __FUNCTION__, ret, buf,
                    inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        } else if (0 == ret) {
            printf("ret:%d\n", ret);
        } else {
            printf("ret:%d\n", ret);
        }
    }

    return 0;
}

server_callback_t g_server_callback[EPOLL_CB_ID_MAX] = {
&server_callback_1,
&server_callback_2,
};

int epoll_event_process(struct epoll_event *event)
{
    int cb_id = -1;
    int fd = -1;

    cb_id = event->data.u64 & 0xffff;
    fd = event->data.u64 >> 32;

    if (fd < 0) {
        printf("%s: invalid fd[%d]\n", __FUNCTION__, fd);
        return -1;
    }

    if (cb_id < 0 || cb_id >= EPOLL_CB_ID_MAX) {
        printf("%s: invalid cb id[%d]\n", __FUNCTION__, cb_id);
        return -1;
    }

    printf("cb id:%d, fd:%d\n", cb_id, fd);
    (g_server_callback[cb_id])(event, fd);    

    return 0;
}

int main(int argc, char *argv[])
{
    int ret = -1;
    long socket_fd1 = -1;
    long socket_fd2 = -1;
    int read_cnt = 0;
    int num = 0;
    int i = 0;
    struct epoll_event event;
    struct epoll_event events_array[10];

    g_epoll_fd = epoll_create(10);
    if (g_epoll_fd < 0) {
        printf("epoll_create failure:%s\n", strerror(errno));
        return -1;
    }

    socket_fd1 = server_udp1();
    if (socket_fd1 < 0) {
        printf("%s: server_udp failed\n", __FUNCTION__);
        return -1;
    }

    socket_fd2 = server_udp2();
    if (socket_fd1 < 0) {
        printf("%s: server_udp failed\n", __FUNCTION__);
        return -1;
    }

    event.events =  EPOLLIN;
    event.data.u64 = socket_fd1 << 32 | EPOLL_CB_ID_SERVER1;
    ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, socket_fd1, &event);
    if(ret < 0) {
        printf("epoll_trl failure:%s\n", strerror(errno));
        close(g_epoll_fd);
        return -1;
    }

    event.events =  EPOLLIN;
    event.data.u64 = socket_fd2 << 32 | EPOLL_CB_ID_SERVER2;
    ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, socket_fd2, &event);
    if(ret < 0) {
        printf("epoll_trl failure:%s\n", strerror(errno));
        close(g_epoll_fd);
        return -1;
    }

    while (1) {
        num = epoll_wait(g_epoll_fd, events_array, 10, -1);
        if (num < 0) {
            printf("epoll_wait failure:%s\n", strerror(errno));
            close(g_epoll_fd);
            break;
        } else if (num == 0) {
            printf("eopll_wait timeout!\n");
            continue;
        }

        for (i = 0; i < num; i++) {
            epoll_event_process(&events_array[i]);
        }
    }

    return 0;
}

client_epoll_cb.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9999

int client_udp(char *server_ip, int server_port)
{
    int ret = 0;
    int socket_fd = -1;
    int addr_len = 0;
    struct sockaddr_in server_addr;
    char buf[1024] = {"hello world"};

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        printf("%s: socket failed\n", __FUNCTION__);
        return 0;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);

    addr_len = sizeof(server_addr);
    ret = sendto(socket_fd, buf, strlen(buf), 0,
                     (struct sockaddr *)&server_addr, addr_len);
    if (ret > 0) {
        printf("send data: [%s] to %s:%d\n", buf, inet_ntoa(server_addr.sin_addr),
                        ntohs(server_addr.sin_port));
    } else {
        printf("ret:%d\n", ret);
    }

    close(socket_fd);

    return 0;
}

int main(int argc, char *argv[])
{
    if (argc < 3) {
        printf("usage: %s 127.0.0.1 9999/10000\n", argv[0]);
        return -1;
    }

    client_udp(argv[1], atoi(argv[2]));

    return 0;
}

Makefile:

all:
	gcc -o server server_epoll_cb.c
	gcc -o client client_epoll_cb.c
clean:
	-@rm server client

测试:

$ ./client 127.0.0.1 9999
send data: [hello world] to 127.0.0.1:9999
$ ./client 127.0.0.1 10000
send data: [hello world] to 127.0.0.1:10000
$ ./server
cb id:0, fd:4
server_callback_1: server-1 recv len:11, data:[hello world] from 127.0.0.1:56433
cb id:1, fd:5
server_callback_2: server-2 recv len:11, data:[hello world] from 127.0.0.1:37377

三、select机制

3.1 常用API

3.1.1 select()

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

作用:监听指定的描述符事件
参数说明:
  nfds: readfds,writefds,exceptfds三个集合中文件描述符的范围,设置为三个集合中所有描述符最的值加1
  readfds: 监听可以读操作的文件描述符集合
  writefds: 监听可以写操作的文件描述符集合
  exceptfds: 监听发生错误的文件描述符集合
  timeout: 超时参数,有三种情况:

  • 设置为NULL时, select一直阻塞,直到有描述符发生变化才返回
  • 秒和毫秒变量均设为0时,select不管有没有描述符变化,立即返回
  • 超时时间设置为大于0时,select一直阻塞,直到超时或有描述符发生变化才返回。
       struct timeval {
        long tv_sec; /* seconds /
        long tv_usec; /
    microseconds */
      };
    返回值:
       大于0: 三个集合中描述符发生变化的总数量
       0: 没有描述符发生变化
      -1: 发生错误,errno将被设置
    注:select函数返回后,会改变timeout的值和readfds,writefds,exceptfds三个集合的值。因此再次调用select函数前,需要重置readfds,writefds,exceptfds三个集合和timeout。

3.1.2 FD_XX()

FD_XX()系列函数用来对描述符集合进行增删改查,具体如下:

  • void FD_CLR(int fd, fd_set *set);
    从集合set中删除描述符fd

  • int FD_ISSET(int fd, fd_set *set);
    判断描述符fd是否在集合set中

  • void FD_SET(int fd, fd_set *set);
    添加描述符fd到集合set中

  • void FD_ZERO(fd_set *set);
    清空集合set(删除所有的fd)

3.2 示例分析

select_server.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <errno.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8000

#define MAX_RECV_LEN 1024
#define MAX_CLIENT_NUM 30
#define BACK_LOG 20

static int running = 1;

int main(int argc, char *argv[])
{
    int ret = -1;
    int listen_fd = -1;
    int new_conn_fd = -1;
    char buf[1024] = {0};
    int len = 0;
    int i = 0;
    int max_fd = -1;
    int num = -1;
    struct sockaddr_in serv_addr;
    struct sockaddr_in cli_addr;
    socklen_t serv_addr_len = 0;
    socklen_t cli_addr_len = 0;
    int client_fd[MAX_CLIENT_NUM];
    char recv_buf[MAX_RECV_LEN];
    struct timeval timeout;
    unsigned int value = 1;
    fd_set read_set;
    fd_set select_read_set;

    FD_ZERO(&read_set);
    FD_ZERO(&select_read_set);

    for (i = 0; i < MAX_CLIENT_NUM; i++) {
        client_fd[i] = -1;
    } 

    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(&cli_addr, 0, sizeof(cli_addr));

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("Fail to socket");
        exit(1);
    }

    max_fd = listen_fd;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
            (void *)&value, sizeof(value)) < 0) {
	perror("Fail to setsockopt");
	exit(1);
    }

    serv_addr_len = sizeof(serv_addr);
    if (bind(listen_fd, (struct sockaddr*)&serv_addr, serv_addr_len) < 0) {
        perror("Fail to bind");
        exit(1);
    }
    if (listen(listen_fd, BACK_LOG) < 0) {
        perror("Fail to listen");
        exit(1);
    }

    FD_SET(listen_fd, &read_set);
    while (running) {
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        max_fd = listen_fd;
        for (i = 0; i < MAX_CLIENT_NUM; i++) {
            if (max_fd < client_fd[i]) {
                max_fd = client_fd[i];
            }
        }

        select_read_set = read_set;
        ret = select(max_fd + 1, &select_read_set, NULL, NULL, &timeout);
        if (ret == 0) {
            printf("timeout\n");
        }
        else if (ret < 0) {
            printf("error occur, ret:%d\n", ret);
        }
        else {
            if (FD_ISSET(listen_fd, &select_read_set)) {
                len = sizeof(cli_addr);
                new_conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &len);
                if (new_conn_fd < 0) {
                    perror("Fail to accept");
                    exit(1);
                } else {
                    printf("Client[%d] connect\n", new_conn_fd);
                    for (i = 0; i < MAX_CLIENT_NUM; i++) {
                        if (client_fd[i] == -1) {
                            client_fd[i] = new_conn_fd;
                            FD_SET(new_conn_fd, &read_set);
                            break;
                        }
                        if (max_fd < new_conn_fd) {
                            max_fd = new_conn_fd;
                        }
                    }
                }
            } else {
                for (i = 0; i < MAX_CLIENT_NUM; i++) {
                    memset(recv_buf, 0, MAX_RECV_LEN);
                    if (FD_ISSET(client_fd[i], &select_read_set)) {
                        num = read(client_fd[i], recv_buf, MAX_RECV_LEN);
                        if (num < 0) {
                            printf("Client(%d) error\n", client_fd[i]);
                            FD_CLR(client_fd[i], &read_set);
                            close(client_fd[i]);
                            client_fd[i] = -1;
                        } else if (num > 0) {
                            recv_buf[num] = '\0';
                            printf("Recieve client[%d] data[%s]\n", client_fd[i], recv_buf);
                        } if (num == 0) {
                            printf("Client[%d] disconnect\n", client_fd[i]);
                            FD_CLR(client_fd[i], &read_set);
                            close(client_fd[i]);
                            client_fd[i] = -1;
                        }
                    }
                }
            }
        }
    }

    return 0;
}

select_client.c:

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

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8000
#define MAX_RECV_LEN 1024

static int running = 1;

int main(int argc, char *argv[])
{
    int sock_fd = -1;
    int ret = -1;
    char buf[1024] = {0};
    int num = 0;
    struct sockaddr_in serv_addr;
    struct sockaddr_in cli_addr;
    socklen_t serv_addr_len = 0;

    memset(&serv_addr, 0, sizeof(serv_addr));

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("Fail to socket");
        exit(1);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    serv_addr_len = sizeof(serv_addr);
    if (connect(sock_fd, (struct sockaddr *)&serv_addr, serv_addr_len) < 0) {
        perror("Fail to connect");
        exit(1);
    }

    while (running) {
        num = read(STDIN_FILENO, buf, MAX_RECV_LEN);
        if (num > 0) {
            buf[num - 1] = '\0';
            printf("buf: %s\n", buf);
            num--;
            num = write(sock_fd, buf, num);
            if (num < 0) {
                printf("write failed\n");
                exit(1);
            }

            if (strncmp(buf, "exit", strlen("exit")) == 0) {
                printf("Client exit\n");
                close(sock_fd);
                return 0;
            } 
        }
    }

    return 0;
}

Makefile:

all:
	gcc -o server select_server.c
	gcc -o client select_client.c
clean:
	-@rm server client

测试:

$ ./server 
Client[4] connect
Recieve client[4] data[hello world]
Client[4] disconnect
$ ./client 
hello world
buf: hello world

四、poll机制

4.1 常用API

4.1.1 poll()

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

作用:监听指定的描述符事件
参数说明:
  fds: 监听的描述符数据
  nfds:监听的描述符数量
  timeout: 超时时间,单位是毫秒。
    大于0:表示poll在没有事件发生时的阻塞超时时间
    0: 不管有没有事件发生,都立即返回
    -1: 阻塞直到有事件发生
返回值:
  成功时,返回有事件发生的描述符的数量;超时时返回0。有错误时,返回-1.
   struct pollfd {
    int fd; /* file descriptor /
    short events; /
requested events /
     short revents; /
returned events */
   };

  fd: 文件描述符
  events: 按位设置的变量,是一个输入参数,用于表示监听的事件;如果设置为0,则表明监听所有的事件
  revents:按位设置的变量,是一个输出参数,用于表示实际发生的事件

          POLLIN :读事件
          POLLPRI:紧急读事件
          POLLOUT: 写事件
          POLLRDHUP: 对端关闭事件
          POLLERR : 错误事件
          Error condition (output only).
          POLLHUP:挂起事件
          POLLNVAL: 非法请求

4.2 示例分析

poll_server.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <errno.h>


#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8000

#define MAX_RECV_LEN 1024
#define MAX_CLIENT_NUM 30
#define BACK_LOG 20
#define MAX_FDS 10

static int running = 1;

int main(int argc, char *argv[])
{
    int ret = -1;
    int listen_fd = -1;
    int new_conn_fd = -1;
    char buf[1024] = {0};
    int len = 0;
    int i = 0;
    int num = -1;
    struct sockaddr_in serv_addr;
    struct sockaddr_in cli_addr;
    socklen_t serv_addr_len = 0;
    socklen_t cli_addr_len = 0;
    char recv_buf[MAX_RECV_LEN];
    int timeout_ms = 0;
    unsigned int value = 1;
    int nfds = 0;
    struct pollfd pollfds[MAX_FDS];

    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(&cli_addr, 0, sizeof(cli_addr));

    memset(pollfds, 0, sizeof(struct pollfd) * sizeof(pollfds));

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("Fail to socket");
        exit(1);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
            (void *)&value, sizeof(value)) < 0) {
	perror("Fail to setsockopt");
	exit(1);
    }

    serv_addr_len = sizeof(serv_addr);
    if (bind(listen_fd, (struct sockaddr*)&serv_addr, serv_addr_len) < 0) {
        perror("Fail to bind");
        exit(1);
    }
    if (listen(listen_fd, BACK_LOG) < 0) {
        perror("Fail to listen");
        exit(1);
    }

    pollfds[0].fd = listen_fd;
    pollfds[0].events = POLLIN;
    nfds++;

    while (running) {
        timeout_ms = 5000;
        ret = poll(pollfds, nfds, timeout_ms);
        if (ret == 0) {
            printf("timeout\n");
        }
        else if (ret < 0) {
            printf("error occur, ret:%d\n", ret);
        }
        else {
            if (pollfds[0].revents & POLLIN) {
                len = sizeof(cli_addr);
                new_conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &len);
                if (new_conn_fd < 0) {
                    perror("Fail to accept");
                    exit(1);
                } else {
                    printf("Client[%d] connect\n", new_conn_fd);
                    for (i = 0; i < MAX_FDS; i++) {
                        if (pollfds[i].fd == 0) {
                            pollfds[i].fd = new_conn_fd;
                            pollfds[i].events = POLLIN;
                            nfds++;
                            break;
                        }
                    }
                }
            }

            for (i = 1; i < nfds; i++) {
                if (pollfds[i].revents & POLLIN) {
                    num = read(pollfds[i].fd, recv_buf, MAX_RECV_LEN);
                    if (num < 0) {
                        close(pollfds[i].fd);
                        nfds--;
                        pollfds[i].fd = 0;
                        pollfds[i].events = 0;
                    } else if (num > 0) {
                        recv_buf[num] = '\0';
                        printf("Recieve client[%d] data[%s]\n", pollfds[i].fd, recv_buf);
                    } if (num == 0) {
                        printf("Client[%d] disconnect\n", pollfds[i].fd);
                        close(pollfds[i].fd);
                        nfds--;
                        pollfds[i].fd = 0;
                        pollfds[i].events = 0;
                    }
                }
            }
        }
    }

    return 0;
}

pool_client.c:

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

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8000
#define MAX_RECV_LEN 1024

static int running = 1;

int main(int argc, char *argv[])
{
    int sock_fd = -1;
    int ret = -1;
    char buf[1024] = {0};
    int num = 0;
    struct sockaddr_in serv_addr;
    struct sockaddr_in cli_addr;
    socklen_t serv_addr_len = 0;

    memset(&serv_addr, 0, sizeof(serv_addr));

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("Fail to socket");
        exit(1);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    serv_addr_len = sizeof(serv_addr);
    if (connect(sock_fd, (struct sockaddr *)&serv_addr, serv_addr_len) < 0) {
        perror("Fail to connect");
        exit(1);
    }

    while (running) {
        num = read(STDIN_FILENO, buf, MAX_RECV_LEN);
        if (num > 0) {
            buf[num - 1] = '\0';
            printf("buf: %s\n", buf);
            num--;
            num = write(sock_fd, buf, num);
            if (num < 0) {
                printf("write failed\n");
                exit(1);
            }

            if (strncmp(buf, "exit", strlen("exit")) == 0) {
                printf("Client exit\n");
                close(sock_fd);
                return 0;
            } 
        }
    }

    return 0;
}

Makefile:

all:
	gcc -o server poll_server.c
	gcc -o client poll_client.c
clean:
	-@rm server client

测试:

$ ./client 
hello world
buf: hello world
$ ./server 
Client[4] connect
Recieve client[4] data[hello world]
Client[4] disconnect
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪游东戴河

你就是这个世界的唯一

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值