1. 程式人生 > >高效能資料傳輸系統的框架設計

高效能資料傳輸系統的框架設計

<div class="article-header-box">
	<div class="article-header">
		<div class="article-title-box">
			<span class="article-type type-2 float-left">轉</span>				<h1 class="title-article">高併發的socket的高效能設計</h1>
		</div>
		<div class="article-info-box">
			<div class="article-bar-top" style="height: 24px;">
																			<span class="time">2015年03月26日 19:46:32</span>
				<a class="follow-nickName" href="https://me.csdn.net/tlaff" target="_blank">方長存</a>
					<span class="read-count">閱讀數:16466</span>
					
													<span class="tags-box artic-tag-box">
							<span class="label">標籤:</span>
															<a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;linux&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=linux&amp;t=blog" target="_blank">linux																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;socket&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=socket&amp;t=blog" target="_blank">socket																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;高效能&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=高效能&amp;t=blog" target="_blank">高效能																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;併發&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=併發&amp;t=blog" target="_blank">併發																</a>
						<span class="article_info_click">更多</span></span>
																				<div class="tags-box space">
							<span class="label">個人分類:</span>
															<a class="tag-link" href="https://blog.csdn.net/tlaff/article/category/467184" target="_blank">C																</a>
						</div>
																							</div>
			<div class="operating">
													</div>
		</div>
	</div>
</div>
<article>
	<div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post">
							            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-e2445db1a8.css">
					<div class="htmledit_views">

1 引言

   隨著網際網路和物聯網的高速發展,使用網路的人數和電子裝置的數量急劇增長,其也對網際網路後臺服務程式提出了更高的效能和併發要求。本文的主要目的是闡述在單機上如何進行高併發、高效能訊息傳輸系統的框架設計,以及該系統的常用技術,但不對其技術細節進行討論。如您有更好的設計方案和思路,望共分享之![注:此篇用select來講解,雖在大併發的情況下,epoll擁有更高的效率,但整體設計思路是一致的]

首先來看看課本和學習資料上關於處理併發網路程式設計的三種常用方案,以及對應的大體思路和優缺點:

1) IO多路複用模型

->思路:單程序(非多執行緒)呼叫select()函式來處理多個連線請求。

     ->優點:單程序(非多執行緒)可支援同時處理多個網路連線請求。

     ->缺點:最大併發為1024個,當併發數較大時,其處理效能很低。

2) 多程序模型

     ->思路:當連線請求過來時,主程序fork產生一個子程序,讓子程序負責與客戶端連線進行資料通訊,當客戶端主動關閉連線後,子程序結束執行。

     ->優點:模式簡單,易於理解;連線請求很小時,效率較高。

     ->缺點:當連線請求過多時,系統資源很快被耗盡。比如:當連線請求達到10k時,難道要啟動10k個程序嗎?

   3) 多執行緒模型

     ->思路:首先啟動多個工作執行緒,而主執行緒負責接收客戶端連線請求,工作執行緒負責與客戶端通訊;當連線請求過來時,ACCEPT執行緒將sckid放入一個數組中,工作執行緒中的空閒執行緒從陣列中取走一個sckid,對應的工作執行緒再與客戶端連線進行資料通訊,當客戶端主動關閉連線後,此工作執行緒又去從指定陣列中取sckid,依次重複執行。

     ->優點:擁有方案2)的優點,且能夠解決方案2)的缺點。

     ->缺點:不能支援併發量大的請求和量稍大的長連線請求。

通過對以上三種方案的分析,以上方案均不能滿足高併發、高效能的伺服器的處理要求。針對以上設計方案問題的存在,該如何設計才能做到高併發、高效能的處理要求呢?

2 設計方案

2.1 大體框架

   為提高併發量和處理效能,在此採用2層的設計架構。第一層由接收執行緒組成,負責接收客戶端資料;第二層由工作執行緒組成,負責對接收的資料進行相應處理。為了減少資料的複製和IO操作,將接收到的客戶端資料使用佇列進行儲存;工作執行緒收到處理指令後,從指令中提取相應的引數,便可知道到哪個執行緒的佇列中獲取資料。因此,系統的大體架構如下所示:

1) 框架-1

圖1 大體框架-01

[注:接收執行緒數:接收佇列數:工作執行緒數 = N:N:X]

