日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

高并發服務器開發與配置

 

一、4大具有代表性的并發模型及其優缺點

4大具有代表性的并發模型:Apache模型(Process Per Connection,簡稱PPC),TPC(Thread PerConnection)模型,select模型和poll模型、Epoll模型。

Apache(PPC)模型和TPC模型是最容易理解的,Apache模型在并發上是通過多進程實現的,而TPC模型是通過多線程實現的,但是這2種方式在大量進程/線程切換時會造成大量的開銷。

select模型是通過一種輪詢機制來實現的。需要注意select模型有3大不足:

a.Socket文件數量限制:該模式可操作的Socket數由FD_SETSIZE決定,內核默認32*32=1024.

b.操作限制:通過遍歷FD_SETSIZE(1024)個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍,這樣效率就會隨文件數量的增多呈現線性下降,把FD_SETSIZE改大的后果就是遍歷需要更久的時間。管理文件數量和管理效率成反比。

c.內存復制限制:內核/用戶空間的信息交換是通過內存拷貝來完成的,這樣在高并發情況下就會存在大量的數據拷貝,浪費時間。

poll模型與select類似,也是通過輪詢來實現,但它與select模型的區別在于Socket數量沒有限制,所以poll模型有2大不足:操作限制和內存復制限制。

Epoll模型改進了poll和sellect模型。Epoll沒有文件數量限制,上限是當前用戶單個進程最大能打開的文件數;使用事件驅動,不使用輪詢,而使用基于內核提供的反射模式。有“活躍Socket”時,內核訪問該Socket的callback,直接返回產生事件的文件句柄;內核/用戶空間信息交換通過共享內存mmap實現,避免了數據復制。

目前市場用得比較多的就是Apache、Nginx、Lighttpd. Apache的占有率是最高是毋庸置疑的,但它主要是采用select模式開發。當前主流的異步web服務器Lighttpd和Nginx都是基于Epoll的。它們具有非常好的架構,可以運行在簡單的web集群中。但在數據結構、內存管理都多個細節方面處理nginx考慮更加完善。nginx從event、跨平臺、基礎數據結構都很多細節方面進行了考慮和優化。nginx必定是未來的apache,未來的主流。

二、主機環境對高并發應用程序的天然限制

高并發的應用程序至少需要考慮3大限制條件:用戶進程的默認內存空間為4G,線程棧默認為8M,用戶進程最大能管理的文件描述符默認為1024個,網卡對客戶端端口號數量的限制。

linux下高并發socket服務器端和客戶端最大連接數所受的限制問題(修改軟限制和硬限制)

1、配置用戶進程可打開的最多文件數量的限制

在Linux平臺上,無論編寫客戶端程序還是服務端程序,在進行高并發TCP連接處理時,最高的并發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是因為系統為每個TCP連接都要創建一個socket句柄,每個socket句柄同時也是一個文件句柄)。可使用ulimit命令查看系統允許當前用戶進程打開的文件數限制:[speng@as4 ~]$ ulimit -n1024 #系統默認對某一個用戶打開文件數的用戶軟限制是1024,用戶硬限制是4096個

這表示當前用戶的每個進程最多允許同時打開1024個文件,這1024個文件中還得除去每個進程必然打開的標準輸入,標準輸出,標準錯誤,服務器監聽 socket,進程間通訊的unix域socket等文件,那么剩下的可用于客戶端socket連接的文件數就只有大概1024-10=1014個左右。也就是說缺省情況下,基于Linux的通訊程序最多允許同時1014個TCP并發連接。

如果想支持更高數量的TCP并發連接的通訊處理程序,就必須修改Linux對當前用戶的進程同時打開的文件數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在當前系統能夠承受的范圍內進一步限制用戶同時能打開的文件數;硬限制則是根據系統硬件資源狀況(主要是系統內存)計算出來的系統最多可同時打開的文件數量。通常軟限制小于或等于硬限制。修改上述限制的最簡單的辦法就是使用ulimit命令:[speng@as4 ~]$ ulimit -n 100 #(只能設置比當前soft限制更小的數)上述命令中,指定要設置的單一進程允許打開的最大文件數。如果系統回顯類似于"Operation notpermitted"之類的話,說明上述限制修改失敗,實際上是因為在此指定的數值超過了Linux系統對該用戶打開文件數的軟限制或硬限制。因此,就需要修改Linux系統對用戶的關于打開文件數的軟限制和硬限制。如果需要設置比當前軟限制和硬限制更大的數,只能修改配置文件,步驟如下:

第一步,修改/etc/security/limits.conf文件,在文件中添加如下行:speng soft nofile 10240speng hard nofile 10240其中speng指定了要修改的用戶的用戶名,可用'*'號表示修改所有用戶的限制;soft或hard指定要修改軟限制還是硬限制;10240則指定了想要修改的新的限制值,即最大打開文件數(請注意軟限制值要小于或等于硬限制)。修改完后保存文件。

第二步,修改/etc/pam.d/login文件,在文件中添加如下行:session required 
/lib/security/pam_limits.so 這是告訴Linux在用戶完成系統登錄后,應該調用pam_limits.so模塊來設置系統對該用戶可使用的各種資源數量的最大限制(包括用戶可打開的最大文件數限制),而pam_limits.so模塊就會從/etc/security/limits.conf文件中讀取配置來設置這些限制值。修改完后保存此文件。

第三步,查看Linux系統級的最大打開文件數限制-硬限制,使用如下命令:[speng@as4 ~]$ cat 
/proc/sys/fs/file-max12158這表明這臺Linux系統最多允許同時打開(即包含所有用戶打開文件數總和)12158個文件,是Linux系統級硬限制,所有用戶級的打開文件數限制都不應超過這個數值。通常這個系統級硬限制是Linux系統在啟動時根據系統硬件資源狀況計算出來的最佳的最大同時打開文件數限制,如果沒有特殊需要,不應該修改此限制,除非想為用戶級打開文件數限制設置超過此限制的值。修改此硬限制的方法是修改/etc/rc.local腳本,在腳本中添加如下行:echo 22158 > /proc/sys/fs/file-max這是讓Linux在啟動完成后強行將系統級打開文件數硬限制設置為22158.修改完后保存此文件。

第四步,完成上述步驟后重啟系統,一般情況下就可以將Linux系統對指定用戶的單一進程允許同時打開的最大文件數限制設為指定的數值。如果重啟后用 ulimit-n命令查看用戶可打開文件數限制仍然低于上述步驟中設置的最大值,這可能是因為在用戶登錄腳本/etc/profile中使用ulimit -n命令已經將用戶可同時打開的文件數做了限制。由于通過ulimit-n修改系統對用戶可同時打開文件的最大數限制時,新修改的值只能小于或等于上次 ulimit-n設置的值,因此想用此命令增大這個限制值是不可能的。所以,如果有上述問題存在,就只能去打開/etc/profile腳本文件,在文件中查找是否使用了ulimit-n限制了用戶可同時打開的最大文件數量,如果找到,則刪除這行命令,或者將其設置的值改為合適的值,然后保存文件,用戶退出并重新登錄系統即可。 通過上述步驟,就為支持高并發TCP連接處理的通訊處理程序解除關于打開文件數量方面的系統限制。

2、修改網絡內核對TCP連接的有關限制  

在Linux上編寫支持高并發TCP連接的客戶端通訊處理程序時,有時會發現盡管已經解除了系統對用戶同時打開文件數的限制,但仍會出現并發TCP連接數增加到一定數量時,再也無法成功建立新的TCP連接的現象。出現這種現在的原因有多種。

第一種原因可能是因為Linux網絡內核對本地端口號范圍有限制(客戶端能使用的端口號限制)。此時,進一步分析為什么無法建立TCP連接,會發現問題出在connect()調用返回失敗,查看系統錯誤提示消息是"Can't assign requestedaddress".同時,如果在此時用tcpdump工具監視網絡,會發現根本沒有TCP連接時客戶端發SYN包的網絡流量。這些情況說明問題在于本地Linux系統內核中有限制。其實,問題的根本原因在于Linux內核的TCP/IP協議實現模塊對系統中所有的客戶端TCP連接對應的本地端口號的范圍進行了限制(例如,內核限制本地端口號的范圍為1024~32768之間)。當系統中某一時刻同時存在太多的TCP客戶端連接時,由于每個TCP客戶端連接都要占用一個唯一的本地端口號(此端口號在系統的本地端口號范圍限制中),如果現有的TCP客戶端連接已將所有的本地端口號占滿(端口耗盡),因此系統會在這種情況下在connect()調用中返回失敗,并將錯誤提示消息設為"Can't assignrequested address".有關這些控制邏輯可以查看Linux內核源代碼,以linux2.6內核為例,可以查看tcp_ipv4.c文件中如下函數:static int tcp_v4_hash_connect(struct sock *sk)請注意上述函數中對變量sysctl_local_port_range的訪問控制。變量sysctl_local_port_range的初始化則是在tcp.c文件中的如下函數中設置:void __init tcp_init(void)內核編譯時默認設置的本地端口號范圍可能太小,因此需要修改此本地端口范圍限制,方法為

