Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1706800
  • 博文数量: 98
  • 博客积分: 667
  • 博客等级: 上士
  • 技术积分: 1631
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-27 15:59
个人简介

一沙一世界 一树一菩提

文章分类

全部博文(98)

文章存档

2021年(8)

2020年(16)

2019年(8)

2017年(1)

2016年(11)

2015年(17)

2014年(9)

2013年(4)

2012年(19)

2011年(1)

2009年(4)

分类: 网络与安全

2014-05-26 16:17:23

2014-5-26第一次编辑
首先,文章中许多东西都是引用网络上其他人提供的信息。
先说说考虑到这个文章的背景啊。
前段时间写一个数据隔离、中转服务器的代码,这台机器主要是隔离客户端和服务器端。主要是过滤信息,隔离外界与数据库的直接联系。
但是到现在我一直怀疑这台隔离、中转服务器能不能起到相应的作用。
客户端是无线结点,通过gprs连接。领导拍板要能应付10000个结点的连接。
由于应用的特殊性,这些结点的连接周期不是随机的,而是以小时级别的周期循环连接。然后把每个连接的具体需求通过http形式操作另外一台机器的数据库。

首先为了减少隔离服务器压力,根据id号在循环周期内分时,这样连接处理压力一下就很小了。
由于对多线程编程比较熟悉,所以就采用先建立线程池,来的连接用fifo存储。用互斥锁来管理fifo中的连接分配给哪个线程。
反正设计目标是实现了。当时也就没有多想,收工了。

后来无意中看到关于网络编程是采用多进程还是多线程的帖子。结合自己的背景。感觉很有必要记录下来,等下次不至于找不到想看的东西。
最先记录的就是下面的内容:(来自网络)
“多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。”
-------这个适用场景是,各个进程之间通讯交互少,藕荷少,很适合多进程方式

“多线程是平面交通系统,造价低,但红绿灯太多,老堵车。”
-------这个原因是业务决定了,各模块间交互频繁,藕荷性高,适合多线程。

“高性能交易服务器中间件,如TUXEDO,都是主张多进程的。实际测试表明,TUXEDO性能和并发效率是非常高的。”
------不是主张多进程,实在是它产生与1983年,那时侯多线程标准混乱,接口不友好。直到90年代,POSIX1003.4a小组才统一了这种局面。TUXEDO作为中间件,多进程的分布式灵活部署,以及高性能硬件的机器参与,促成了TUXEDO的高性能。

总之,多进程多线程的使用场景不同,多进程可以更灵活的分布式部署,整合更多的机器。藕荷性高数据交互频繁的单个模块内适合多线程。

后来又看到另外的描述:
对nginx和ligty的分析。
“为什么nginx/ligty用多进程+io多路复用就获得高效结果呢





就到这里吧,看到这里,分析我的程序情况,确实时间主要耗费在io等待上,处理时间很短。按照这个理解,用多进程应该性能提高不少。
另外进程数量,暂时不好确定,貌似和多核cpu有关,大概对应关系几核几进程吧。这个具体性能需要根据实际情况验证了。


2014-5-27 第二次追加编辑
今天主要是记录下epoll的优缺点。 大部分来源于百度百科
一    适用情况:
epoll是为处理大批量而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量中只有少量活跃的情况下的系统CPU利用率。
这里,我们要注意使用情况的3个小注意点:
1  多fd,select能支持的fd数量最大为FD_SETSIZE,默认值是1024 epoll则没有这个限制,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和关系很大。
2  大量并发连接而少数活跃的情况。如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3  降低cpu利用率,不是io效率,epoll只对活跃的socket作出操作,同样的操作,select和poll对全部socket进行检索处理,而epoll只对活跃的socket进行操作,在相同的socket和相同的活跃socket数量情况下,epoll降低cpu利用率。