優點:

   1)、有效避免接收執行緒之間出現鎖競爭的情況。

   每個接收執行緒對應一個接收佇列,每個接收執行緒將接收到的資料只放在自己對應的佇列中;

   2)、在資料量不是很大的情況下,此框架結構還是能夠滿足處理要求。

缺點:

   1)、在連線數量很少、而資料量很大時,將會造成鎖衝突嚴重,致使效能急劇下降。

   假如:當前系統中只有1個TCP連線,由Recv執行緒2負責接收該連線中的所有資料。Recv執行緒2每收到一條資料,就將隨機通知工作執行緒到該佇列上取資料。在某個時刻,該連線的客戶端發來大量資料,將造成所有工作執行緒同時到Recv佇列2中來取資料。此時將會出現嚴重的鎖衝突現象,效能急劇下降。

2) 框架-2

圖2 大體框架-02

[注:接收執行緒數:接收佇列數:工作執行緒數 = X:Y:Y]

優點:

   1)、有效避免工作執行緒之間出現鎖競爭的情況。

   每個工作執行緒對應一個接收佇列,每個接收執行緒將接收到的資料只放在自己對應的佇列中;

    2)、工作執行緒數 >= 2*接收執行緒數 時,能夠有效的減少接收執行緒之間的鎖競爭的情況

   在這種情況下,我想你可以得到你想要的處理效能!

缺點:

   1)、需要為更高的效能,付出更多的系統資源(主要:記憶體和CPU)。

2.2 如何提高併發量?

“併發量”是指系統可接受的TCP連線請求數。首先需要明確的是:"高併發"只是一個相對概念。如:有些系統1K併發就算是高併發,而有些系統100K併發也不能滿足要求。因此,在此只給出提高併發量的設計思路。

眾所周知,IO多路複用中1個select函式最多可管理FD_SETSIZE(該值一般為1024)個SOCKET套接字,而如果要求併發量達到100K時,顯然已大大超過了1個select的管理能力,那該如何解決?

   答案是:使用多個select可以有效的解決以上問題。100K約等於100 * 1024,故需大約100個select才能有效管理100k併發。那該如何呼叫100個select來管理100k的併發呢?

   因FD的管理在程序之間是獨立的,雖然子程序在建立之時,會繼承父程序的FD,但後續連線產生的FD卻無法讓子程序繼續繼承,因此,要實現對100k併發的有效管理,使用多執行緒實現高併發是理想的選擇。即:每個執行緒呼叫1個select,而每個select可以管理1024個併發。

   在理想情況下,啟動N個接收執行緒,系統便可處理N *1024的併發。如:啟動100個接收執行緒,單機便可處理100 * 1024 = 100k的網路併發。但需要注意的是:執行緒越多,消耗的資源越多,作業系統排程的開銷越大,如果排程開銷超過多執行緒帶來的效能提升,隨著執行緒的增加,將導致系統性能越低。(如果要求處理5k以上的請求,我將毫不猶豫的選擇"多執行緒+epoll"的方式)

2.3 如何提高處理效能?

1) Recv流程

   為了提高Recv執行緒接收來自客戶端的資料的效能,其處理過程需要使用到:IO多路複用技術,非阻塞IO技術、記憶體池技術、加鎖技術、事件觸發機制、負載均衡策略、UNINX-UDP技術、設計模式等,這需要研發人員對各技術有深刻的認識和理解。Recv執行緒的大體處理流程:

圖2 Recv執行緒處理流程

   為了減少資料的複製,可以在接收資料開始時,Recv執行緒就為將要接收的資料從接收佇列中分配一塊空間。當Recv執行緒接收到一條完整的客戶端資料後,則通過UNINX-UDP傳送訊息,告知某一Work執行緒到指定接收佇列中取走資料進行處理。Recv執行緒通知Work執行緒的過程需要採用負載均衡策略。

2) Work流程

   在無處理訊息到來之前,一直處在阻塞狀態,當有Recv執行緒的處理通知時,則接收訊息內容,對訊息進行分析,再根據訊息的內容到指定的接收佇列中取資料,再對資料進行相應的處理。其大體流程如下圖所示:

圖3 Work執行緒處理流程

