当select转接的文件描述符跨度过大时,每次都遍历文件描述符表显然会降低效率,因此要对其优化。
优化思路:添加一个数组来存储要监听的文件描述符,直接遍历这个数组即可
程序流程:
#include "wrap.h"
#include
#include
#include
#define SRV_PORT 6669
#define OPEN_MAX 1024
int main()
{
int cfd,lfd,ret;
struct sockaddr_in srv_addr,clt_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t clt_addr_len;
char buf[BUFSIZ] = {0};
char cltip[16];
lfd = Socket(AF_INET,SOCK_STREAM,0);
int opt = 1;
ret = setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
if(-1 == ret)
sys_err("setsockopt error");
Bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
Listen(lfd,128);
int maxfd = lfd;
int maxindex = 0;
fd_set rset,allset;
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(lfd,&allset);
int client[OPEN_MAX];
for(int i=0 ;i<OPEN_MAX ;++i)
{
client[i] = -1;
}
int nread;
while(1)
{
rset = allset;
nread = select(maxfd+1,&rset,NULL,NULL,NULL);
if(-1 == ret)
sys_err("select error");
if(nread == 0)
continue;
if(FD_ISSET(lfd,&rset)) //有新客户端接入
{
clt_addr_len = sizeof(clt_addr);
cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
printf("客户端ip:%s,port:%d 接入\n",
inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,cltip,sizeof(cltip)),
ntohs(clt_addr.sin_port));
int i;
//将新文件描述符添加到数组中
for(i=0 ;i<OPEN_MAX; ++i)
{
if(client[i] == -1)
{
client[i] = cfd;
if(maxindex < i)
maxindex = i;
if(maxfd < cfd)
maxfd = cfd;
//将新文件描述符添加到allset
FD_SET(cfd,&allset);
break;
}
}
if(i == OPEN_MAX)
{
printf("too much fd\n");
exit(1);
}
}
else // 客户端通信
{
for(int i = 0 ;i <= maxindex ;++i)
{
if(client[i] != -1){
int sockfd = client[i];
if(FD_ISSET(sockfd,&rset)) //判断如果在rset中,通信
{
ret = Read(sockfd,buf,sizeof(buf));
if(0 == ret)
{
FD_CLR(sockfd,&allset);
client[i] = -1;
Close(sockfd);
}
else if(ret > 0)
{
for(int j = 0;j < ret ;++j)
{
buf[j] = toupper(buf[j]);
}
Write(sockfd,buf,ret);
Write(STDOUT_FILENO,buf,ret);
}
}
}
}
}
}
Close(lfd);
return 0;
}
同步:阻塞
异步:不阻塞
查看 当前 Linux 系统所能打开的 最大文件个数 —— 受硬件影响。
cat /proc/sys/fs/file-max
获取当前 用户下的进程, 默认打开的文件描述符个数。 —— 默认值 1024
使用命令ulimit -a
itcast@ubuntu:~/classcode/itcode/4day/select_server$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3472
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 65536
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3472
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
sudo vi /etc/security/limits.conf 打开文件, 添加设置。 不能超过 cat /proc/sys/fs/file-
max 命令查询结果。
* soft nofile 65536 和 * hard nofile 65536
sudo vi /etc/systemd/user.conf 打开文件, 添加设置
DefaultLimitNOFILE=65535
sudo vi /etc/systemd/system.conf 打开文件, 添加设置
DefaultLimitNOFILE=65535
必须重启 Linux 系统,使配置文件生效。
ulimit -a 查看到 修改后的 默认打开的文件数
ulimit -n 4096。 设置的临时数量 < 65535 即可生效。
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:【数组】监听的文件描述符
struct pollfd {
int fd; // 待监听的文件描述符
short events; // 待监听的文件描述符的监听事件
POLLIN、 POLLOUT、 POLLERR
short revents; // 传入时,赋值为0。 poll 函数返回后,如果满足对应事件,
// 该成员变量的值变为非0。 POLLIN、 POLLOUT、POLLERR
};
nfds: 监听数组的实际有效的监听个数。————不是数组的容量。
timeout:
> 0: 超时时长。 单位:毫秒。
-1: 阻塞等。
0: 不阻塞。
返回值:
成功:返回满足对应监听事件的文件描述符 【总个数】
失败:-1, errno
> 0: 实际读到的字节数
= 0: socket 中 对端关闭。close()
- 1: 如果 errno == EINTR 被异常中断。 需要重启。
如果 errno == EAGAIN 或 EWOULDBLOCK 以非阻塞方式读数据, 但没有数据。 需要,再次读。
如果 errno == ECONNRESET。 说明连接被重置。 需要 close,移除出监听队列。
其余为 异常。
epoll_create()
#include
// 创建一棵监听红黑树
int epoll_create(int size);
size:创建的红黑树的监听节点的数量。(仅供内核参考)
返回值:
成功:新创建的红黑树根节点的fd
失败:-1, errno
epoll_ctl()
// 操作监听红黑树
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数epfd:epoll_create 函数的返回值。epfd
参数op:对监听红黑树所做的操作。
EPOLL_CTL_ADD: 添加 fd 到监听红黑树。(设置监听)
EPOLL_CTL_MOD: 修改 fd 在监听红黑树上的监听事件。
EPOLL_CTL_DEL: 将一个fd从监听红黑树上摘下。(取消监听)
参数fd:待监听的 fd。
参数event:【不是数组】本质是 struct epoll_event 结构体变量的地址。----传入参数。
成员 events: EPOLLIN、 EPOLLOUT、 EPOLLERR
成员 data:联合体(共用体)
int fd: 对应监听事件的 fd
void *ptr:
uint32_t u32:
uint64_t u64:
返回值:
成功:0
失败:-1, errno
epoll_wait()
// 阻塞监听
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd:epoll_create 函数的返回值。epfd
events:传出参数【数组】,满足监听条件的那些 fd 结构体。
maxevents:数组元素的总个数。1024
举例:struct epoll_event events[1024];
timeout:
> 0: 超时时间,单位:毫秒
-1:阻塞监听。
0:非阻塞。
返回值:
> 0: 满足监听事件的文件描述符【总个数】。可以用作循环 events 数组的上限。
0:没有fd满足
-1:失败。 errno
#include "wrap.h"
#include
#include
#include
#define SRV_PORT 6668
#define OPEN_MAX 1024
int main()
{
int lfd,cfd;
int ret;
char cltip[16];
char buf[BUFSIZ] = {0};
socklen_t clt_addr_len;
struct sockaddr_in srv_addr,clt_addr;
bzero(&clt_addr,sizeof(clt_addr));
bzero(&srv_addr,sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET,SOCK_STREAM,0);
int opt = 1;
ret = setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
if(-1 == ret)
{
sys_err("setsockopt error");
}
Bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
Listen(lfd,128);
struct epoll_event tep,ep[OPEN_MAX];
int epfd = epoll_create(OPEN_MAX);
if(-1 == epfd)
sys_err("epoll_create error");
tep.events = EPOLLIN;
tep.data.fd = lfd;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&tep);
if(-1 == ret)
sys_err("epoll_ctl error");
while(1)
{
int nread = epoll_wait(epfd,ep,OPEN_MAX,-1);
if(-1 == nread)
sys_err("epoll_wait error");
for(int i=0 ;i<nread ;++i)
{
if (!(ep[i].events & EPOLLIN)) //如果不是"读"事件, 继续循环
continue;
int sockfd = ep[i].data.fd;
if(sockfd == lfd) //有新的客户端接入
{
clt_addr_len = sizeof(clt_addr);
cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
printf("客户端ip:%s,port:%d 接入\n",
inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,cltip,sizeof(cltip)),
ntohs(clt_addr.sin_port));
tep.events = EPOLLIN;
tep.data.fd = cfd;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&tep);
if(-1 == ret)
sys_err("epoll_ctl");
}
else //与客户端通信
{
ret = Read(sockfd,buf,sizeof(buf));
if(0 == ret) //客户端退出
{
int n = epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
if(-1 == n)
sys_err("epoll_ctl error");
Close(sockfd);
}
else if(ret > 0)
{
for(int j = 0 ;j < ret ;++j)
buf[j] = toupper(buf[j]);
Write(sockfd,buf,ret);
Write(STDOUT_FILENO,buf,ret);
}
}
}
}
Close(lfd);
return 0;
}