二    家族函数
epoll相关的系统调用有:
epoll_create,      epoll_create用来创建一个epoll
epoll_ctl,    epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件
epoll_wait。    epoll_wait/epoll_pwait接收发生在被侦听的描述符上的,用户感兴趣的IO事件
Linux-2.6.19又引入了可以屏蔽指定信号的epoll_wait: epoll_pwait。
epoll用完后,直接用close关闭即可,非常方便。事实上,任何被侦听的文件符只要其被关闭,那么它也会自动从被侦听的集合中删除,很是智能。
注意:
每次添加/修改/删除被侦听都需要调用epoll_ctl,所以要尽量少地调用epoll_ctl,防止其所引来的开销抵消其带来的好处。有的时候,应用中可能存在大量的(比如说Web服务器),epoll_ctl将被频繁地调用,可能成为这个系统的瓶颈。

三    使用情况
那么究竟如何来使用epoll呢?其实非常简单。
通过在包含一个头文件#include 以及几个简单的API将可以大大的提高你的的支持人数。
首先通过epoll_create(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event *events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
1
nfds=epoll_wait(kdpfd,events,maxevents,-1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。maxevents是当前需要监听的所有socket。最后一个timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件发生,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
epoll_wait范围之后应该是一个循环,遍历所有的事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
for(n=0;n
{
if(events[n].data.fd==listener)
{
//如果是主socket的事件的话,则表示
//有新连接进入了,进行新连接的处理。
client=accept(listener,(structsockaddr*)&local,&addrlen);
if(client<0)
{
perror("accept");
continue;
}
setnonblocking(client);//将新连接置于非阻塞模式
ev.events=EPOLLIN|EPOLLET;//并且将新连接也加入EPOLL的监听队列。
//注意,这里的参数EPOLLIN|EPOLLET并没有设置对写socket的监听,
//如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作
//也监听的话,应该是EPOLLIN|EPOLLOUT|EPOLLET
ev.data.fd=client;
if(epoll_ctl(kdpfd,EPOLL_CTL_ADD,client,&ev)<0)
{
//设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,
//这里用EPOLL_CTL_ADD来加一个新的epoll事件,通过EPOLL_CTL_DEL来减少一个
//epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。
fprintf(stderr,"epollsetinsertionerror:fd=%d0,client);
return-1;
}
}
elseif(event[n].events&EPOLLIN)
{
//如果是已经连接的用户,并且收到数据,
//那么进行读入
intsockfd_r;
if((sockfd_r=event[n].data.fd)<0)continue;
read(sockfd_r,buffer,MAXSIZE);
//修改sockfd_r上要处理的事件为EPOLLOUT
ev.data.fd=sockfd_r;
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd_r,&ev)
}
elseif(event[n].events&EPOLLOUT)
{
//如果有数据发送
intsockfd_w=events[n].data.fd;
write(sockfd_w,buffer,sizeof(buffer));
//修改sockfd_w上要处理的事件为EPOLLIN
ev.data.fd=sockfd_w;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd_w,&ev)
}
do_use_fd(events[n].data.fd);
}
对,epoll的操作就这么简单,总共不过4个API:epoll_create, epoll_ctl, epoll_wait和close。
四    epoll为何快
1    epoll比select的提高实际上是一个用空间换时间思想的具体应用,具体就是epoll_wait只返回活跃的socket。不必像select和poll那样去检索查找活跃的socket。
2     select和poll每次执行前都需要把socket传递
t给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的描述符会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket描述符给内核,你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的描述符了,每次调用epoll_ctl只是在往内核的数据结构里塞入或者去掉socket描述符。所以epoll_ctl频繁使用会成为系统的瓶颈。

五    epoll的两种模式(LT和ET)
无论是LT和ET模式,都适用于以上所说的流程。区别是:
LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。
这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。 所以ET触发模式编程的时候一定要注意,一次处理数据一定要处理完毕。否则数据就丢了。


阅读(3440) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~