2.3 鏈路分發

   說了這麼多,但一直未提到各Recv執行緒是如何分配和獲取客戶端通訊SOCKET的。眾所周知,當一個執行緒通過TCP方式繫結指定埠後,其他執行緒或程序想再次繫結該埠時,必將返回錯誤。而如果讓一個Listen套接字同時加入到多個Recv執行緒的select的可讀集合進行監聽,又會出現“驚群"現象:當有1個新的客戶端連線請求到來時,所有的Recv執行緒都會被驚醒 —— 這顯然是應該避免的。為了避免"驚群"的出現,通常有如下2種方案: 方案1) 建立鏈路分發模組    當有客戶端連線請求過來時,該執行緒呼叫accept接收來自客戶端的連線,再將新SOCKET-FD分發給某1個Recv執行緒,該Recv執行緒再將FD加入select的FD_SET中進行監聽,從而實現Recv執行緒與客戶端的通訊。 方案2) Lsn套接字流轉偵聽    當一個執行緒/程序搶佔到該Listen套接字後,該執行緒/程序將會開始接收來自客戶端的連線請求和監聽產生的套接字以及後續的資料接收等工作,當該執行緒接收的套接字超過一定量時,該執行緒將會主動放棄對Listen套接字的監聽,而讓其他執行緒/程序去搶佔Listen套接字。NGINX採用的就是此方案。    在此採用的是方案1)的解決辦法:Listen執行緒將接收的客戶端請求產生的通訊SOCKET均衡的分發給RECV執行緒[採用UNINX-UDP的方式傳送]。其大體框架如下:

圖4 鏈路分發

3 方案總結

   以上設計方案適合客戶端向服務端傳輸大量資料的場景,如果需要服務端反饋最終的處理結果,則需為Recv執行緒增加一個與之對應傳送佇列,在此不再贅述。總之,要做到高併發、高效能的網路通訊系統,往往需要以下技術做支撐,這需要研發人員對以下技術擁有深刻的理解和認識,當然這還遠遠不夠。

   1)IO多路複用技術 2)非阻塞IO技術 3)事件驅動機制 4)執行緒池技術 5)負載均衡策略 6)記憶體池技術 7)快取技術 8)鎖技術 9)設計模式 10)高效演算法和技巧的使用等等

以上轉載自:"祁峰"的CSDN部落格:http://blog.csdn.net/qifengzou/article/details/23912267

在linux 沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路複用的方法來實現併發服務程式。在大資料、高併發、叢集等一些名詞唱得火熱之年代,select和poll的用武之地越來越有限,風頭已經被epoll佔盡。

本文便來介紹epoll的實現機制,並附帶講解一下select和poll。通過對比其不同的實現機制,真正理解為何epoll能實現高併發。

select()和poll() IO多路複用模型

