read 函数返回值
>0 :实际读到的字节数
=0 :socket中,表示对端关闭 close()
-1 : 如果errno = EINTR 被异常中断。需要重启。
如果errno =EAGIN 或 EWOULDBLOCK 以非阻塞的方式读数据,但是没有数据。需要再次读
如果errno = ECONNRESET 说明连接被重置。需要close()。 移除监听队列
错误
突破 1024 文件描述符限制
cat /proc/sys/fs/file-max
当前计算机能打开的最大文件个数。受硬件影响
ulimit -a -->当前用户下的进程。默认打开文件件描述符个数 缺省 为 1024
修改:
打开 sudo vi /etc/security/limits.conf 写入
* soft nofile 65536 -->设置默认值 可以直接借助命令修改【注销用户 使其生效】
* hard nofile 100000 --》命令修改上限
命令修改: ulimit -n 21000
突破 1024 文件描述符限制
cat /proc/sys/fs/file-max
当前计算机能打开的最大文件个数。受硬件影响
ulimit -a -->当前用户下的进程。默认打开文件件描述符个数 缺省 为 1024
修改:
打开 sudo vi /etc/security/limits.conf 写入
* soft nofile 65536 -->设置默认值 可以直接借助命令修改【注销用户 使其生效】
* hard nofile 100000 --》命令修改上限
命令修改: ulimit -n 21000
epoll:
int epoll_create(int size) 创建一颗监听红黑树
size:创建的红黑树的监听结点数量(仅供内核参考)
返回值: 指向新创建的红黑树的根结点 fd
失败 -1 errno
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event) 操作监听红黑树
epfd:epoll_create函数的返回值
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
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout); 阻塞监听
epfd: epoll_create 函数返回值
events:传出参数【数组】,满足监听条件的 那些fd结构体
maxevents:数组 元素的总个数 1024
struct epoll_event events[1024]
timeout:
-1:阻塞
0:不阻塞
》0:超时时间(毫秒)
返回值:
》0:满足监听的 总个数。可以用作循环上限
0 :没有fd满足监听事件
-1 : 失败 errno
epoll 实现多路IO转接思路
lfd = socket(); 监听连接事件 lfd
bind()
listen()
int epfd = epoll_create() epfd,监听红黑树树根
struct epoll_event tep,ep[1024] ; tep,用来设置单个fd属性,ep是 epoll_wait() 传出的满足监听事件的数组
tep.events = EPOLLIN; 初始化lfd 监听属性
tep.data.fd = lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&tep) 将lfd 添加到监听红黑树上
while(1){
ret = epoll_wait (epfd,ep,1024,-1); 实施监听
for(i=0;i<ret;i++){
if(ep[i].data.fd == lfd){ //lfd 满足读事件,有新的客户端发起连接请求
cfd = accept();
tep.events = EPOLLIN; //初始化cfd监听属性
tep.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&tep);
}else{
n = read(ep[i].data.fd,buf,sizeof(buf));
if(n==0){
close(ep[i].data.fd)
epoll_ctl(epfd,EPOLL_CTL_DEL,ep[i].data.fd,NULL) //将关闭的cfd 从监听树上摘下
}else if(n>0){
小 --》大
write(ep[i].data.fd,buf,n);
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000
int main(){
int i,listenfd,connfd,sockfd;
int n,num=0;
ssize_t nready,efd,res;
char buf[BUFSIZ],str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
struct epoll_event tep,ep[OPEN_MAX];
listenfd = socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
efd=epoll_create(OPEN_MAX);
if(efd==-1){
perror("epoll_create error");
exit(1);
}
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep); //将lfd及对应的结构体设置到树上,efd可以找到该树
if(res ==-1){
perror("epoll_ctl error");
exit(1);
}
for(;;){
nready = epoll_wait(efd,ep,OPEN_MAX,-1);
if(nready==-1){
perror("epoll_wait error");
exit(1);
}
for(i=0;i<nready;++i){
if(ep[i].data.fd==listenfd){
clilen = sizeof(cliaddr);
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
printf("received from %s PORT %d\n",
inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
ntohs(cliaddr.sin_port));
printf("cfd %d--client %d\n",connfd,++num);
tep.events = EPOLLIN;tep.data.fd = connfd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
if(res == -1){
perror("epoll_ctl error");
exit(1);
}
}else {
sockfd = ep[i].data.fd;
n = read(sockfd,buf,MAXLINE);
if(n==0){ //读到0 说明客户端关闭连接
res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);//将该文件描述符从红黑树上摘除
close(sockfd); //关闭与客户端连接
printf("client[%d]closed connection\n",sockfd);
}else if(n<0){
perror("read n<0 error");
res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
close(sockfd);
}else{
for(i= 0;i<n;i++)
buf[i]=toupper(buf[i]);
write(STDOUT_FILENO,buf,n);
write(sockfd,buf,n);
}
}
}
}
close(listenfd);
close(efd);
return 0;
}
epoll 事件模式
ET 模式(边沿触发):
高速工作方式,只支持 no-block socket 在这种模式下,当描述符从未就绪变为就绪时,内核会通过 epoll 告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个
fd 做 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)
缓冲区剩余未读尽的数据不会导致 epoll_wait 返回。 新的事件满足,才会触发。
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
LT模式:(水平触发 -- 默认采用模式)
支持 block 和 no -block socket 。内核会告诉你一个文件描述符是否就绪了,然后你可以对就绪文件描述符 IO操作
若果不做任何操作,内核还是后继续通知你,所以,这种编程模式出错误可能性小 传统的select poll都是这种
缓冲区剩余未读尽数据会导致 epoll_wait 返回
//借助管道 了解 epoll ET /LT 模式
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/epoll.h>
#include<errno.h>
#include<unistd.h>
#define MAXLINE 10
int main(int argc,char* argv[]){
int efd,i;
int pfd[2];
pid_t pid;
char buf[MAXLINE],ch = 'a';
pipe(pfd);
pid = fork();
if(pid==0){ //子写
close(pfd[0]);
while(1){
//aaaa\n
for(i=0;i<MAXLINE/2;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
//bbbb\n
for(;i<MAXLINE;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1],buf,sizeof(buf));
sleep(5);
}
close(pfd[1]);
}else if(pid>0){ //父读
struct epoll_event event,resevent[10];
int res,len;
close(pfd[1]);
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; //边沿触发
//event.events = EPOLLIN ; //水平触发(默认)
event.data.fd = pfd[0];
epoll_ctl(efd,EPOLL_CTL_ADD,pfd[0],&event);
while(1){
res= epoll_wait(efd,resevent,10,-1);
printf("res %d\n",res);
if(resevent[0].data.fd == pfd[0]){
len = read(pfd[0],buf,MAXLINE/2);
write(STDOUT_FILENO,buf,len);
}
}
close(pfd[0]);
close(efd);
}
return 0;
}
// 借助套接字 了解 epoll ET/LT模式
//server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(){
int i,listenfd,connfd,sockfd;
int len;
int n,num=0;
ssize_t nready,efd,res;
char buf[MAXLINE],str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
struct epoll_event event,resevent[10];
listenfd = socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
efd=epoll_create(10);
event.events = EPOLLIN | EPOLLET; //边缘触发
//event.events = EPOLLIN ;// 水平 clilen = sizeof(cliaddr);
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
printf("received from %s PORT %d\n",
inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
ntohs(cliaddr.sin_port));
printf("cfd %d--client %d\n",connfd,++num);
event.data.fd = connfd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);
while(1){
res = epoll_wait (efd,resevent,10,-1);
printf("res %d\n",res);
if(resevent[0].data.fd == connfd){
len = read (connfd,buf,MAXLINE/2);
write(STDOUT_FILENO,buf,len);
}
}
return 0;
}
================================================================================
//client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SERV_PORT 9000
#define MAXLINE 10
void sys_err(const char* str){
perror(str);
exit(1);
}
int main(){
int sockfd,i;
char ch = 'a';
char buf[MAXLINE];
struct sockaddr_in serv_addr; //服务器地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);
sockfd= socket(AF_INET,SOCK_STREAM,0);
connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
while(1){
for(i=0;i<MAXLINE/2;++i)
buf[i]= ch;
buf[i-1]='\n';
ch++;
for(;i<MAXLINE;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
write(sockfd,buf,sizeof(buf));
sleep(5);
}
close(sockfd);
return 0;
}
结论:
epoll 的 ET 模式 高效模式 ,但是只支持 非阻塞模式 -- 忙轮询
struct epoll_event event;
event.events = EPOLLIN | EPOLLET:
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event)
int flag = fcntl(cfd,F_GETFL)
flag|=0_NOBLOCK
fcntl(cfd,F_SETFL,flag)
// epoll ET模式 非阻塞使用方式
//serve
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
#include<fcntl.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(){
int i,listenfd,connfd,sockfd;
int len,flag;
int n,num=0;
ssize_t nready,efd,res;
char buf[MAXLINE],str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
struct epoll_event event,resevent[10];
listenfd = socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,20);
efd=epoll_create(10);
event.events = EPOLLIN | EPOLLET; //边缘触发
clilen = sizeof(cliaddr);
connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
printf("received from %s PORT %d\n",
inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
ntohs(cliaddr.sin_port));
printf("cfd %d--client %d\n",connfd,++num);
flag = fcntl(connfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(connfd,F_SETFL,flag);
event.data.fd = connfd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);
while(1){
res = epoll_wait (efd,resevent,10,-1);
printf("res %d\n",res);
if(resevent[0].data.fd == connfd){
while((len = read (connfd,buf,MAXLINE/2))>0){ //非阻塞读 轮询
write(STDOUT_FILENO,buf,len);
}
}
}
return 0;
}
==================================================================================
//client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SERV_PORT 9000
#define MAXLINE 10
void sys_err(const char* str){
perror(str);
exit(1);
}
int main(){
int sockfd,i;
char ch = 'a';
char buf[MAXLINE];
struct sockaddr_in serv_addr; //服务器地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);
sockfd= socket(AF_INET,SOCK_STREAM,0);
connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
while(1){
for(i=0;i<MAXLINE/2;++i)
buf[i]= ch;
buf[i-1]='\n';
ch++;
for(;i<MAXLINE;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
write(sockfd,buf,sizeof(buf));
sleep(5);
}
close(sockfd);
return 0;
}
优点:
高效 。 突破1024 文件描述符
epoll是Linux下多路复用IO接口select/poll的增强版本,
它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,
因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,
另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
缺点:
不能跨平台
epoll ET 模式 + 非阻塞、轮询+ void* ptr
原来:socket、bind、1isten -- epol1_create 创建监听红黑树--返回epfd - -epol1_ct1()向树上添加一个监听fd - while (1) --
epol1_wait 监听 -- 对应监听 fd 有事件产生 -- 返回监听满足数组。-- 判断返回数组元素 -- 1fd满足- Accept - cfd满足
- read() ---小-->大-- write回去。
反应堆:不但要监听 cfd的 读事件、还要监听cfd的写事件。
ocket、bind、1isten -- epol1_create 创建监听红黑树--返回epfd - -epol1_ct1()向树上添加一个监听fd - while (1) --
epol1_wait 监听 -- 对应监听 fd 有事件产生 -- 返回监听满足数组。-- 判断返回数组元素 -- 1fd满足- Accept - cfd满足
- read() ---小-->大 --cfd从监听红黑树上摘下 --EPOLLOUT ---回调函数 -- epoll_ctl() --EPOLL_CTL_ADD 重新放到红黑树上
监听写事件 -- 等待 epoll_wait 返回 -- 说明 cfd 可写 -- write回去 -- cfd从监听红黑树上摘下 --EPOLLIN --epoll_ctl()--
EPOLL_CTL_ADD 重新放到红黑树上监听读事件 --epoll_wait 监听
来源:CSDN
作者:我喜欢的人很优秀
链接:https://blog.csdn.net/weixin_44374280/article/details/104384114