摘要
在linux环境下多路复用模型一共有三种,分别是select、poll、epoll。本文主要记录select模型实现网络socket服务器多路并发的相关要点和知识。在写这篇文章之前,已经使用多进程和多线程实现服务器多路并发。多进程和多线程多线程对于内核的负担和内存的开销都是巨大的,原因是内核都会为新来的客户端创建新进程和新线程,会导致内核的工作效率降低。select的最大优势在于它可以在一个线程中同时处理多个socket的IO请求。
select工作机制
-
Linux下select调用的过程:
用户层应用程序调用select(),底层调用poll();
核心层调用sys_select() ------> do_select(); -
select首先循环遍历fd_set里面的文件描述符对应的驱动程序的poll函数,每个设备驱动poll函数会将调用select的用户进程插入到对应资源的等待队列中,然后poll函数返回bitmask告诉当前哪些资源可用,当遍历完fd_set文件描述集后如果有资源可读或可写,则返回可读或可写资源的文件描述个数,没有就进入睡眠状态,直到有资源利用时才唤醒。
-
select返回时,会把fd_set描述符集中没有发生事件的描述符清空,所以一般写程序时会定义一个数组暂存文件描述符,第二次select监测时再加入fd_set描述符集。
-
在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数中,这也意味着select所用到的FD_SET是有限的,也正是这个原因select()默认只能同时处理1024个客户端的连接请求:
c /linux/posix_types.h: #define __FD_SETSIZE 1024
实现select服务器并发的函数总结
1、一个重要函数
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
参数解析:
- max_fd:指待测试的fd的总个数,值等于最大描述符+1(select监测的文件描述从0开始的)。
- readset:监测可读,不监测设置为NULL。
- writeset:监测可写,不监测设置为NULL。
- exceptset:监测异常,不监测设置为NULL。
- timeout:设置select的超时时间,如果设置为NULL则永不超时;
2、两个结构体
(1)、struct fd_set:可以理解为一个集合,这个集合中存放的是文件描述符。
(2)、struct timeval:时间值。
struct timeval
{
time_t tv_sec;//second
time_t tv_usec;//minisecond
};
3、四个宏定义
-
FD_CLR(inr fd,fd_set* set);用来清除set中相关fd 的位
-
FD_ISSET(int fd,fd_set *set);用来测试set中相关fd 的位是否为真
-
FD_SET(int fd,fd_set *set);用来设置set中相关fd的位
-
FD_ZERO(fd_set *set);用来清除set的全部位
程序流程图
实现代码
这里只贴出server_main.c,更多代码:
select服务器端:点击查看
客户端:点击查看
server_main.c
#include "server.h"
int main(int argc,char **argv)
{
int server_port;
int sockfd = -1;
int clienfd = -1;
int re_val = -1;
//参数解析
re_val = argument_parse(argc, argv, &server_port);
if(re_val < 0)
{
printf("参数解析错误!\n");
exit(0);
}
//套接字初始化
re_val = socket_init(server_port,&sockfd);
if(re_val < 0)
{
printf("套接字初始化错误!\n");
}
//select服务器开始工作
server_select_create(sockfd);
return 0;
}
测试
第一次测试:出现乱码
解决方案:数据处理之前未清空数据缓冲区——memset(buf,0,sizeof(buf));
第二次测试:成功
clienfd[4]:代表第一个客户端连接上来
clienfd[5]:代表第二个客户端连接上来