select的缺點:

  1. 單個程序能夠監視的檔案描述符的數量存在最大限制,通常是1024,當然可以更改數量,但由於select採用輪詢的方式掃描檔案描述符,檔案描述符數量越多,效能越差;(在linux核心標頭檔案中,有這樣的定義:#define __FD_SETSIZE    1024)
  2. 核心 / 使用者空間記憶體拷貝問題,select需要複製大量的控制代碼資料結構,產生巨大的開銷;
  3. select返回的是含有整個控制代碼的陣列,應用程式需要遍歷整個陣列才能發現哪些控制代碼發生了事件;
  4. select的觸發方式是水平觸發,應用程式如果沒有完成對一個已經就緒的檔案描述符進行IO操作,那麼之後每次select呼叫還是會將這些檔案描述符通知程序。

相比select模型,poll使用連結串列儲存檔案描述符,因此沒有了監視檔案數量的限制,但其他三個缺點依然存在。

拿select模型為例,假設我們的伺服器需要支援100萬的併發連線,則在__FD_SETSIZE 為1024的情況下,則我們至少需要開闢1k個程序才能實現100萬的併發連線。除了程序間上下文切換的時間消耗外,從核心/使用者空間大量的無腦記憶體拷貝、陣列輪詢等,是系統難以承受的。因此,基於select模型的伺服器程式,要達到10萬級別的併發訪問,是一個很難完成的任務。

因此,該epoll上場了。

epoll IO多路複用模型實現機制

由於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萬個連線的控制代碼資料,核心也不需要去遍歷全部的連線。

下面來看看Linux核心具體的epoll機制實現思路。

當某一程序呼叫epoll_create方法時,Linux核心會建立一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。eventpoll結構體如下所示:

  1. structeventpoll{
  2. ....
  3. /*紅黑樹的根節點,這顆樹中儲存著所有新增到epoll中的需要監控的事件*/
  4. structrb_root rbr;
  5. /*雙鏈表中則存放著將要通過epoll_wait返回給使用者的滿足條件的事件*/
  6. structlist_headrdlist;
  7. ....
  8. };
每一個epoll物件都有一個獨立的eventpoll結構體,用於存放通過epoll_ctl方法向epoll物件中新增進來的事件。這些事件都會掛載在紅黑樹中,如此,重複新增的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。

而所有新增到epoll中的事件都會與裝置(網絡卡)驅動程式建立回撥關係,也就是說,當相應的事件發生時會呼叫這個回撥方法。這個回撥方法在核心中叫ep_poll_callback,它會將發生的事件新增到rdlist雙鏈表中。

在epoll中,對於每一個事件,都會建立一個epitem結構體,如下所示:

  1. structepitem{
  2. structrb_node rbn;//紅黑樹節點
  3. structlist_head rdllink;//雙向連結串列節點
  4. structepoll_filefd ffd; //事件控制代碼資訊
  5. structeventpoll *ep; //指向其所屬的eventpoll物件
  6. structepoll_eventevent;//期待發生的事件型別
  7. }
當呼叫epoll_wait檢查是否有事件發生時,只需要檢查eventpoll物件中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件複製到使用者態,同時將事件數量返回給使用者。

epoll資料結構示意圖

從上面的講解可知:通過紅黑樹和雙鏈表資料結構,並結合回撥機制,造就了epoll的高效。

OK,講解完了Epoll的機理,我們便能很容易掌握epoll的用法了。一句話描述就是:三步曲。

第一步:epoll_create()系統呼叫。此呼叫返回一個控制代碼,之後所有的使用都依靠這個控制代碼來標識。

第二步:epoll_ctl()系統呼叫。通過此呼叫向epoll物件中新增、刪除、修改感興趣的事件,返回0標識成功,返回-1表示失敗。

第三部:epoll_wait()系統呼叫。通過此呼叫收集收集在epoll監控中已經發生的事件。

最後,附上一個epoll程式設計例項。(作者為sparkliang)

  1. //
  2. // a simple echo server using epoll in linux
  3. //
  4. // 2009-11-05
  5. // 2013-03-22:修改了幾個問題,1是/n格式問題,2是去掉了原始碼不小心加上的ET模式;
  6. // 本來只是簡單的示意程式,決定還是加上 recv/send時的buffer偏移
  7. // by sparkling
  8. //
  9. #include <sys/socket.h>
  10. #include <sys/epoll.h>
  11. #include <netinet/in.h>
  12. #include <arpa/inet.h>
  13. #include <fcntl.h>
  14. #include <unistd.h>
  15. #include <stdio.h>
  16. #include <errno.h>
  17. #include <iostream>
  18. using namespace std;
  19. #define MAX_EVENTS 500
  20. struct myevent_s
  21. {
  22. int fd;
  23. void (*call_back)(int fd, int events, void *arg);
  24. int events;
  25. void *arg;
  26. int status; // 1: in epoll wait list, 0 not in
  27. char buff[128]; // recv data buffer
  28. int len, s_offset;
  29. long last_active; // last active time
  30. };
  31. // set event
  32. void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
  33. {
  34. ev->fd = fd;
  35. ev->call_back = call_back;
  36. ev->events = 0;
  37. ev->arg = arg;
  38. ev->status = 0;
  39. bzero(ev->buff, sizeof(ev->buff));
  40. ev->s_offset = 0;
  41. ev->len = 0;
  42. ev->last_active = time(NULL);
  43. }
  44. // add/mod an event to epoll
  45. void EventAdd(int epollFd, int events, myevent_s *ev)
  46. {
  47. struct epoll_event epv = {0, {0}};
  48. int op;
  49. epv.data.ptr = ev;
  50. epv.events = ev->events = events;
  51. if(ev->status == 1){
  52. op = EPOLL_CTL_MOD;
  53. }
  54. else{
  55. op = EPOLL_CTL_ADD;
  56. ev->status = 1;
  57. }
  58. if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)
  59. printf("Event Add failed[fd=%d], evnets[%d]\n", ev->fd, events);
  60. else
  61. printf("Event Add OK[fd=%d], op=%d, evnets[%0X]\n", ev->fd, op, events);
  62. }
  63. // delete an event from epoll
  64. void EventDel(int epollFd, myevent_s *ev)
  65. {
  66. struct epoll_event epv = {0, {0}};
  67. if(ev->status != 1) return;
  68. epv.data.ptr = ev;
  69. ev->status = 0;
  70. epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
  71. }
  72. int g_epollFd;
  73. myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd
  74. void RecvData(int fd, int events, void *arg);
  75. void SendData(int fd, int events, void *arg);
  76. // accept new connections from clients
  77. void AcceptConn(int fd, int events, void *arg)
  78. {
  79. struct sockaddr_in sin;
  80. socklen_t len = sizeof(struct sockaddr_in);
  81. int nfd, i;
  82. // accept
  83. if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
  84. {
  85. if(errno != EAGAIN && errno != EINTR)
  86. {
  87. }
  88. printf("%s: accept, %d", __func__, errno);
  89. return;
  90. }
  91. do
  92. {
  93. for(i = 0; i < MAX_EVENTS; i++)
  94. {
  95. if(g_Events[i].status == 0)
  96. {
  97. break;
  98. }
  99. }
  100. if(i == MAX_EVENTS)
  101. {
  102. printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
  103. break;
  104. }
  105. // set nonblocking
  106. int iret = 0;
  107. if((iret = fcntl(nfd, F_SETFL, O_NONBLOCK)) < 0)
  108. {
  109. printf("%s: fcntl nonblocking failed:%d", __func__, iret);
  110. break;
  111. }
  112. // add a read event for receive data
  113. EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);
  114. EventAdd(g_epollFd, EPOLLIN, &g_Events[i]);
  115. }while(0);
  116. printf("new conn[%s:%d][time:%d], pos[%d]\n", inet_ntoa(sin.sin_addr),
  117. ntohs(sin.sin_port), g_Events[i].last_active, i);
  118. }
  119. // receive data
  120. void RecvData(int fd, int events, void *arg)
  121. {
  122. struct myevent_s *ev = (struct myevent_s*)arg;
  123. int len;
  124. // receive data
  125. len = recv(fd, ev->buff+ev->len, sizeof(ev->buff)-1-ev->len, 0);
  126. EventDel(g_epollFd, ev);
  127. if(len > 0)
  128. {
  129. ev->len += len;
  130. ev->buff[len] = '\0';
  131. printf("C[%d]:%s\n", fd, ev->buff);
  132. // change to send event
  133. EventSet(ev, fd, SendData, ev);
  134. EventAdd(g_epollFd, EPOLLOUT, ev);
  135. }
  136. else if(len == 0)
  137. {
  138. close(ev->fd);
  139. printf("[fd=%d] pos[%d], closed gracefully.\n", fd, ev-g_Events);
  140. }
  141. else
  142. {
  143. close(ev->fd);
  144. printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
  145. }
  146. }
  147. // send data
  148. void SendData(int fd, int events, void *arg)
  149. {
  150. struct myevent_s *ev = (struct myevent_s*)arg;
  151. int len;
  152. // send data
  153. len = send(fd, ev->buff + ev->s_offset, ev->len - ev->s_offset, 0);
  154. if(len > 0)
  155. {
  156. printf("send[fd=%d], [%d<->%d]%s\n", fd, len, ev->len, ev->buff);
  157. ev->s_offset += len;
  158. if(ev->s_offset == ev->len)
  159. {
  160. // change to receive event
  161. EventDel(g_epollFd, ev);
  162. EventSet(ev, fd, RecvData, ev);
  163. EventAdd(g_epollFd, EPOLLIN, ev);
  164. }
  165. }
  166. else
  167. {
  168. close(ev->fd);
  169. EventDel(g_epollFd, ev);
  170. printf("send[fd=%d] error[%d]\n", fd, errno);
  171. }
  172. }
  173. void InitListenSocket(int epollFd, short port)
  174. {
  175. int listenFd = socket(AF_INET, SOCK_STREAM, 0);
  176. fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
  177. printf("server listen fd=%d\n", listenFd);
  178. EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);
  179. // add listen socket
  180. EventAdd(epollFd, EPOLLIN, &g_Events[MAX_EVENTS]);
  181. // bind & listen
  182. sockaddr_in sin;
  183. bzero(&sin, sizeof(sin));
  184. sin.sin_family = AF_INET;
  185. sin.sin_addr.s_addr = INADDR_ANY;
  186. sin.sin_port = htons(port);
  187. bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
  188. listen(listenFd, 5);
  189. }
  190. int main(int argc, char **argv)
  191. {
  192. unsigned short port = 12345; // default port
  193. if(argc == 2){
  194. port = atoi(argv[1]);
  195. }
  196. // create epoll
  197. g_epollFd = epoll_create(MAX_EVENTS);
  198. if(g_epollFd <= 0) printf("create epoll failed.%d\n", g_epollFd);
  199. // create & bind listen socket, and add to epoll, set non-blocking
  200. InitListenSocket(g_epollFd, port);
  201. // event loop
  202. struct epoll_event events[MAX_EVENTS];
  203. printf("server running:port[%d]\n", port);
  204. int checkPos = 0;
  205. while(1){
  206. // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
  207. long now = time(NULL);
  208. for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd
  209. {
  210. if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
  211. if(g_Events[checkPos].status != 1) continue;
  212. long duration = now - g_Events[checkPos].last_active;
  213. if(duration >= 60) // 60s timeout
  214. {
  215. close(g_Events[checkPos].fd);
  216. printf("[fd=%d] timeout[%d--%d].\n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
  217. EventDel(g_epollFd, &g_Events[checkPos]);
  218. }
  219. }
  220. // wait for events to happen
  221. int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);
  222. if(fds < 0){
  223. printf("epoll_wait error, exit\n");
  224. break;
  225. }
  226. for(int i = 0; i < fds; i++){
  227. myevent_s *ev = (struct myevent_s*)events[i].data.ptr;
  228. if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event
  229. {
  230. ev->call_back(ev->fd, events[i].events, ev->arg);
  231. }
  232. if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event
  233. {
  234. ev->call_back(ev->fd, events[i].events, ev->arg);
  235. }
  236. }
  237. }
  238. // free resource
  239. return 0;
  240. }