第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:
net.ipv4.ip_local_port_range = 1024 65000這表明將系統對本地端口范圍限制設置為1024~65000之間。請注意,本地端口范圍的最小值必須大于或等于1024;而端口范圍的最大值則必須<=65535.修改完后保存此文件。

第二步,執行sysctl命令:[speng@as4 ~]$ sysctl -p如果系統沒有錯誤提示,就表明新的本地端口范圍設置成功。如果按上述端口范圍進行設置,則理論上單獨一個進程最多可以同時建立60000多個TCP客戶端連接。

第二種無法建立TCP連接的原因可能是因為Linux網絡內核的IP_TABLE防火墻對最大跟蹤的TCP連接數有限制。此時程序會表現為在 connect()調用中阻塞,如同死機,如果用tcpdump工具監視網絡,也會發現根本沒有TCP連接時客戶端發SYN包的網絡流量。由于 IP_TABLE防火墻在內核中會對每個TCP連接的狀態進行跟蹤,跟蹤信息將會放在位于內核內存中的conntrackdatabase中,這個數據庫的大小有限,當系統中存在過多的TCP連接時,數據庫容量不足,IP_TABLE無法為新的TCP連接建立跟蹤信息,于是表現為在connect()調用中阻塞。此時就必須修改內核對最大跟蹤的TCP連接數的限制,方法同修改內核對本地端口號范圍的限制是類似的:

第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:net.ipv4.ip_conntrack_max = 10240這表明將系統對最大跟蹤的TCP連接數限制設置為10240.請注意,此限制值要盡量小,以節省對內核內存的占用。

第二步,執行sysctl命令:[speng@as4 ~]$ sysctl -p如果系統沒有錯誤提示,就表明系統對新的最大跟蹤的TCP連接數限制修改成功。如果按上述參數進行設置,則理論上單獨一個進程最多可以同時建立10000多個TCP客戶端連接。

三、高并發采用的IO訪問方案

使用支持高并發網絡I/O的編程技術在Linux上編寫高并發TCP連接應用程序時,必須使用合適的網絡I/O技術和I/O事件分派機制。可用的I/O技術有同步I/O(當前I/O訪問完成再進行下一次訪問),非阻塞式同步I/O(也稱反應式I/O,select,poll,epoll實現),以及異步I/O.在高TCP并發的情形下,如果使用同步I/O,這會嚴重阻塞程序的運轉,除非為每個TCP連接的I/O創建一個線程。但是,過多的線程又會因系統對線程的調度造成巨大開銷。因此,在高TCP并發的情形下使用同步 I/O是不可取的.

這時可以考慮使用非阻塞式同步I/O或異步I/O.非阻塞式同步I/O的技術包括使用select(),poll(),epoll等機制。異步I/O的技術就是使用AIO. 從I/O事件分派機制來看,使用select()是不合適的,因為它所支持的并發連接數有限(通常在1024個以內)。如果考慮性能,poll()也是不合適的,盡管它可以支持的較高的TCP并發數,但是由于其采用"輪詢"機制,當并發數較高時,其運行效率相當低,并可能存在I/O事件分派不均,導致部分TCP連接上的I/O出現"饑餓"現象。而如果使用epoll或AIO,則沒有上述問題(早期Linux內核的AIO技術實現是通過在內核中為每個 I/O請求創建一個線程來實現的,這種實現機制在高并發TCP連接的情形下使用其實也有嚴重的性能問題。但在最新的Linux內核中,AIO的實現已經得到改進)。

綜上所述,在開發支持高并發TCP連接的Linux應用程序時,應盡量使用epoll或AIO技術來實現并發的TCP連接上的I/O控制,這將為提升程序對高并發TCP連接的支持提供有效的I/O保證。

epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

1.為什么是epoll,而不是select?

(1)epoll支持在一個用戶進程內打開最大系統限制的文件描述符select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是1024。對于那些需要支持的上萬連接數目的IM服務器來說顯然太少了。這時候一般有2種選擇:一是可以選擇修改這個宏然后重新編譯內核,不過資料也同時指出這樣會帶來網絡效率的下使用epoll進行高性能網絡編程 降,二是可以選擇多進程的解決方案(傳統的Apache方案),不過雖然linux上面創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max查看,一般來說這個數目和系統內存關系很大。

(2)epoll的IO讀取效率不隨FD數目增加而線性下降傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由于網絡延時,任一時間只有部分的socket是“活躍”的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對“活躍”的socket進行操作---這是因為在內核實現中epoll是根據每個fd上面的callback函數實現的。那么,只有“活躍”的socket才會主動的去調用 callback函數,其他idle狀態socket則不會,在這點上,epoll實現了一個“偽”AIO,因為這時候推動力在os內核。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環境,epoll并不比select/poll有什么效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。傳統的select以及poll的效率會因為在線人數的線形遞增而導致呈二次乃至三次方的下降,這些直接導致了網絡服務器可以支持的人數有了個比較明顯的限制。select/poll線性掃描文件描述符,epoll事件觸發

(3)epoll使用mmap加速內核與用戶空間的消息傳遞(文件描述符傳遞)這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核與用戶空間mmap同一塊內存實現的。

(4)epoll有2種工作方式epoll有2種工作方式:LT和ET。LT(level triggered電平觸發)是缺省的工作方式,并且同時支持block和no-block socket(阻塞和非阻塞).在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的(如果你不作任何操作,會通知多次),所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。ET (edge-triggered邊緣觸發)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文件描述符發送更多的就緒通知(無論如何,只通知一次),直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。ET和LT的區別就在這里體現,LT事件不會丟棄,而是只要讀buffer里面有數據可以讓用戶讀或寫buffer為空,則不斷的通知你。而ET則只在事件發生之時通知。可以簡單理解為LT是水平觸發,而ET則為邊緣觸發。LT模式只要有事件未處理就會觸發,而ET則只在高低電平變換時(即狀態從1到0或者0到1)觸發。綜上所述,epoll適合管理百萬級數量的文件描述符。

2.epoll相關的系統調用總共不過3個API:epoll_create, epoll_ctl, epoll_wait。

(1)int epoll_create(int maxfds) 創建一個epoll的句柄,返回新的epoll設備句柄。在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。int epoll_create1(int flags)是int epoll_create(int maxfds) 的變體,已將maxfds廢棄不用。flags只有2種取值:flags=0表示epoll_create(int maxfds) 一樣,文件數上限應該是系統用戶進程的軟上限;flags=EPOLL_CLOEXEC 表示在新打開的文件描述符里設置 close-on-exec (FD_CLOEXEC) 標志。相當于先調用pfd=epoll_create,在使用fcntl設置pfd的FD_CLOEXEC選項。意思是在使用execl產生的子進程里面,將此描述符關閉,不能再使用它,但是在使用fork調用的子進程中,此描述符并不關閉,仍可使用。

(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epoll的事件注冊函數,返回0表示設置成功。第一個參數是epoll_create()的返回值。第二個參數表示動作,用三個宏來表示:EPOLL_CTL_ADD:注冊新的fd到epfd中;EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;EPOLL_CTL_DEL:從epfd中刪除一個fd;第三個參數是需要監聽的fd。第四個參數是告訴內核需要監聽什么事,struct epoll_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 */};epoll能監控的文件描述符的7個events可以是以下7個宏的集合:EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);EPOLLOUT:表示對應的文件描述符可以寫;EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);EPOLLERR:表示對應的文件描述符發生錯誤;EPOLLHUP:表示對應的文件描述符被掛斷;EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于電平觸發(Level Triggered)來說的。如果不設置則為電平觸發。EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);收集在epoll監控的事件中已經發生的事件,返回發生的事件個數。參數events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中(events不可以是空指針,內核只負責把數據復制到這個events數組中,不會去幫助我們在用戶態中分配內存)。maxevents告之內核這個events數組有多大,這個 maxevents的值不能大于創建epoll_create()時的maxfds。參數timeout是epoll_wait超時時間毫秒數,0會立即返回非阻塞,-1永久阻塞。如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時。 Linux-2.6.19又引入了可以屏蔽指定信號的epoll_wait: epoll_pwait

