Linux企业级开发技术(4)——epoll企业级开发之epoll例程
为了使大家更加深入了解epoll模型在企业应用中的使用,下面给出一段基于epoll的服务器代码,并在代码中添加了详细注释:
#include <deque>
#include <map>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <sys/time.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h> #include <string>
#include <cstdio>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h> #include <cstdlib>
#include <cctype>
#include <sstream>
#include <utility>
#include <stdexcept> #include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <signal.h> using namespace std; #define MAXLINE 5
#define LISTENQ 5
#define SERV_PORT 5000 bool bWrite = false; void setnonblocking(int sock)
{
intopts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts= opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
} static void sig_pro(int signum)
{
cout<< "recv signal:" << signum << endl;
} int main(int argc, char* argv[])
{
inti, n, listenfd, connfd, nfds;
charline[MAXLINE + 1];
socklen_tclilen; //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
structepoll_event ev,events[20]; //生成用于处理accept的epoll专用的文件描述符
intepfd=epoll_create(256);
structsockaddr_in clientaddr;
structsockaddr_in serveraddr; //为让应用程序不必对慢速系统调用的errno做EINTR检查,可以采取两种方式:1.屏蔽中断信号,2.处理中断信号
//1.由signal()函数安装的信号处理程序,系统默认会自动重启动被中断的系统调用,而不是让它出错返回,
// 所以应用程序不必对慢速系统调用的errno做EINTR检查,这就是自动重启动机制.
//2.对sigaction()的默认动作是不自动重启动被中断的系统调用,
// 因此如果我们在使用sigaction()时需要自动重启动被中断的系统调用,就需要使用sigaction的SA_RESTART选项 //忽略信号
//sigset_tnewmask;
//sigemptyset(&newmask);
//sigaddset(&newmask,SIGINT);
//sigaddset(&newmask,SIGUSR1);
//sigaddset(&newmask,SIGUSR2);
//sigaddset(&newmask,SIGQUIT);
//pthread_sigmask(SIG_BLOCK,&newmask, NULL); //处理信号
//默认自动重启动被中断的系统调用,而不是让它出错返回,应用程序不必对慢速系统调用的errno做EINTR检查
//signal(SIGINT,sig_pro);
//signal(SIGUSR1,sig_pro);
//signal(SIGUSR2,sig_pro);
//signal(SIGQUIT,sig_pro); structsigaction sa;
sa.sa_flags = SA_RESTART; //SA_RESART:自动重启动被中断的系统调用,0:默认不自动重启动被中断的系统调用
sa.sa_handler = sig_pro;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL); /*//系统调用被中断信号中断的测试验证
charbuf[1024];
int nn; while(1){
if((nn = read(STDIN_FILENO, buf, 1024)) == -1) {
if(errno == EINTR)
printf("read isinterrupted\n");
}
else {
write(STDOUT_FILENO, buf, nn);
}
} return 0;*/ listenfd= socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family= AF_INET;
serveraddr.sin_addr.s_addr= htonl(INADDR_ANY);
serveraddr.sin_port=htons(SERV_PORT);
bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));
listen(listenfd,LISTENQ); for( ; ; )
{
cout<< "active" << endl; //等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i = 0; i < nfds; ++i)
{
if(events[i].data.fd < 0)
{
continue;
} if(events[i].data.fd == listenfd) //监听上的事件
{
cout<< "[conn] events=" << events[i].events << endl; if(events[i].events&EPOLLIN) //有连接到来
{
do
{
clilen= sizeof(struct sockaddr);
connfd= accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd > 0)
{
cout<< "[conn] peer=" << inet_ntoa(clientaddr.sin_addr)<< ":" << ntohs(clientaddr.sin_port) << endl; //把socket设置为非阻塞方式
setnonblocking(connfd);
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else
{
cout<< "[conn] errno=" << errno << endl; if(errno == EAGAIN) //没有连接需要接收了
{
break;
}
elseif (errno == EINTR) //可能被中断信号打断,,经过验证对非阻塞socket并未收到此错误,应该可以省掉该步判断
{
;
}
else //其它情况可以认为该描述字出现错误,应该关闭后重新监听
{
cout<< "[conn] close listen because accept fail and errno not equaleagain or eintr" << endl; //此时说明该描述字已经出错了,需要重新创建和监听
close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]); //重新监听
listenfd= socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd);
ev.data.fd=listenfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));
listen(listenfd,LISTENQ);
break;
}
}
}while (1);
}
elseif (events[i].events&EPOLLERR || events[i].events&EPOLLHUP) //有异常发生
{
cout<< "[conn] close listen because epollerr or epollhup" <<errno << endl; close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]); //重新监听
listenfd= socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd);
ev.data.fd=listenfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));
listen(listenfd,LISTENQ);
}
}
else //连接上的事件
{
cout<< "[data] events=" << events[i].events << endl; if(events[i].events&EPOLLIN) //有数据可读
{
do
{
n= read(events[i].data.fd, line, MAXLINE);
if(n > 0) //读到数据
{
line[n]= '\0'; //综合下面两种情况,在读到字节数大于0时必须继续读,不管读到字节数是否等于接收缓冲区大小,
//也不管错误代码是否为EAGAIN,否则要么导致关闭事件丢失,要么导致后续数据的丢失
if(n < MAXLINE)
{
//经过验证,如果对方发送完数据后就断开,即使判断是否错误代码为EAGAIN,也会导致close事件丢失,
//必须继续读,以保证断开事件的正常接收
cout<< "[data] n > 0, read less recv buffer size, errno="<< errno << ",len=" << n << ",data=" << line << endl;
}
else
{
//经过验证,发送字节数大于等于接收缓冲区时,读到字节数为接收缓冲区大小,错误代码为EAGAIN,
//必须继续读,以保证正常接收后续数据
cout<< "[data] n > 0, read equal recv buffer size, errno="<< errno << ",len=" << n << ",data=" << line << endl;
}
}
elseif (n < 0) //读取失败
{
if (errno == EAGAIN) //没有数据了
{
cout<< "[data] n < 0, no data, errno=" << errno <<endl; break;
}
else if(errno == EINTR) //可能被内部中断信号打断,经过验证对非阻塞socket并未收到此错误,应该可以省掉该步判断
{
cout<< "[data] n < 0, interrupt, errno=" << errno <<endl;
}
else //客户端主动关闭
{
cout<< "[data] n < 0, peer close, errno=" << errno<< endl; close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
break;
}
}
elseif (n == 0) //客户端主动关闭
{
cout<< "[data] n = 0, peer close, errno=" << errno <<endl; //同一连接可能会出现两个客户端主动关闭的事件,一个errno是EAGAIN(11),一个errno是EBADF(9),
//对错误的文件描述符EBADF(9)进行关闭操作不会有什么影响,故可以忽略,以减少errno判断的开销 close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
break;
}
}while (1);
}
elseif (events[i].events&EPOLLOUT) //可以写数据
{
cout<< "[data] epollout" << endl; if(events[i].data.u64 >> 32 == 0x01) //假定0x01代表关闭连接
{
//在需要主动断开连接时仅注册此事件不含可读事件,用来处理服务端主动关闭
close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
}
else //其它情况可以去设置该连接的可写标志
{
bWrite= true;
}
}
elseif (events[i].events&EPOLLERR || events[i].events&EPOLLHUP) //有异常发生
{
cout<< "[data] close peer because epollerr or epollhup" <<endl; close(events[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
}
}
}
}
return 0;
} ssize_t mysend(int socket, const void*buffer, size_t length, int flags)
{
ssize_ttmp;
size_tleft = length;
constchar *p = (const char *)buffer; while(left > 0)
{
if(bWrite) //判断该连接的可写标志
{
tmp= send(socket, p, left, flags);
if(tmp < 0)
{
//当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,
if(errno == EAGAIN)
{
//设置该连接的不可写标志
bWrite= false; usleep(20000);
continue;
}
elseif (errno == EINTR)
{
//被中断信号打断的情况可以忽略,经过验证对非阻塞socket并未收到此错误,应该可以省掉该步判断
}
else
{
//其它情况下一般都是连接出现错误了,外部采取关闭措施
break;
}
}
elseif ((size_t)tmp == left)
{
break;
}
else
{
left-= tmp;
p+= tmp;
}
}
else
{
usleep(20000);
}
} return(ssize_t)(length - left);
}
Linux企业级开发技术(4)——epoll企业级开发之epoll例程的更多相关文章
- 关于PHP在企业级开发领域的访谈——企业级开发,PHP准备好了吗?
关于PHP在企业级开发领域的访谈 ——企业级开发,PHP准备好了吗? 转自:http://www.nowamagic.net/librarys/veda/detail/256 虽然PHP是Web应用开 ...
- Linux企业级开发技术(1)——epoll企业级开发之简介
Epoll是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入.和 select 相似,是高效 I/O 多路复用技术. 其实在 Linux 下设 ...
- Linux企业级开发技术(2)——epoll企业级开发之epoll接口
epoll的接口非常简单,总共只有三个函数: 1.int epoll_create(intsize); 生成一个 Epoll 专用的文件描述符,size用来告诉内核这个监听的数目一共有多大.这个参数不 ...
- Linux企业级开发技术(3)——epoll企业级开发之epoll模型
EPOLL事件有两种模型: Edge Triggered (ET) 边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据. Level Triggered (LT) 水平触发 只要有数据都会触 ...
- Linux企业级开发技术(6)——libevent企业级开发之内存管理
默认情况下,libevent使用C库的内存管理函数在堆上分配内存.通过提供malloc.realloc和free的替代函数,可以让libevent使用其他的内存管理器.希望libevent使 用一个更 ...
- Linux企业级开发技术(7)——libevent企业级开发之锁和线程
编写多线程程序的时候,在多个线程中同时访问同样的数据并不总是安全的. libevent的结构体在多线程下通常有三种工作方式: 1.某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的. 2 ...
- Linux企业级开发技术(5)——libevent企业级开发之简介
Libevent是一个用于编写高速可移植非阻塞IO应用的库,它的设计目标是: 可移植性:使用libevent编写的程序应该可以在libevent支持的所有平台上工作.即使没有好的方式进行非阻塞IO,l ...
- Android安全开发之ZIP文件目录遍历
1.ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在“../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件.如果被覆盖掉的文件是动态链接s ...
- Android数据绑定技术一,企业级开发
PS:数据绑定,顾名思义是数据与一些控件或者用户账号等绑定,这样用的好处是便于管理.代码清晰,量少. 首先要了解什么是数据绑定? 为什么要用数据绑定? 怎么用数据绑定? 语法的使用 简单例子,数据绑定 ...
随机推荐
- Java基础知识强化16:深入分析Java线程中断机制
1.Thread.interrupt真的能中断线程吗? 在平时的开发过程中,相信都会使用到多线程,在使用多线程时,大家也会遇到各种各样的问题,今天我们就来说说一个多线程的问题——线程中断. ...
- Topcoder SRM 661 (Div.1) 250 MissingLCM - 数论
[题意] 给你一个数N(1<=N<=10^6),要求最小的M(M>N),使得lcm(n+1,n+2,...m)=lcm(1,2,3,...,m) [思路] 手速太慢啦,等敲完代码的时 ...
- javascript中,你真的会用console吗?
使用console进行性能测试和计算代码运行时间 对于前端开发人员,在开发过程中经常需要监控某些表达式或变量的值,如果使用用debugger会显得过于笨重,最常用的方法是会将值输出到控制台上方便调试. ...
- 在vSphere5.0虚拟机里的Ubuntu Server 32位安装JDK
本机操作系统Win7 服务器用vSphere 5.0 虚拟机 在虚拟机安装了Ubuntu Server 12.04 1.首先到Oracle官网上下载jdk-7u51-linux-i586.tar.g ...
- 关于一些Android冷知识
1. 在Android4.0以后,EditText就由以前的输入框变成了一条划线的输入方式,如需要变为老版本的,只需在layout里面引入代码: android:background="@a ...
- 关于get和set访问器以及属性和字段变量的区别问题
属性是对一个或者多个字段的封装. 类里面为什么要用一个共有的属性来封装其中的字段,也可以这样说用共有属性来封装私有变量,其中的好处应该大家都说的出来,就是为了实现数据的封装和保证了数据的安全 ...
- webservice发送数据,取数据的方式
1.通过调用对方的webservice接口方式,取得对方的数据,并解析(我们取数据) 2.对方调用我们的接口,得到数据.(对方来取) 3.对用对方接口,将我们的数据封装好以后,直接调用对方接口,对方可 ...
- JSONKit 在iOS9 arm7 64位下出现的问题
最近遇到了一个关于JSONKit的问题,在项目加了arm7 64位以后,JSONKIT会出现[params JSONString] forKey:@”gson”];报错的情况,如下图 具体原因不太清楚 ...
- LAMP的编译日志,
在CentOS5.2上,编译LAMP的,两年前测试通过的,现在留印 ### 在记事本中 ,不要打开 自动换行,否则一些命令 无法正常运行###把源文件考到/src/目录下,然后进入/src////// ...
- Bootstrap_Javascript_滚动监视器
滚动监控器是Bootstrap提供的非常实用的JavaScript插件,被广泛应用到Web开发中.其表现形式是: 1.当用户鼠标滚动时,滚动条的位置会自动更新导航条中相应的导航项. 2.用户拖动滚动条 ...