上文轉載自:http://www.cricode.com/3499.html
</article>

歡迎使用Markdown編輯器

你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。

新的改變

我們對Markdown編輯器進行了一些功能拓展與語法支援,除了標準的Markdown編輯器功能,我們增加了如下幾點新功能,幫助你用它寫部落格:

  1. 全新的介面設計 ,將會帶來全新的寫作體驗;
  2. 在創作中心設定你喜愛的程式碼高亮樣式,Markdown 將程式碼片顯示選擇的高亮樣式 進行展示;
  3. 增加了 圖片拖拽 功能,你可以將本地的圖片直接拖拽到編輯區域直接展示;
  4. 全新的 KaTeX數學公式 語法;
  5. 增加了支援甘特圖的mermaid語法1 功能;
  6. 增加了 多螢幕編輯 Markdown文章功能;
  7. 增加了 焦點寫作模式、預覽模式、簡潔寫作模式、左右區域同步滾輪設定 等功能,功能按鈕位於編輯區域與預覽區域中間;
  8. 增加了 檢查列表 功能。

功能快捷鍵

撤銷:Ctrl/Command + Z 重做:Ctrl/Command + Y 加粗:Ctrl/Command + Shift + B 斜體:Ctrl/Command + Shift + I 標題:Ctrl/Command + Shift + H 無序列表:Ctrl/Command + Shift + U 有序列表:Ctrl/Command + Shift + O 檢查列表:Ctrl/Command + Shift + C 插入程式碼:Ctrl/Command + Shift + K 插入連結:Ctrl/Command + Shift + L 插入圖片:Ctrl/Command + Shift + G

合理的建立標題,有助於目錄的生成

直接輸入1次#,並按下space後,將生成1級標題。 輸入2次#,並按下space後,將生成2級標題。 以此類推,我們支援6級標題。有助於使用TOC語法後生成一個完美的目錄。

如何改變文字的樣式

強調文字 強調文字

加粗文字 加粗文字

標記文字

刪除文字

引用文字

H2O is是液體。

210 運算結果是 1024.

插入連結與圖片

連結: link.

圖片: Alt

帶尺寸的圖片: Alt

當然,我們為了讓使用者更加便捷,我們增加了圖片拖拽功能。

如何插入一段漂亮的程式碼片

部落格設定頁面,選擇一款你喜歡的程式碼片高亮樣式,下面展示同樣高亮的 程式碼片.

// An highlighted block
var foo = 'bar';

生成一個適合你的列表

  • 專案
    • 專案
      • 專案
  1. 專案1
  2. 專案2
  3. 專案3
  • 計劃任務
  • 完成任務

建立一個表