IO多路复用模型之Select

在Unix/C中,Socket相关的函数操作都是阻塞式的,在单线程下服务端只能处理一个客户端请求。采用多线程处理客户端请求,虽然能充分发挥多核CPU能力,但是在客户端连接过多,并发度维持相对较高水平时,多线程引起线程的上下文切换将导致系统效率低下。Select多路复用模型的核心思想,在单线程下处理多客户端连接,Unix/C提供了相应函数库,具体如下:
int select (int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
const struct timeval* timeout);
struct timeval {
long tv_sec; //秒
long tv_usec; //毫秒
};
fd_set是一个SOCKET链表,以下宏可以对该队列进行操作:
FD_CLR( s, *set) 从队列set删除句柄s;
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;
FD_SET( s, *set )把句柄s添加到队列set中;
FD_ZERO( *set ) 把set队列初始化成空队列.
Select执行流程:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
3:调用select函数
4:用FD_ISSET对套接字句柄进行检查。
服务端代码如下:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#define PORT 8080
#define BACKLOG 1024
#define BUFSIZE 64

int client_count;

int main(){
    int server_socket = -1;
    struct sockaddr_in socket_addr;
    memset(&socket_addr, 0, sizeof(socket_addr));
    socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    socket_addr.sin_port = htons(PORT);
    socket_addr.sin_family = AF_INET;
    if((server_socket = create_socket(socket_addr, BACKLOG))<0)
        return -1;
    struct timeval tik;
    memset(&tik, 0, sizeof(tik));
    fd_set socket_fd;
    int fds[BACKLOG];
    memset(fds, 0, sizeof(fds));
    int max_fd;

    while(1){
        FD_ZERO(&socket_fd); //初始化fd_set
        FD_SET(server_socket, &socket_fd);//服务端socket添加至fd_set
        tik.tv_sec = 10;
        tik.tv_usec = 0;
        max_fd = server_socket;
        int index;
        for(index = 0; index < client_count; index++){
            FD_SET(fds[index], &socket_fd);//添加客户端Socket至fd_set
            if(fds[index] > max_fd)
                max_fd = fds[index];
        }

        int select_ret = -1;
        if((select_ret = select(max_fd+1, &socket_fd, NULL, NULL, &tik)) < 0){
            perror("select error");
            return -1;
        }
        if(select_ret == 0)
            continue;
        for(index = 0;index < client_count;index++)
            if(FD_ISSET(fds[index], &socket_fd))
                read_handler(&fds[index]);//处理IO事件
        if(FD_ISSET(server_socket, &socket_fd))
            connect_handler(server_socket, fds + client_count);//处理连接
    }
    return 0;
}

char *get_client_info(int client_fd){
    if(client_fd <= 0)
        return NULL;
    char client_info[16];
    memset(client_info, 0, 16);
    struct sockaddr_in peeraddr;
    socklen_t len = sizeof(peeraddr);
    memset(&peeraddr, 0, len);
    getpeername(client_fd, &peeraddr, &len);
    sprintf(client_info, "[%s:%d]", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    return client_info;
}

void read_handler(int *socket_fd){
    if(socket_fd == NULL)
        return;
    int len;
    char buf[BUFSIZE];
    memset(buf, 0, sizeof(buf));
    if((len = read(*socket_fd, buf, sizeof(buf)))<0){
        perror("read error");
        return;
    }else if(len == 0 ){
        perror("client close");
        *socket_fd = -1;
    }else{
        printf("%s%s", get_client_info(*socket_fd), buf);
        write(*socket_fd, buf, sizeof(buf));
    }
}

void connect_handler(int server_socket, int *fd){
    if(server_socket == NULL || fd == NULL)
        return;
    struct sockaddr_in client_addr;
    int len  = sizeof(client_addr);
    memset(&client_addr, 0, len);
    int accept_ret = -1;
    if((accept_ret = accept(server_socket, &client_addr, &len)) < 0){
        perror("client error");
        return;
    }else{
        *fd = accept_ret;
        client_count++;
        printf("[%s:%d] connected\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    }

}

int create_socket(struct sockaddr_in socket_addr, int backlog){
    int server_socket = -1;
    if((server_socket = socket(AF_INET, SOCK_STREAM, 0))<0){
        perror("socket error");
        return -1;
    }
    int on = 1;
    setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    int bind_ret = -1;
    if((bind_ret = bind(server_socket, &socket_addr, sizeof(socket_addr)))<0){
        perror("bind error");
        return -1;
    }
    int listen_ret = -1;
    if((listen_ret = listen(server_socket, backlog))<0){
        perror("listen error");
        return -1;
    }
    return server_socket;
}

客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

int main(){
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    client_addr.sin_port = htons(PORT);
    int socket_fd = -1;
    if((socket_fd = socket(AF_INET, SOCK_STREAM, 0))<0){
        perror("socket error");
        return -1;
    }
    connect(socket_fd, &client_addr, sizeof(client_addr));
    char send_buf[64],recv_buf[64];
    memset(send_buf, 0, sizeof(send_buf));
    memset(recv_buf, 0, sizeof(recv_buf));
    while(fgets(send_buf,sizeof(send_buf),stdin)!=NULL){
        write(socket_fd, send_buf, strlen(send_buf));
        read(socket_fd, recv_buf, sizeof(recv_buf));
        fputs(recv_buf, stdout);
        memset(send_buf, 0, sizeof(send_buf));
        memset(recv_buf, 0, sizeof(recv_buf));
    }
    close(socket_fd);
    return 0;
}

运行效果如下图:
这里写图片描述
小结:Select模型解决了单线程下,无法处理多客户端请求问题。但由于其采用轮询策略依次去处理所有socket句柄,而通常大多数连接处理不活跃状态,这导致轮询效率低下;实验表明,在大部分客户端不活跃而维持长连接状态时,select多路复用模型的效率并不比多线程分别处理客户端效率高。此外,由于Select基于文件描述符实现多路复用,其最大并发处理的并发数也受到操作系统相应的限制(linux默认1024)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值