【epoll】epoll多路複用和Reactor設計思想

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"目錄","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1、Reactor設計思想","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"文章相關視頻講解:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"C/C++ Linux服務器開發高級架構學習視頻點擊:","attrs":{}},{"type":"link","attrs":{"href":"https://ke.qq.com/course/417774?flowToken=1013189","title":null,"type":null},"content":[{"type":"text","text":"C/C++Linux服務器開發/Linux後臺架構師-學習視頻","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.bilibili.com/video/BV1oy4y1n74u/","title":null,"type":null},"content":[{"type":"text","text":"epoll原理剖析以及reactor模型應用","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.bilibili.com/video/BV1rN411Q7HY/","title":null,"type":null},"content":[{"type":"text","text":"linux epoll網絡編程細節處理","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小前言:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reactor必要","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 傳統OIO模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.2 Reactor模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.3 單線程Reactor模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單Reactor多線程模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.4 多線程Reactor模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"封裝Epoll實現併發","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reactor模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"封裝Epoll實現reactor模式的高性能併發服務器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"epoll的api","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reactor模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"EPOLL實現的要點","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1、Reactor設計思想","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小前言:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"reactor是對epoll的一層封裝 ,epoll是對io進行管理,reactor將對io的管理轉化爲對事件的管理","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Reactor必要","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"傳統OIO模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如圖2.1所示爲傳統IO模式處理示意圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/52/52c512c45dc869e111563d59fa4c0cf3.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖中所示一般是一個請求一個單獨的處理線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:server的accpet操作是阻塞的,業務處理中的handler中的讀寫請求也是阻塞的。那麼這樣的一種IO模式將會導致一個線程的請求沒有處理完成無法處理下一個請求,這樣就大大降低了吞吐量,這將是一個嚴重的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這種問題就出現了一個經典的模式——Connection Per Thread即一個線程處理一個請求。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/29/29252349f67185150ea1a66f7485c0c6.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於每一個新的請求都會分配一個新的線程來處理,這樣的好處就是每個socket的請求相互之間不受影響,每個請求的業務邏輯相互之間也不影響。任何socket的讀寫操作都不會影響到後面的請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:不是每個鏈接都有請求發生,這樣就浪費了很多的線程資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個時候可以採用多路複用IO模型的方式來處理IO事件,使用Reactor將響應IO事件和業務處理分開,一個或多個線程來處理IO事件,然後將就緒得到事件分發到業務處理handlers線程去異步非阻塞處理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.2 Reactor模式","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.3 單線程Reactor模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是單線程Reactor模式,單線程模式採用一個Reactor線程來【處理套接字、新連接的創建】,並且【將接收到的請求分發到處理器Handler中】。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如圖2.2爲簡單的單線程Reactor模式示意圖,Reactor和數據處理(handler)都在一個線程裏,圖2.2參考doug lea論文《Scalable IO in Java》論文。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/ec7cbbb70f816ec0560c52bedb81ad51.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2.2單線程Reactor模式示意圖","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單Reactor多線程模式:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a0/a01bc4aeca6368fea48f50486608602e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.4 多線程Reactor模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多線程reactor模式的設計思想就是將handler線程放入到線程次中,在多核的情況下也可以考慮多個Selector選擇器來處理事件,如圖2.3爲簡單的多線程Reactor示意圖;","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d2/d2f9d6387e7401498c111b0fd8ae6671.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖2.3多線程Reactor模式示意圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關於C/C++ Linux後端開發網絡底層原理知識 點擊 ","attrs":{}},{"type":"link","attrs":{"href":"https://jq.qq.com/?_wv=1027&k=9w0IMi5B","title":null,"type":null},"content":[{"type":"text","text":"學習資料","attrs":{}}]},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 獲取,內容知識點包括Linux,Nginx,ZeroMQ,MySQL,Redis,線程池,MongoDB,ZK,Linux內核,CDN,P2P,epoll,Docker,TCP/IP,協程,DPDK等等。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b9/b963efffe2f0a8d97a4bcbc35fd2a599.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"封裝Epoll實現併發","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一次學epoll時,容易錯誤的認爲epoll可以實現併發,其實正確的說法是藉助epoll可以實現高性能併發服務器,epoll只是提供了IO複用,在IO複用,真正的併發只能通過線程進程實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Reactor模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reactor模式實現非常簡單,使用同步IO模型,即業務線程處理數據需要","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主動等待或詢問","attrs":{}},{"type":"text","text":",主要特點是利用epoll監聽listen描述符是否有響應,及時將","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶連接","attrs":{}},{"type":"text","text":"信息","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"放於一個隊列","attrs":{}},{"type":"text","text":",epoll和隊列都是在主進程/線程中,由子進程/線程來接管各個描述符,對描述符進行下一步操作,包括connect和數據讀寫。主程讀寫就緒事件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大致流程圖如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b1/b16a62eb5fe94675dd346a71d3c5b3da.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Preactor模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Preactor模式完全將IO處理和業務分離,使用異步IO模型,即","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內核完成數據處理後主動通知給應用處理","attrs":{}},{"type":"text","text":",主進程/線程不僅要完成listen任務,還需要完成內核數據緩衝區的映射,直接將數據buff傳遞給業務線程,業務線程只需要處理業務邏輯即可。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大致流程如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f534762e1d42c3b7ee97a382c1bcf85.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"封裝Epoll實現reactor模式的高性能併發服務器","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"epoll的api","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先介紹epoll的api","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"int epoll_create(int size); // 創建epfd\nint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //向epfd註冊(fd,event)\n\n// epoll_event結構體定義\nstruct epoll_event {\n __uint32_t events; /* epoll 事件 */\n epoll_data_t data; /* 傳遞的數據,用於處理ready的fd,獲得上下文關係 */\n}\n// data聯合體,一般用其指針域ptr,因爲要從這個data讀取到上下文信息\ntypedef union epoll_data {\n void *ptr;\n int fd;\n uint32_t u32;\n uint64_t u64;\n} epoll_data_t;\n\n// op宏\n /* EPOLL_CTL_ADD(註冊新的fd到epfd)\n * EPOLL_CTL_MOD(修改已經註冊的fd的監聽事件)\n * EPOLL_CTL_DEL(epfd刪除一個fd)\n */\n*\n * events : {EPOLLIN, EPOLLOUT, EPOLLPRI, \n EPOLLHUP, EPOLLET, EPOLLONESHOT}\n */\n\nint epoll_wait(int epfd, struct epoll_event *event, \n int maxevents, int timeout); //用於輪詢註冊的fd,若滿足相應的註冊事件,\n // 則結束epoll_wait阻塞\n/* @param timeout 超時時間\n * -1: 永久阻塞\n * 0: 立即返回,非阻塞\n * >0: 指定微秒\n*/","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Reactor模式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"io模式的歷程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單線程,一般阻塞->多線程,一般阻塞(一條連接一線程)->線程池(減少線程創建銷燬開銷)->reactor(更小粒度的線程)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂更小的粒度的線程是指,傳統的多線程是一個連接一個線程,粒度太大,比如可以把一個連接繼續細分成三個步驟:read,process,send三個步驟,每個步驟佔一個線程,處理完後交給主線程調度,進入下一個處理模塊","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"EPOLL實現的要點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"0. 創建epoll_fd = epoll_create(MAX_EVENT+1)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 維護event數組","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.1 一個交給epoll_wait維護(空的放進去,ready的出來)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.1.1 特定的結構體 struct epoll_event","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.2 一個自己維護(維持對所有註冊事件的監控)(全局)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.2.1 由於自己維護,想怎麼寫怎麼寫,一般的會讓epoll_event.data.ptr = myevent,以作爲回調函數的參數,保持一個上下文關係","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 創建並初始化事件(epoll_event)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.1 結構體有兩個字段 event和data , event是EPOLLIN這樣的宏,data的作用是記錄一些消息,這樣ready的時候可以訪問這個消息,比如fd, 回調函數指針,status等等","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. 向epfd註冊事件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.1 epoll_ctl(epfd , op_macro , target_fd , &epoll_event )","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.2 op_macro是代表epoll_ctl的類型,諸如EPOLL_CTL_ADD","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.3 target_fd是要監聽的fd,比如tcp監聽套接字ls_socket,epoll_event就是當事件發生時,epoll_wait()裏面 event[]將會出現的結構體","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4. 寫回調函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.1 這是reactor模式的重點.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.2 典型的,檢測到ls_socket可讀後(epoll_wait不再阻塞),進入回調函數(通過event[i].data.ptr->callback),假如命名爲accept_fn(),在這個函數簡單來看要做的事就是","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.conn_socket = accept(ls_socket)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.創建,初始化epoll_event並註冊到epfd(當然還要加進自己維護的fd列表),","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由於epoll是輪詢的模式,需要將conn_socket用fcntl設爲O_NONBLOCK,非阻塞.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.3 這個不像select,需要每次重新加入fd到列表裏面,註冊一次即可","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.4 conn_socket被通過事件EPOLLIN註冊到epfd,下一步馬上的,epoll_wait檢測出conn_socket可讀(假設確實可讀),然後回調進入recvdata函數(需要在創建並初始化epoll_","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"event指定, 實際上是自定義一個結構體,讓data.ptr指向這個結構體就行),一般的,就以這個結構體組成自己維護的fd list,","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"4.5 recvdata函數首先","attrs":{}},{"type":"text","text":"調用recv(fd,my_event->buf,...),把待發送的數據存在my_event->buf , 然後修改(fd,my","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"event)到epfd爲發送事件EPOLLOUT,以及改變回調函數爲callback_send","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.6 來到epoll_wait,檢測到該fd可寫,則回調進入senddata函數,該函數調用send(fd,my","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"event->buf)發送數據,然後修改fd的註冊事件爲EPOLLIN(即EPOLL_CTLMOD),清空my_","attrs":{}},{"type":"text","text":"event->buf....如此反覆","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. callback函數如何獲得my_event? 我們在epoll_event中能獲得event和data,在data.ptr中找到my_event,典型的my_event可能包括回調函數指針,event_type,fd,buf[BUFLEN],last_active_time,...","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"nfd = epoll_wait(epfd , epoll_events , MAX_EVENT+1 , 1000)\nassert(nfd>0)\nfor i in range(nfd):\n my_event* ev = (my_event*) epoll_events[i].data.ptr\n // if (epoll_events[i].events& EPOLLIN){} 用按位與的方式檢測相等,因爲這種flag宏一般都是位不相同的\n // 用回調函數的方法時,不需要比較event_type,直接調用函數即可\n // ev->callback_fn(ev) 不可以 callback_fn的原型應該是void(*callvback_fn)(void*),\n // 因爲定義callback原型的時候,ev還是不完整的類型\n ev->callback_fn((void*)ev)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章