linux多路复用
⑴ Handler消息机制(一):linux的epoll机制
在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比select模型, poll使用链表保存文件描述符,因此没有了监视文件数量的限制 ,但其他三个缺点依然存在。
假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。
由于epoll的实现机制与select/poll机制完全不同,上面所说的 select的缺点在epoll上不复存在。
设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?
在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:
1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3)调用epoll_wait收集发生的事件的连接
如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。
而所有 添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法 。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。
epoll结构示意图
通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。
events可以是以下几个宏的集合:
EPOLLIN:触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);
EPOLLOUT:触发该事件,表示对应的文件描述符上可以写数据;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET:将EPOLL设为边缘触发(EdgeTriggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
示例:
ET(EdgeTriggered) :高速工作模式,只支持no_block(非阻塞模式)。在此模式下,当描述符从未就绪变为就绪时,内核通过epoll告知。然后它会假设用户知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到某些操作导致那个文件描述符不再为就绪状态了。(触发模式只在数据就绪时通知一次,若数据没有读完,下一次不会通知,直到有新的就绪数据)
LT(LevelTriggered) :缺省工作方式,支持blocksocket和no_blocksocket。在LT模式下内核会告知一个文件描述符是否就绪了,然后可以对这个就绪的fd进行IO操作。如果不作任何操作,内核还是会继续通知!若数据没有读完,内核也会继续通知,直至设备数据为空为止!
1.我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)……
ET工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,在第2步执行了一个写操作,第三步epoll_wait会返回同时通知的事件会销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
只有当read(2)或者write(2)返回EAGAIN时(认为读完)才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时(即小于sizeof(buf)),就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
LT工作模式:
LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。
当调用 epoll_wait检查是否有发生事件的连接时,只是检查 eventpoll对象中的 rdllist双向链表是否有 epitem元素而已,如果 rdllist链表不为空,则把这里的事件复制到用户态内存中,同时将事件数量返回给用户。因此,epoll_wait的效率非常高。epoll_ctl在向 epoll对象中添加、修改、删除事件时,从 rbr红黑树中查找事件也非常快,也就是说,epoll是非常高效的,它可以轻易地处理百万级别的并发连接。
1.减少用户态和内核态之间的文件句柄拷贝;
2.减少对可读可写文件句柄的遍历。
https://cloud.tencent.com/developer/information/linux%20epoll%E6%9C%BA%E5%88%B6
https://blog.csdn.net/u010657219/article/details/44061629
https://jiahao..com/s?id=1609322251459722004&wfr=spider&for=pc
⑵ linux下的epoll有什么作用
你好,希望我的回答对你有帮助 1. Epoll是何方神圣? Epoll可是当前在Linux下开发大规模并发网络程序的热门人选,Epoll 在Linux2.6内核中正式引入,和select相似,其实都I/O多路复用技术而已,并没有什么神秘的。 其实在Linux下设计并发网络程序,向来不缺少方法,比如典型的Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及select模型和poll模型,那为何还要再引入Epoll这个东东呢?那还是有得说说的… 2. 常用模型的缺点 如果不摆出来其他模型的缺点,怎么能对比出Epoll的优点呢。 2.1 PPC/TPC模型 这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我。只是PPC是为它开了一个进程,而TPC开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程/线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。 2.2 select模型 1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧… 2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!! 3. 内核/用户空间内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。 2.3 poll模型 基本上效率和select是相同的,select缺点的2和3它都没有改掉。 3. Epoll的提升 把其他模型逐个批判了一下,再来看看Epoll的改进之处吧,其实把select的缺点反过来那就是Epoll的优点了。 3.1. Epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。 3.2. 效率提升,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。 3.3. 内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。 4. Epoll为什么高效 Epoll的高效和其数据结构的设计是密不可分的,这个下面就会提到。 首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了快去处理,而应用程序必须轮询所有的FD集合,测试每个FD是否有事件发生,并处理事件;代码像下面这样: int res = select(maxfd+1, &readfds, NULL, NULL, 120); if(res > 0) { for (int i = 0; i < MAX_CONNECTION; i++) { if (FD_ISSET(allConnection[i], &readfds)) { handleEvent(allConnection[i]); } } } // if(res == 0) handle timeout, res < 0 handle error Epoll不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD 集合。 intres = epoll_wait(epfd, events, 20, 120); for(int i = 0; i < res;i++) { handleEvent(events[n]); } 5. Epoll关键数据结构 前面提到Epoll速度快和其数据结构密不可分,其关键数据结构就是: structepoll_event { __uint32_t events; // Epoll events epoll_data_t data; // User data variable }; typedefunion epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; 可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。 6. 使用Epoll 既然Epoll相比select这么好,那么用起来如何呢?会不会很繁琐啊…先看看下面的三个函数吧,就知道Epoll的易用了。 int epoll_create(int size); 生成一个Epoll专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个Epoll fd上能关注的最大socket fd数,大小自定,只要内存足够。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event ); 控制某个Epoll文件描述符上的事件:注册、修改、删除。其中参数epfd是epoll_create()创建Epoll专用的文件描述符。相对于select模型中的FD_SET和FD_CLR宏。 int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout); 等待I/O事件的发生;参数说明: epfd:由epoll_create() 生成的Epoll专用的文件描述符; epoll_event:用于回传代处理事件的数组; maxevents:每次能处理的事件数; timeout:等待I/O事件发生的超时值; 返回发生事件数。 相对于select模型中的select函数。 7. 例子程序 下面是一个简单Echo Server的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。 参考地址:http://blog.csdn.net/sparkliang/article/details/4770655
⑶ epoll是什么意思
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显着提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
⑷ 关于Linux下的select/epoll
select这个系统调用的原型如下
第一个参数nfds用来告诉内核 要扫描的socket fd的数量+1 ,select系统调用最大接收的数量是1024,但是如果每次都去扫描1024,实际上的数量并不多,则效率太低,这里可以指定需要扫描的数量。 最大数量为1024,如果需要修改这个数量,则需要重新编译Linux内核源码。
第2、3、4个参数分别是readfds、writefds、exceptfds,传递的参数应该是fd_set 类型的引用,内核会检测每个socket的fd, 如果没有读事件,就将对应的fd从第二个参数传入的fd_set中移除,如果没有写事件,就将对应的fd从第二个参数的fd_set中移除,如果没有异常事件,就将对应的fd从第三个参数的fd_set中移除 。这里我们应该 要将实际的readfds、writefds、exceptfds拷贝一份副本传进去,而不是传入原引用,因为如果传递的是原引用,某些socket可能就已经丢失 。
最后一个参数是等待时间, 传入0表示非阻塞,传入>0表示等待一定时间,传入NULL表示阻塞,直到等到某个socket就绪 。
FD_ZERO()这个函数将fd_set中的所有bit清0,一般用来进行初始化等。
FD_CLR()这个函数用来将bitmap(fd_set )中的某个bit清0,在客户端异常退出时就会用到这个函数,将fd从fd_set中删除。
FD_ISSET()用来判断某个bit是否被置1了,也就是判断某个fd是否在fd_set中。
FD_SET()这个函数用来将某个fd加入fd_set中,当客户端新加入连接时就会使用到这个函数。
epoll_create系统调用用来创建epfd,会在开辟一块内存空间(epoll的结构空间)。size为epoll上能关注的最大描述符数,不够会进行扩展,size只要>0就行,早期的设计size是固定大小,但是现在size参数没什么用,会自动扩展。
返回值是epfd,如果为-1则说明创建epoll对象失败 。
第一个参数epfd传入的就是epoll_create返回的epfd。
第二个参数传入对应操作的宏,包括 增删改(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD) 。
第三个参数传入的是 需要增删改的socket的fd 。
第四个参数传入的是 需要操作的fd的哪些事件 ,具体的事件可以看后续。
返回值是一个int类型,如果为-1则说明操作失败 。
第一个参数是epfd,也就是epoll_create的返回值。
第二个参数是一个epoll_event类型的指针,也就是传入的是一个数组指针。 内核会将就绪的socket的事件拷贝到这个数组中,用户可以根据这个数组拿到事件和消息等 。
第三个参数是maxevents,传入的是 第二个参数的数组的容量 。
第四个参数是timeout, 如果设为-1一直阻塞直到有就绪数据为止,如果设为0立即返回,如果>0那么阻塞一段时间 。
返回值是一个int类型,也就是就绪的socket的事件的数量(内核拷贝给用户的events的元素的数量),通过这个数量可以进行遍历处理每个事件 。
一般需要传入 ev.data.fd 和 ev.events ,也就是fd和需要监控的fd的事件。事件如果需要传入多个,可以通过按位与来连接,比如需要监控读写事件,只需要像如下这样操作即可: ev.events=EPOLLIN | EPOLLOUT 。
LT(水平触发), 默认 的工作模式, 事件就绪后用户可以选择处理和不处理,如果用户不处理,内核会对这部分数据进行维护,那么下次调用epoll_wait()时仍旧会打包出来 。
ET(边缘触发),事件就绪之后, 用户必须进行处理 ,因为内核把事件打包出来之后就把对应的就绪事件给清掉了, 如果不处理那么就绪事件就没了 。ET可以减少epoll事件被重复触发的次数,效率比LT高。
如果需要设置为边缘触发只需要设置事件为类似 ev.events=EPOLLIN | EPOLLET 即可 。
select/poll/epoll是nio多路复用技术, 传统的bio无法实现C10K/C100K ,也就是无法满足1w/10w的并发量,在这么高的并发量下,在进行上下文切换就很容易将服务器的负载拉飞。
1.将fd_set从用户态拷贝到内核态
2.根据fd_set扫描内存中的socket的fd的状态,时间复杂度为O(n)
3.检查fd_set,如果有已经就绪的socket,就给对应的socket的fd打标记,那么就return 就绪socket的数量并唤醒当前线程,如果没有就绪的socket就继续阻塞当前线程直到有socket就绪才将当前线程唤醒。
4.如果想要获取当前已经就绪的socket列表,则还需要进行一次系统调用,使用O(n)的时间去扫描socket的fd列表,将已经打上标记的socket的fd返回。
CPU在同一个时刻只能执行一个程序,通过RR时间片轮转去切换执行各个程序。没有被挂起的进程(线程)则在工作队列中排队等待CPU的执行,将进程(线程)从工作队列中移除就是挂起,反映到Java层面的就是线程的阻塞。
什么是中断?当我们使用键盘、鼠标等IO设备的时候,会给主板一个电流信号,这个电流信号就给CPU一个中断信号,CPU执行完当前的指令便会保存现场,然后执行键盘/鼠标等设备的中断程序,让中断程序获取CPU的使用权,在中断程序后又将现场恢复,继续执行之前的进程。
如果第一次没检测到就绪的socket,就要将其进程(线程)从工作队列中移除,并加入到socket的等待队列中。
socket包含读缓冲区+写缓冲区+等待队列(放线程或eventpoll对象)
当从客户端往服务器端发送数据时,使用TCP/IP协议将通过物理链路、网线发给服务器的网卡设备,网卡的DMA设备将接收到的的数据写入到内存中的一块区域(网卡缓冲区),然后会给CPU发出一个中断信号,CPU执行完当前指令则会保存现场,然后网卡的中断程序就获得了CPU的使用权,然后CPU便开始执行网卡的中断程序,将内存中的缓存区中的数据包拿出,判断端口号便可以判断它是哪个socket的数据,将数据包写入对应的socket的读(输入)缓冲区,去检查对应的socket的等待队列有没有等待着的进程(线程),如果有就将该线程(进程)从socket的等待队列中移除,将其加入工作队列,这时候该进程(线程)就再次拥有了CPU的使用权限,到这里中断程序就结束了。
之后这个进程(线程)就执行select函数再次去检查fd_set就能发现有socket缓冲区中有数据了,就将该socket的fd打标记,这个时候select函数就执行完了,这时候就会给上层返回一个int类型的数值,表示已经就绪的socket的数量或者是发生了错误。这个时候就再进行内核态到用户态的切换,对已经打标记的socket的fd进行处理。
将原本1024bit长度的bitmap(fd_set)换成了数组的方式传入 ,可以 解决原本1024个不够用的情况 ,因为传入的是数组,长度可以不止是1024了,因此socket数量可以更多,在Kernel底层会将数组转换成链表。
在十多年前,linux2.6之前,不支持epoll,当时可能会选择用Windows/Unix用作服务器,而不会去选择Linux,因为select/poll会随着并发量的上升,性能变得越来越低,每次都得检查所有的Socket列表。
1.select/poll每次调用都必须根据提供所有的socket集合,然后就 会涉及到将这个集合从用户空间拷贝到内核空间,在这个过程中很耗费性能 。但是 其实每次的socket集合的变化也许并不大,也许就1-2个socket ,但是它会全部进行拷贝,全部进行遍历一一判断是否就绪。
2.select/poll的返回类型是int,只能代表当前的就绪的socket的数量/发生了错误, 如果还需要知道是哪些socket就绪了,则还需要再次使用系统调用去检查哪些socket是就绪的,又是一次O(n)的操作,很耗费性能 。
1.epoll在Kernel内核中存储了对应的数据结构(eventpoll)。我们可以 使用epoll_create()这个系统调用去创建一个eventpoll对象 ,并返回eventpoll的对象id(epfd),eventpoll对象主要包括三个部分:需要处理的正在监听的socket_fd列表(红黑树结构)、socket就绪列表以及等待队列(线程)。
2.我们可以使用epoll_ctl()这个系统调用对socket_fd列表进行CRUD操作,因为可能频繁地进行CRUD,因此 socket_fd使用的是红黑树的结构 ,让其效率能更高。epoll_ctl()传递的参数主要是epfd(eventpoll对象id)。
3.epoll_wait()这个系统调用默认会 将当前进程(线程)阻塞,加入到eventpoll对象的等待队列中,直到socket就绪列表中有socket,才会将该进程(线程)重新加入工作队列 ,并返回就绪队列中的socket的数量。
socket包含读缓冲区、写缓冲区和等待队列。当使用epoll_ctl()系统调用将socket新加入socket_fd列表时,就会将eventpoll对象引用加到socket的等待队列中, 当网卡的中断程序发现socket的等待队列中不是一个进程(线程),而是一个eventpoll对象的引用,就将socket引用追加到eventpoll对象的就绪列表的尾部 。而eventpoll对象中的等待队列存放的就是调用了epoll_wait()的进程(线程),网卡的中断程序执行会将等待队列中的进程(线程)重新加入工作队列,让其拥有占用CPU执行的资格。epoll_wait()的返回值是int类型,返回的是就绪的socket的数量/发生错误,-1表示发生错误。
epoll的参数有传入一个epoll_event的数组指针(作为输出参数),在调用epoll_wait()返回的同时,Kernel内核还会将就绪的socket列表添加到epoll_event类型的数组当中。
⑸ 为什么用 linux 做服务器 如果做用它做游戏服务器, 支持多少 用户同时在线
让我来为你解答。你的问题,最重要的不是操作系统的问题,而是你的架构选择的问题。你所说的MyEclipse+Tomcat这个根本就不搭嘛,由此可见你不懂架构。MyEclipse只是一个IDE而已,就用途和记事本是一样的,而Tomcat可以说是一个应用服务器,中间件的组成部分,用于J2EE的开发。
你这样说我就默认你们做的是网页游戏,因为做PC游戏的,不管是网游还是单机,没人用Java写。
如果是网游,并且想获得高并发,那么推荐使用Linux。Linux的性价比绝对是最好的,稳定高性能。这点Windows比不上,原因就在于两个系统的底层设计上,Windows本身的设计决定了它没法处理很高的并发,如果你懂网络编程的话就很容易理解,在UNIX系统上有非常牛的功能就是I/O多路复用,通过该功能,可以轻易支持上千甚至几万的并发(当然机器的配置要跟上)。而Linux相对于UNIX不仅实现了传统的I/O多路复用,更提供了e-poll接口,e-poll接口如果正确使用的话可以带来性能的极大提升,也正是这个原因Nginx网页服务器在短期内侵蚀了Apache的部分市场份额。而Windows虽然也有I/O多路复用的概念,但其实是信号驱动的,仅是功能的模拟,而没能在内核中真正优化。Windows下要高的并发,只有一种办法,就是做线程池,还是得信号驱动并发。所以Windows在普通计算机上的并发就少的可怜了,其实即使实在服务器、小型机、大型机上如果装Windows也是浪费,Windows同UNIX/Linux在做开发上看简直就是小巫见大巫了,只能是大众产品,而不能是高端产品。你可以对比一下Windows同商业级UNIX/Linux的价格,就知道那个牛了。
Windows为了同UNIX兼容,不要认为Windows是最好的,最早的,这个普通人说说就算了,学计算机的说出去让人笑死。最当初的网络诞生自伯克利版本的UNIX,之后Linux在94年时就拥有了网络功能,微软在94年时的操作系统还是单用户,单任务的。而UNIX与Linux早已是多用户、多任务的了。就连图形界面,都是最先在UNIX开发出来的。至于如今为什么UNIX/Linux的图形界面没有广泛的被人知道,其原因不是不先进,而是刚开始定位就不是给普通人用的。你可以去大公司悄悄,银行、保险、证券、研究所、政府军方核心部门,都使用UNIX,民间使用的桌面UNIX比如苹果的Mac,都不错。就是因为UNIX是最好的,所以IBM、Oracle任何一家,都有能力同微软抗衡,在企业级市场尤其是高端企业市场,微软的发言权很少。
⑹ 请比较Linux与Windows在网络编程方面的特点
找了一段,大致涉及到了您的问题:
一、socket的模式
socket一般有两种模式:同步和异步(windows网络编程技术中也可叫锁定和非锁定,Linux网络编程叫阻塞和非阻塞)。
二、socket的类型
socket一般有三种类型,基于TCP的流式套接字,基于UDP的数据报套接字和原始套接字。
三、socket的IO模型
socket
的IO模型是编程中使用socket两种模式的策略,它们适用的场合不同,在不同的操作系统上支持的模型也不同,例如windows从NT版本才开始支持
完成端口模型。Linux和Windows所支持的模型也有区别,当然也有相同的地方,可能叫法不一样,但大致思路是一样的,下面分别介绍windows
和Linux的IO模型
1、 Windows下的套接字IO模型:
A、 Select(选择)模型
用于同步socket的状态检测模型,又叫(Linux)多路复用,可以同时检测多个socket的状态
B、 WSAAsyncSelect(异步选择)模型
用于异步socket的异步事件设置,它是基于Windows消息的模型,必须先打开一个窗口,然后把窗口和socket的消息绑定,这样,在socket有消息通知时,操作系统便通知窗口,然后在窗口进行处理。
C、 WSAEventSelect(异步事件)模型
用
于异步socket的异步事件,它是基于网络事件的模型,先使用CreateEvent创建一个事件,然后使用WSAEventSelect进行事件绑
定,然后可以使用WaitForMultipleObject(Event)进行事件监听,可以同时监听多个事件,不光是socket的,比如可以监听使
用CreateWaitableTimer创建的Timer等。
D、 重叠IO模型
用
于异步socket,在创建socket时需要在创建函数WSASocket中使用WSA_FLAG_OVERLAPPED标志,然后在投递IO请求的时
候将一个Overlapped结构体指针赋给投递函数,可以使用WSAWaitForMultipleObject来监听事件,然后使用
WSAGetOverlappedResult来获取IO的状态,也可以在Overlapped结构体中使用完成例程来处理,即在投递函数中把完成例程赋
给投递函数。
E、 完成端口模型
它
是迄今为止最复杂的一种IO模型,当应用程序需要管理众多的套接字并且希望随着系统内安装的CPU数目的增多,应用程序的性能也可以线性增加,就可以使用
这种模型,它的原理是每个CPU可以单独负责一个线程的执行,避免线程的频繁切换。使用这种模型往往可以达到最佳的系统性能。
首
先需要使用CreateIOCompletePort来创建完成端口,然后将IO句柄和此端口绑定,绑定也是使用此函数,当然也可以一次完成。接着是创建
工作者线程,工作者线程会使用GetQueuedCompletionStatus进入完成端口维护的线程池,当有完成事件时,会激活一个线程。
2、 Linux下的IO模型
A、阻塞IO
B、非阻塞IO
C、IO多路复用(选择)
D、信号驱动
用于异步socket,首先设定信号处理函数,然后使用fcntl函数设定socket的拥有者,像windows下使用WSAAsncSelect设定socket的窗口一样。使用这种模型,当内核操作可以被操作的时候通知我们的应用程序
E、异步IO
当内核在所有操作完成后才会通知应用程序
四、socket的一些使用上的优化
A、缓冲区的优化,可以考虑让应用程序使用比较小的缓冲区,但同时使用多个WSARecv
B、使用socket选项SO_SNDBUF和SO_RCVBUF设置socket缓冲区大小,如果设为0,操作体系统会使用应用程序的缓冲区,这样避免了从系统缓冲区向用户区复制的开销
五、注意这些IO模型有些不光是针对socket的,其他的IO操作也可以使用,最常用使用的是WriteFile,ReadFile等函数。
其它查考网址:
http://blog.163.com/tianle_han/blog/static/6617826200821522743948/
http://blog.csdn.net/yibulianhua/article/details/5374317
⑺ Linux系统I/O模型及select、poll、epoll原理和应用
理解Linux的IO模型之前,首先要了解一些基本概念,才能理解这些IO模型设计的依据
操作系统使用虚拟内存来映射物理内存,对于32位的操作系统来说,虚拟地址空间为4G(2^32)。操作系统的核心是内核,为了保护用户进程不能直接操作内核,保证内核安全,操作系统将虚拟地址空间划分为内核空间和用户空间。内核可以访问全部的地址空间,拥有访问底层硬件设备的权限,普通的应用程序需要访问硬件设备必须通过 系统调用 来实现。
对于Linux系统来说,将虚拟内存的最高1G字节的空间作为内核空间仅供内核使用,低3G字节的空间供用户进程使用,称为用户空间。
又被称为标准I/O,大多数文件系统的默认I/O都是缓存I/O。在Linux系统的缓存I/O机制中,操作系统会将I/O的数据缓存在页缓存(内存)中,也就是数据先被拷贝到内核的缓冲区(内核地址空间),然后才会从内核缓冲区拷贝到应用程序的缓冲区(用户地址空间)。
这种方式很明显的缺点就是数据传输过程中需要再应用程序地址空间和内核空间进行多次数据拷贝操作,这些操作带来的CPU以及内存的开销是非常大的。
由于Linux系统采用的缓存I/O模式,对于一次I/O访问,以读操作举例,数据先会被拷贝到内核缓冲区,然后才会从内核缓冲区拷贝到应用程序的缓存区,当一个read系统调用发生的时候,会经历两个阶段:
正是因为这两个状态,Linux系统才产生了多种不同的网络I/O模式的方案
Linux系统默认情况下所有socke都是blocking的,一个读操作流程如下:
以UDP socket为例,当用户进程调用了recvfrom系统调用,如果数据还没准备好,应用进程被阻塞,内核直到数据到来且将数据从内核缓冲区拷贝到了应用进程缓冲区,然后向用户进程返回结果,用户进程才解除block状态,重新运行起来。
阻塞模行下只是阻塞了当前的应用进程,其他进程还可以执行,不消耗CPU时间,CPU的利用率较高。
Linux可以设置socket为非阻塞的,非阻塞模式下执行一个读操作流程如下:
当用户进程发出recvfrom系统调用时,如果kernel中的数据还没准备好,recvfrom会立即返回一个error结果,不会阻塞用户进程,用户进程收到error时知道数据还没准备好,过一会再调用recvfrom,直到kernel中的数据准备好了,内核就立即将数据拷贝到用户内存然后返回ok,这个过程需要用户进程去轮询内核数据是否准备好。
非阻塞模型下由于要处理更多的系统调用,因此CPU利用率比较低。
应用进程使用sigaction系统调用,内核立即返回,等到kernel数据准备好时会给用户进程发送一个信号,告诉用户进程可以进行IO操作了,然后用户进程再调用IO系统调用如recvfrom,将数据从内核缓冲区拷贝到应用进程。流程如下:
相比于轮询的方式,不需要多次系统调用轮询,信号驱动IO的CPU利用率更高。
异步IO模型与其他模型最大的区别是,异步IO在系统调用返回的时候所有操作都已经完成,应用进程既不需要等待数据准备,也不需要在数据到来后等待数据从内核缓冲区拷贝到用户缓冲区,流程如下:
在数据拷贝完成后,kernel会给用户进程发送一个信号告诉其read操作完成了。
是用select、poll等待数据,可以等待多个socket中的任一个变为可读,这一过程会被阻塞,当某个套接字数据到来时返回,之后再用recvfrom系统调用把数据从内核缓存区复制到用户进程,流程如下:
流程类似阻塞IO,甚至比阻塞IO更差,多使用了一个系统调用,但是IO多路复用最大的特点是让单个进程能同时处理多个IO事件的能力,又被称为事件驱动IO,相比于多线程模型,IO复用模型不需要线程的创建、切换、销毁,系统开销更小,适合高并发的场景。
select是IO多路复用模型的一种实现,当select函数返回后可以通过轮询fdset来找到就绪的socket。
优点是几乎所有平台都支持,缺点在于能够监听的fd数量有限,Linux系统上一般为1024,是写死在宏定义中的,要修改需要重新编译内核。而且每次都要把所有的fd在用户空间和内核空间拷贝,这个操作是比较耗时的。
poll和select基本相同,不同的是poll没有最大fd数量限制(实际也会受到物理资源的限制,因为系统的fd数量是有限的),而且提供了更多的时间类型。
总结:select和poll都需要在返回后通过轮询的方式检查就绪的socket,事实上同时连的大量socket在一个时刻只有很少的处于就绪状态,因此随着监视的描述符数量的变多,其性能也会逐渐下降。
epoll是select和poll的改进版本,更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的只需一次。
epoll_create()用来创建一个epoll句柄。
epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个就绪链表中管理。
epoll_wait() 可以从就绪链表中得到事件完成的描述符,因此进程不需要通过轮询来获得事件完成的描述符。
当epoll_wait检测到描述符IO事件发生并且通知给应用程序时,应用程序可以不立即处理该事件,下次调用epoll_wait还会再次通知该事件,支持block和nonblocking socket。
当epoll_wait检测到描述符IO事件发生并且通知给应用程序时,应用程序需要立即处理该事件,如果不立即处理,下次调用epoll_wait不会再次通知该事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用nonblocking socket,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
【segmentfault】 Linux IO模式及 select、poll、epoll详解
【GitHub】 CyC2018/CS-Notes
⑻ Linux中select poll和epoll的区别
select、poll、epoll都是IO多路复用的机制,但是他们的机制有很大的区别
1、select
select机制刚开始的时候,需要把fd_set从用户空间拷贝到内核空间,并且检测的fd数是有限制的,由FD_SETSIZE设置,一般是1024。
检测的时候,根据timeout,遍历fd_set表,把活跃的fd(可读写或者错误),拷贝到用户空间,
再在用户空间依次处理相关的fd。
这个机制是linux内核很早的版本,epool是根据select,pool基础上优化的,缺点比较多。
缺点:
1)每次调用select的时候需要把fd_set从用户空间拷贝到内存空间,比较耗性能。
2)wait时,需要遍历所有的fd,消耗比较大。
3)select支持的文件数大小了,默认只有1024,如果需要增大,得修改宏FD_SETSIZE值,并编译内核(麻烦,并且fd_set中的文件数多的话,每次遍历的成本就很大)。
2. pool
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
3. epool
epool是select和poll的改进版本,
* 先是使用int epoll_create(int size)在内存中创建一个指定size大小的事件空间,
* 再使用int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);事件注册函数,注册新的fd到epfd的epool对象空间中,并指明event(可读写啊等等),注意:在注册新事件fd的过程中,也再内核中断处理程序里注册fd对应的回调函数callback,告诉内核,一旦这个fd中断了,就把它放到ready队列里面去。
* 再使用int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);在epool对象对应的ready队列里取就绪的fd,并使用内存映射mmap拷贝到用户空间。
* 再在用户空间依次处理相关的fd。
优点:
1)支持一个进程打开大数目的socket描述符
select 一个进程打开FD是有限制的,由FD_SETSIZE设置,默认值是1024。epool可以打开的FD数可以很大,一般1GB的内存有10万多的FD数,具体数目可以cat /proc/sys/fs/file-max查看。
2) IO效率不随FD数目增加而线性下降
3) 使用mmap加速内核与用户空间的消息传递