关于linux网络编程——4
总结:
[服务器编程 -- IO多路复用]
1.单循环服务器
socket
bind
listenwhile (1)
{
connfd = accept
//通信
}
2.并发服务器 --- 进程或线程
socket
bind
listenwhile (1)
{
//任务1 接收客户端的连接
connfd = accept
fork
pthread_create
//任务2 通信
}
同时可以处理多个客户端
3.提高并发的程度
IO多路复用
//多路
//复用IO模型
//阻塞IO --- 简单 低效
//非阻塞IO --- 轮询 + CPU(负担中)
//信号驱动IO --- 发信号通知一下
//SIGIO
//处理数量有限
//IO多路复用
//
10进程或线程
10进程或线程 --- 监控多路IO
-------------------------------
select
多路IO复用的服务器: //实现并发 --- 可以同时处理多个客户端listenfd = socket
bind
listen
//connfd = accept
//1.准备表
fd_set readfds;
FD_ZERO(&readfds);
//2.添加要监控的文件描述符
FD_SET(listenfd,&reafds);
//3.准备参数
maxfds = listenfd + 1;
fd_set backfds;
while (1)
{
backfds = readfds;
int ret = select(maxfds,&backfds,NULL,NULL,NULL);
if (ret > 0)
{
int i = 0;
for (i = 0; i < maxfds;++i)
{
if (FD_ISSET(i,&backfds))
{
if (i == listenfd) //连接
{
connfd = accept();
//connfd 要被添加到 监控表
FD_SET(connfd,&readfds);
if (connfd + 1 > maxfds)
maxfds = connfd + 1;
}else //负责与客户端通信
{
// i = ?//fd 此时就绪
printf("buf = %s\n",buf);
if (strncmp(buf,"quit",4) == 0)
{
FD_CLR(i,&readfds); //清除对应的客户端的fd
close(i);
}
}
}
}
}
}//优化
int i = maxfds;
for (i = maxfds-1; i >= 0; --i)
{
if (FD_ISSET(i,&readfds))
{
maxfds = i + 1;
}
}
不足:
缺点
1. 最大监听数受限:`FD_SETSIZE` 默认 1024(Linux)
2. 每次调用需重置 fd_set:内核会修改集合,必须每次重新 `FD_SET`
3. 用户态与内核态拷贝开销大
4. 返回后仍需遍历所有 fd 才能知道哪个就绪
5. 效率随 fd 数量增长下降明显
一、pol函数
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能: 对文件描述符监控
参数:@fds struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */};events 事件:POLLIN 读 POLLOUT 写 POLLERR 错误 @nfds 表示要监控的文件描述符的数量 @timeout 时间值 -1 //阻塞 时间值 单位 是 ms (毫秒)0 //非阻塞 n(>0) //阻塞 n ms(毫秒)
返回值:成功 表示 就绪的数量 0 超时情况下表示 没有就绪实际失败 -1
二、使用思路
使用://1.准备监控表 struct pollfd fds[10]; //监控表示 10个fd //2.添加要监控的文件描述符 //点对点聊天 //两路io -- stdin / sockfd int nfds = 0;fds[0].fd = 0;fds[0].events = POLLIN;nfds++;fds[1].fd = sockfd;fds[1].events = POLLIN;nfds++;//3.准备参数 while (1){int ret = poll(fds,nfds,-1);if (ret > 0){int i = 0;for (i = 0; i < nfds; ++i){if(fds[i].revents&POLL_IN){if (fds[i].fd == 0){fgetssend}else //sockfd { recvprintf }}}}}
三、poll实现的并发服务器
//poll实现的并发服务器:
多路IO复用的服务器: //实现并发 --- 可以同时处理多个客户端
listenfd = socketbindlisten
//1.准备表struct pollfd fds[10];
//2.添加要监控的文件描述符 int nfds = 0;fds[0].fd = listenfd;fds[0].events = POLL_IN;nfds++;while (1){ int ret = poll(fds,nfds,-1);if (ret > 0){int i = 0;for (i = 0; i < nfds;++i){if (fds[i].revents&POLL_IN)){if (fds[i].fd == listenfd) //连接 {connfd = accept();//connfd 要被添加到 监控表fds[nfds].fd = connfd;fds[nfds].events = POLL_IN;nfds++; }else //负责与客户端通信 {// i = ?//fd 此时就绪 recv(fds[i].fd,buf,sizeof(buf),0);printf("buf = %s\n",buf);if (strncmp(buf,"quit",4) == 0){fds[i].fd = -1; //-1 不是有效的文件描述符 close(fds[i].fd); }}}}}
}
四 改进与不足:
相比 select 的改进
1. 无 1024 限制:只要系统允许打开足够多 fd
2. 无需重置集合:`events` 和 `revents` 分离
3. 更清晰的事件机制
4. 效率更高:仅遍历传入的数组,不遍历整个 fd 范围
仍存在的问题
- 每次调用仍需将整个 `fds[]` 拷贝到内核
- 返回后仍需遍历全部元素查找就绪 fd
- 时间复杂度仍是 O(n),连接数多时性能下降
五、epoll
5.1函数
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建一个epoll对象
参数:@size 忽略,但是必须大于0
返回值:成功 epoll对象的fd失败 -1 &&errno #include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制 epoll对象
参数:@epfd epoll对象的fd@op EPOLL_CTL_ADD //添加EPOLL_CTL_MOD //修改 EPOLL_CTL_DEL //删除 @fd //要关心的文件描述符 @event typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */};struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = 0; //stdin EPOLLIN //读 EPOLLOUT //写 EPOLLERR //出错EPOLLET //边沿触发___ ___| || |-----中断:-- //电平(水平)触发 --- 有数据 ,它就会一直通知 //边沿触发 --- 没数据,有数据 ---触发 ----只会通知一次返回值:成功 0失败 -1 &&errnoint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:监控对应的文件描述符 ,看是否有就绪
参数:@epfd --epoll对象@events ---保存就绪结果的 一个数组的首地址 @maxevents ---数组的大小 struct epoll_event ret_ev[2];@timeout ---超时的时间 时间单位,还是ms (毫秒数)
返回值:成功 就绪的数量 失败 -1 && errno被设置
5.2使用流程:
1.epoll_create //创建了一个epoll对象 ---- 监控的表 int epfd = epoll_create(2);
2.添加文件描述符
//一个是 标准输入 0
//一个是 sockfd
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = 0; //标准输入
epoll_ctl(epfd,EPOLL_CTL_ADD, 0, &ev);int add_fd(int fd, int epfd)
{struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd; //标准输入 if ( epoll_ctl(epfd,EPOLL_CTL_ADD, fd, &ev)){perror("epoll_ctl add fail");return -1;} return 0;
}int del_fd(int fd, int epfd) //删除
{//struct epoll_event ev;//ev.events = EPOLLIN;//ev.data.fd = fd; //标准输入 if ( epoll_ctl(epfd,EPOLL_CTL_DEL, fd, NULL)){perror("epoll_ctl add fail");return -1;} return 0;
}add_fd(0,epfd);//标准输入
add_fd(clifd,epfd);//socket3.监控文件描述符
while (1)
{struct epoll_event ret_ev[10];int ret = epoll_wait(epfd, ret_ev,3, -1);if (ret > 0){int i = 0;for (i = 0; i < ret; ++i){if (ret_ev[i].data.fd == 0){}else //socket{}}}}