3.epoll的使用過程

(1)首先通過kdpfd=epoll_create(int maxfds)來創建一個epoll的句柄,其中maxfds為你epoll所支持的最大句柄數。這個函數會返回一個新的epoll句柄,之后的所有操作將通過這個句柄來進行操作。在用完之后,記得用close()來關閉這個創建出來的epoll句柄。

(2)之后在你的網絡主循環里面,每一幀的調用epoll_wait(int epfd, epoll_event *events, int max events, int timeout)來查詢所有的網絡接口,看哪一個可以讀,哪一個可以寫了。基本的語法為: nfds=epoll_wait(kdpfd,events,maxevents,-1); 其中kdpfd為用epoll_create創建之后的句柄,events是一個epoll_event*的指針,當epoll_wait這個函數操作成功之后,epoll_events里面將儲存所有的讀寫事件。maxevents是最大事件數量。最后一個timeout是epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件發生,為任意正整數的時候表示等這么長的時間,如果一直沒有事件,則返回。一般如果網絡主循環是單獨的線程的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0來保證主循環的效率。

epoll_wait范圍之后應該是一個循環,遍歷所有的事件:

while(true)
{  
nfds = epoll_wait(epfd,events,20,500); 
    for(n=0;n<nfds;++n) 
 { 
  if(events[n].data.fd==listener)  
 { //如果是主socket的事件的話,則表示   
  //有新連接進入了,進行新連接的處理。
 client=accept(listener,(structsockaddr*)&local,&addrlen);   
  if(client<0)   //在此最好將client設置為非阻塞 
   { perror("accept"); continue; }    
 setnonblocking(client);//將新連接置于非阻塞模式  
ev.events=EPOLLIN|EPOLLET; //并且將新連接也加入EPOLL的監聽隊列。注意: 并沒有設置對寫socket的監聽    
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)    { //如果是已經連接的用戶,并且收到數據, 那么進行讀入      
int sockfd_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實例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#define MAXEVENTS 64
//函數:
//功能:創建和綁定一個TCP socket
//參數:端口
//返回值:創建的socket
static int create_and_bind (char *port)
{  
struct addrinfo hints;  
struct addrinfo *result, *rp;  
int s, sfd;
  memset (&hints, 0, sizeof (struct addrinfo));  
hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
hints.ai_flags = AI_PASSIVE;     /* All interfaces */
  s = getaddrinfo (NULL, port, &hints, &result);//getaddrinfo解決了把主機名和服務名轉換成套接口地址結構的問題。
  if (s != 0)    
{      
fprintf (stderr, "getaddrinfo: %sn", gai_strerror (s));     
 return -1;    
}
  for (rp = result; rp != NULL; rp = rp->ai_next)    
{      
sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);     
 if (sfd == -1)       
 continue;
      s = bind (sfd, rp->ai_addr, rp->ai_addrlen);     
 if (s == 0)        
{         
 /* We managed to bind successfully! */         
 break;       
 }
      close (sfd);   
 }
  if (rp == NULL)    
{      
fprintf (stderr, "Could not bindn");     
 return -1;   
 }
  freeaddrinfo (result);
  return sfd;}
//函數//功能
:設置socket為非阻塞的
static in
tmake_socket_non_blocking (int sfd)
{ 
 int flags, s;
  //得到文件狀態標志  
flags = fcntl (sfd, F_GETFL, 0);  
if (flags == -1)    
{      
perror ("fcntl");      
return -1;    
}
  //設置文件狀態標志  
flags |= O_NONBLOCK;  
s = fcntl (sfd, F_SETFL, flags);  
if (s == -1)   
 {      
perror ("fcntl");     
 return -1;   
 }
  return 0;
}
//端口由參數argv[1]指定
int main (int argc, char *argv[])
{  
int sfd, s; 
 int efd;  
struct epoll_event event; 
 struct epoll_event *events;
  if (argc != 2)    
{      
fprintf (stderr, "Usage: %s [port]n", argv[0]);      
exit (EXIT_FAILURE);   
 }
  sfd = create_and_bind (argv[1]);  
if (sfd == -1)    abort ();
  s = make_socket_non_blocking (sfd); 
 if (s == -1)    
abort ();
  s = listen (sfd, SOMAXCONN);  
if (s == -1)   
 {     
 perror ("listen");      
abort ();    
}
  //除了參數size被忽略外,此函數和epoll_create完全相同  
efd = epoll_create1 (0);  
if (efd == -1)    
{      
perror ("epoll_create");      
abort ();   
 }
  event.data.fd = sfd; 
 event.events = EPOLLIN | EPOLLET;//讀入,邊緣觸發方式  
s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
if (s == -1)    
{      
perror ("epoll_ctl");      
abort ();    
}
  /* Buffer where events are returned */ 
 events = calloc (MAXEVENTS, sizeof event);
  /* The event loop */  
while (1)    
{      
int n, i;
      n = epoll_wait (efd, events, MAXEVENTS, -1);      
for (i = 0; i < n; i++)        
{          
if ((events[i].events & EPOLLERR) ||             
 (events[i].events & EPOLLHUP) ||             
 (!(events[i].events & EPOLLIN)))           
 {              
/* An error has occured on this fd, or the socket is not                 
ready for reading (why were we notified then?) */             
 fprintf (stderr, "epoll errorn");              
close (events[i].data.fd);              
continue;            
}
          else if (sfd == events[i].data.fd)           
 {             
 /* We have a notification on the listening socket, which                 
means one or more incoming connections. */              
while (1)                
{                 
 struct sockaddr in_addr;                  
socklen_t in_len;                  
int infd;                  
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
                  in_len = sizeof in_addr;                 
 infd = accept (sfd, &in_addr, &in_len);                  
if (infd == -1)                   
 {                     
 if ((errno == EAGAIN) ||                         
 (errno == EWOULDBLOCK))                       
 {                         
 /* We have processed all incoming                            
 connections. */                          
break;                        
}                      
else                       
 {                          
perror ("accept");                         
break;                        
}                   
 }
                                  //將地址轉化為主機名或者服務名                  
s = getnameinfo (&in_addr, in_len,                                   
hbuf, sizeof hbuf,                                   
sbuf, sizeof sbuf,                                  
 NI_NUMERICHOST | NI_NUMERICSERV);//flag參數:以數字名返回                                  
//主機地址和服務地址
                  if (s == 0)                    
{                      
printf("Accepted connection on descriptor %d "                            
 "(host=%s, port=%s)n", infd, hbuf, sbuf);                    
}
                  /* Make the incoming socket non-blocking and add it to the                     
list of fds to monitor. */                  
s = make_socket_non_blocking (infd);                  
if (s == -1)                    
abort ();
                  event.data.fd = infd;                  
event.events = EPOLLIN | EPOLLET;                  
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);                 
 if (s == -1)                    
{                      
perror ("epoll_ctl");                      
abort ();                    
}                
}              
continue;            
}          
else            
{              
/* We have data on the fd waiting to be read. Read and                 
display it. We must read whatever data is available                
 completely, as we are running in edge-triggered mode                 
and won't get a notification again for the same                 
data. */             
 int done = 0;
 while (1)               
 {                  
ssize_t count;                  
char buf[512];
count = read (events[i].data.fd, buf, sizeof(buf));                  
if (count == -1)                    
{                     
 /* If errno == EAGAIN, that means we have read all                         
data. So go back to the main loop. */                     
 if (errno != EAGAIN)                        
{                         
 perror ("read");                          
done = 1;                       
 }                      
break;                    
}                  
else if (count == 0)                   
 {                     
 /* End of file. The remote has closed the                         
connection. */                      
done = 1;                      
break;                    
}
                  /* Write the buffer to standard output */                  
s = write (1, buf, count);                  
if (s == -1)                    
{                      
perror ("write");                      
abort ();                    
}               
 }
              if (done)               
 {                  
printf ("Closed connection on descriptor %dn",                          
events[i].data.fd);
                  /* Closing the descriptor will make epoll remove it                     
from the set of descriptors which are monitored. */                  
close (events[i].data.fd);                
}           
 }        
}    
}
  free (events);
  close (sfd);
  return EXIT_SUCCESS;}

運行方式:

在一個終端運行此程序:epoll.out PORT

另一個終端:telnet 127.0.0.1 PORT

分享到:
標簽:并發 服務器
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定