Epoll程式設計-I/O多路複用
Epoll定義
epoll是Linux核心為處理大批控制程式碼而作改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著的減少程式在大量併發連線中只有少量活躍的情況下的系統CPU利用率。因為它會複用檔案描述符集合來傳遞結果而不是迫使開發者每次等待事件之前都必須重新準備要被偵聽的檔案描述符集合,另一個原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被核心IO事件非同步喚醒而加入Ready佇列的描述符集合。然後根據這個集合進行相應的資料處理。
Epoll相關函式
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op,int fd,struct epoll_event *event);
int epoll_wait(int epfd,struct epoll_event*event,int maxevents,int timeout);
Epoll工作模式
Epoll用於兩種工作模式-水平觸發(Level Triggered)和邊緣觸發(Edge Triggered).
水平觸發(Level Triggered),是預設的工作方式,並且同時支援 block和 non-block socket。在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式程式設計出錯誤可能性要小一點。傳統的select\poll都是這種模型的代表。
邊緣觸發(Edge Triggered).是高速工作方式,只支援no-block socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述符傳送更多的就緒通知,等到下次有新的資料進來的時候才會再次出發就緒事件。
區別:LT事件不會丟棄,而是隻要讀buffer裡面有資料可以讓使用者讀取,則不斷的通知你。而ET則只在事件發生之時通知。
Epoll使用步驟
1、使用epoll_create函式建立epoll控制程式碼
在核心版本2.6.8以後epoll_create函式中的size就不被使用,但是需要這個引數大於0。
2、事件註冊
int epoll_ctl(int epfd, int op ,int fd,struct epoll_event *event);
引數解析:
Epfd:是epoll_create所拿到的控制程式碼
op:表示要在epfd上進行的操作,其用三個巨集來表示
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件
EPOLL_CTL_DEL: 從epfd中刪除fd
Fd:需要監聽的fd
Event:告訴核心監聽事件的型別,其 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 datavariable */
};
其中包含兩部分:
事件型別(events),可以使用以下的幾個巨集:
EPOLLIN :表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被結束通話;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡
使用者資料,使用者可以將相關檔案描述符的的資料存放到data中。
3、等待事件產生
int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);
引數events用來從核心得到事件的集合,maxevents告之核心這個events有多大,這個maxevents的值必須大於0,引數timeout是超時時間(毫秒單位計數,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函式返回需要處理的事件數目,如返回0表示已超時。
Epoll應用舉例
實現簡單的聊天室程式,伺服器是基於epoll模型實現。整個程式包含1個標頭檔案和3個c檔案,service.c為伺服器程式,client.c為客戶端程式,socket.c為socket封裝好的API函式介面
service.c程式
/*************************************************************************************************************
* 檔名: service.c
* 功能: 基於TCP建立socket服務端程式
* 作者: edward
* 建立時間: 2014年12月31日9:45
* 最後修改時間: 2012年3月12日
* 詳細: 根據的socket建立TCP連線過程,建立服務程式,
並在服務端等待接收和處理客戶端的資料,
並將結果輸出到終端上
*************************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <fcntl.h>
#include "socket.h"
#define MAXFDS 256
int main(int argc,char**argv)
{
int socketFd;
int nfds;
int epfd;
int NewsocketFd;
int Count;
int rtn;
unsigned char buf[1024];
struct epoll_event ev, events[MAXFDS];
if(2 > argc)
{
Log("Input error\n");
Log("Usage: %s <port>\n",argv[0]);
Log("Eample: %s 1234\n",argv[0]);
return -1;
}
//1、伺服器初始化
socketFd = socketServiceInit(atoi(argv[1]));
if(0 > socketFd)
{
Log("Error socketServiceInit\n");
return -1;
}
do{
//~向核心申請MAXFDS+1大小的儲存fd的空間
epfd = epoll_create(MAXFDS+1);
if(0 > epfd)
{
Log("Error epoll_create\n");
break;
}
//~設定需要監聽的fd和觸發事件型別
ev.data.fd = socketFd;
/*
EPOLLIN :表示對應的檔案描述符可以讀;
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被結束通話;
EPOLLET:表示對應的檔案描述符有事件發生;
*/
ev.events = EPOLLIN | EPOLLET;
//~控制某個epoll檔案描述符上的事件,可以註冊事件,修改事件,刪除事件
rtn = epoll_ctl(epfd,EPOLL_CTL_ADD,socketFd,&ev);
/*
引數:
epfd:由 epoll_create 生成的epoll專用的檔案描述符;
op:要進行的操作例如註冊事件,可能的取值
EPOLL_CTL_ADD 註冊、
EPOLL_CTL_MOD 修 改、
EPOLL_CTL_DEL 刪除
fd:關聯的檔案描述符;
event:指向epoll_event的指標;
如果呼叫成功返回0,不成功返回-1
*/
if(0 > rtn)
{
Log("Error epoll_ctl\n");
break;
}
for(;;)
{ //~輪詢I/O事件的發生
nfds = epoll_wait(epfd,events,MAXFDS,0);
/*
epfd:由epoll_create 生成的epoll專用的檔案描述符;
epoll_event:用於回傳代處理事件的陣列;
maxevents:每次能處理的事件數;
timeout:等待I/O事件發生的超時值;
-1相當於阻塞,
0相當於非阻塞。
一般用-1即可返回發生事件數。
*/
for(Count = 0;Count<nfds;++Count)
{
//建立新的請求連線
if(socketFd == events[Count].data.fd)
{
//2、接收客戶端的連線請求
NewsocketFd = createSocketRec();
if(0 > NewsocketFd)
{
continue;
}
//將客戶端的FD放入epoll中
ev.data.fd = NewsocketFd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,NewsocketFd,&ev);
//接收到資料,讀socket
}else if(events[Count].events & EPOLLIN){
//Log("read");
if(socketFd == events[Count].data.fd) continue;
bzero(buf,sizeof(buf));
//3、接收客戶端資料
rtn = SocketRecv(events[Count].data.fd,buf,sizeof(buf));
if(0 == rtn)
{
Log("Connect Close \n");
epoll_ctl(epfd, EPOLL_CTL_DEL, events[Count].data.fd, NULL);
deleteSocketRec(events[Count].data.fd);
continue;
}
Log("[Recv]: %s",buf);
//4、傳送資料到客戶端
socketSeverSendAllclients(buf,sizeof(buf));
//有資料待傳送,寫socket
}else if(events[Count].events & EPOLLOUT){
//Log("write\n");
strcpy(buf,"data from service");
socketSeverSendAllclients(buf,sizeof(buf));
}
}
}
}while(0);
//5、關閉所有的連線,斷開TCP請求
socketSeverClose();
return 0;
}
Client.c程式
/*************************************************************************************************************
* 檔名: client.c
* 功能: 基於TCP建立socket服務端程式
* 作者: edward
* 建立時間: 2014年12月31日9:45
* 最後修改時間: 2012年3月12日
* 詳細: 根據的socket建立TCP連線過程,建立客戶端程式,
並在與伺服器建立連線後,想服務傳送資料
*************************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <fcntl.h>
#include "socket.h"
#define SIZE 1024
int main(int argc,char**argv)
{
int rtn;
int socketFd;
unsigned char buf[SIZE];
unsigned char flag = 1;
if(3 > argc)
{
Log("Input error\n");
Log("Usage: %s <IP addr><port>\n",argv[0]);
Log("Eample: %s 192.168.0.119 1234\n",argv[0]);
return -1;
}
//1、初始化Socket
socketFd = SocketClientInit(argv[1],argv[2]);
if(0 > socketFd)
{
Log("Error on SocketClientInit function\n ");
return -1;
}
//2、往伺服器傳送資料,並接收伺服器資料
while(flag > 0)
{
bzero(buf,sizeof(buf));
printf("Input: ");
fgets(buf,SIZE,stdin);
rtn = strncmp(buf,"exit",4);
if(0 == rtn)
{
flag = 0;
}
socketSend(socketFd,buf,sizeof(buf));
bzero(buf,sizeof(buf));
SocketRecv(socketFd,buf,sizeof(buf));
Log("Recv: %s",buf);
}
//3、關閉的連線
socketClientClose(socketFd);
return 0;
}
socket.c程式
/*************************************************************************************************************
* 檔名: socket.c
* 功能: socket初始化等操作函式
* 作者: edward
* 建立時間: 2014年12月31日9:45
* 最後修改時間:2014年12月31日9:45
* 詳細: socket操作函式
*************************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <errno.h>
#include <assert.h>
#include "socket.h"
#define MAX_CLIENTS 50
socketRecord_t *socketRecordHead = NULL;
/*********************Socket Server 操作函式部分****************/
/*************************************************************************************************************************
*函式 : int socketServiceInit(unsigned int port)
*功能 : 服務端socket初始化
*引數 : port:埠號
*返回 : socketFd
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 在使用epoll的時候需要設定socket為非阻塞模式
*************************************************************************************************************************/
int socketServiceInit(unsigned int port)
{
struct sockaddr_in serv_addr;
int ret;
if(NULL != socketRecordHead)
{
return -1;
}
//1、分配記憶體空間用於儲存socket的一些資料
socketRecord_t *lsSocket = malloc(sizeof(socketRecord_t));
do{
if(NULL == lsSocket)
break;
//2、建立一個socket連線型別的IPV4 ,流式套接字。
lsSocket->socketFd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == lsSocket->socketFd)
{
Log("Error opening Socket: \n");
break;
}
//3、設定的為非阻塞模式
fcntl(lsSocket->socketFd,F_SETFL,O_NONBLOCK);
bzero(&serv_addr,sizeof(struct sockaddr_in));
//4、填充伺服器端的sockaddr結構
serv_addr.sin_family = AF_INET; //IPV4
//(將本機器上的long資料轉化為網路上的long資料)伺服器程式能執行在任何ip的主機上 //INADDR_ANY 表示主機可以是任意IP地址, 即伺服器程式可以繫結到所有的IP上
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//server_addr.sin_addr.s_addr=inet_addr("192.1 68.1 .1 "); //用 於繫結到一個固定IP,inet_addr用 於把數字加格式的ip轉化為整形ip
serv_addr.sin_port = htons(port);//設定埠號(將本機器上的short資料轉化為網路上的short資料)
//5、捆綁socketfd描述符到IP地址
ret = bind(lsSocket->socketFd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(-1 == ret)
{
Log("Error on binding: \n");
break;
}
//6、設定允許連線的最大客戶端的數目
ret = listen(lsSocket->socketFd,10);
if(-1 == ret)
{
Log("Error on Listen: \n");
break;
}
lsSocket->next = NULL;
socketRecordHead = lsSocket;
Log("Service IP %s:%d\n",inet_ntoa(serv_addr.sin_addr),port);
return lsSocket->socketFd;
}while(0);
free(lsSocket);
return -1;
}
/*************************************************************************************************************************
*函式 : int createSocketRec(void)
*功能 : 服務端socket初始化
*引數 : 無
*返回 : socketfd
-1 失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 在使用epoll的時候需要設定socket為非阻塞模式
*************************************************************************************************************************/
int createSocketRec(void)
{
socketRecord_t *srchRec;
int ret ,tr = 1;
if(NULL == socketRecordHead)
{
return -1;
}
socketRecord_t *newSocket = malloc(sizeof(socketRecord_t));
do{
if(NULL == newSocket)break;
newSocket->clilen = sizeof(newSocket->cli_addr);
//接收客戶端連線請求
newSocket->socketFd = accept(socketRecordHead->socketFd,(struct sockaddr *) &(newSocket->cli_addr), &(newSocket->clilen));
if(0 > newSocket->socketFd)
{
Log("Error on accept: \n");
break;
}
fcntl(newSocket->socketFd, F_SETFL, O_NONBLOCK);
newSocket->next = NULL;
srchRec = socketRecordHead;
while (srchRec->next)
srchRec = srchRec->next;
srchRec->next = newSocket;
Log("content from %s\n",inet_ntoa(newSocket->cli_addr.sin_addr));
return (newSocket->socketFd);
}while(0);
Log("Create error\n");
free(newSocket);
return -1;
}
/*************************************************************************************************************************
*函式 : void deleteSocketRec(int rmSocketFd)
*功能 : 服務端關閉連線請求
*引數 : rmSocketFd:
*返回 : 無
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 在關閉的同時,需要將該socketFD從單連結串列中刪除
*************************************************************************************************************************/
void deleteSocketRec(int rmSocketFd)
{
socketRecord_t *srchRec, *prevRec = NULL;
// Head of the timer list
srchRec = socketRecordHead;
// Stop when rec found or at the end
while ((srchRec->socketFd != rmSocketFd) && (srchRec->next))
{
prevRec = srchRec;
// over to next
srchRec = srchRec->next;
}
if (srchRec->socketFd != rmSocketFd)
{
Log("deleteSocketRec: record not found\n");
return;
}
// Does the record exist
if (srchRec)
{
// delete the timer from the list
if (prevRec == NULL)
{
//trying to remove first rec, which is always the listining socket
Log(
"deleteSocketRec: removing first rec, which is always the listining socket\n");
return;
}
//remove record from list
prevRec->next = srchRec->next;
close(srchRec->socketFd);
free(srchRec);
}
}
/*************************************************************************************************************************
*函式 : int socketSeverGetNumClients(void)
*功能 : 服務端獲取已經連線的客戶端數目
*引數 : 無
*返回 : -1:失敗
>0 :客戶端數目
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 所有的已經連線上的客戶端資訊都儲存在連結串列中
*************************************************************************************************************************/
int socketSeverGetNumClients(void)
{
int recordCnt = 0;
socketRecord_t *srchRec;
//Log("socketSeverGetNumClients++\n", recordCnt);
// Head of the timer list
srchRec = socketRecordHead;
if (srchRec == NULL)
{
//Log("socketSeverGetNumClients: socketRecordHead NULL\n");
return -1;
}
// Stop when rec found or at the end
while (srchRec)
{
//Log("socketSeverGetNumClients: recordCnt=%d\n", recordCnt);
srchRec = srchRec->next;
recordCnt++;
}
//Log("socketSeverGetNumClients %d\n", recordCnt);
return (recordCnt);
}
/*************************************************************************************************************************
*函式 : void socketSeverGetClientFds(int *fds, int maxFds)
*功能 : 服務端關閉連線請求
*引數 : fds: 儲存socketFd的陣列
maxFds:fds陣列大小
*返回 : 無
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 遍歷連結串列得到所有socketfd
*************************************************************************************************************************/
void socketSeverGetClientFds(int *fds, int maxFds)
{
int recordCnt = 0;
socketRecord_t *srchRec;
assert(fds!=NULL);
// Head of the timer list
srchRec = socketRecordHead;
// Stop when at the end or max is reached
while ((srchRec) && (recordCnt < maxFds))
{
//printf("getClientFds: adding fd%d, to idx:%d \n", srchRec->socketFd, recordCnt);
fds[recordCnt++] = srchRec->socketFd;
srchRec = srchRec->next;
}
return;
}
/*************************************************************************************************************************
*函式 : void socketSeverClose(void)
*功能 : 服務端關閉連線請求
*引數 : 無
*返回 : 無
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 在關閉的同時,需要將該socketFD從單連結串列中刪除
*************************************************************************************************************************/
void socketSeverClose(void)
{
int fds[MAX_CLIENTS], idx = 0;
socketSeverGetClientFds(fds, MAX_CLIENTS);
while (socketSeverGetNumClients() > 1)
{
Log("socketSeverClose: Closing socket fd:%d\n", fds[idx]);
deleteSocketRec(fds[idx++]);
}
//Now remove the listening socket
if (fds[0])
{
Log("socketSeverClose: Closing the listening socket\n");
close(fds[0]);
}
}
/*************************************************************************************************************************
*函式 : int socketSeverSendAllclients(unsigned char* buf, int len)
*功能 : 服務端向所有的客戶端傳送資料
*引數 : buf:資料儲存
len:資料長度
*返回 : 0:成功
-1:失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 無
*************************************************************************************************************************/
int socketSeverSendAllclients(unsigned char* buf, int len)
{
int rtn;
socketRecord_t *srchRec;
assert(buf != NULL);
// first client socket
srchRec = socketRecordHead->next;
// Stop when at the end or max is reached
while (srchRec)
{
//printf("SRPC_Send: client %d\n", cnt++);
rtn = write(srchRec->socketFd, buf, len);
if (rtn < 0)
{
Log("ERROR writing to socket %d\n", srchRec->socketFd);
Log("closing client socket\n");
//remove the record and close the socket
deleteSocketRec(srchRec->socketFd);
return rtn;
}
srchRec = srchRec->next;
}
return 0;
}
/*********************Socket Client 操作函式部分****************/
/*************************************************************************************************************************
*函式 : int SocketClientInit(const char *addr,const char* port)
*功能 : 客戶端初始化
*引數 : addr:IP地址
port:埠號
*返回 : socketfd
-1 失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 無
*************************************************************************************************************************/
int SocketClientInit(const char *addr,const char* port)
{
int rtn;
int socketFd;
struct sockaddr_in server_addr;
assert(addr!=NULL);
assert(port!=NULL);
socketFd = socket(AF_INET,SOCK_STREAM,0);
if(0 > socketFd)
{
Log("Error on socket\n");
return -1;
}
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = inet_addr(addr);
rtn = connect(socketFd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
if(0 > rtn)
{
Log("Error on connect\n");
return -1;
}
return socketFd;
}
/*************************************************************************************************************************
*函式 : int socketClientClose(int socketFd)
*功能 : 客戶端關閉連線請求
*引數 : socketFd: 客戶端檔案描述符
*返回 : 0:成功
-1:失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 無
*************************************************************************************************************************/
int socketClientClose(int socketFd)
{
if(socketFd)
{
close(socketFd);
return 0;
}
return -1;
}
/*********************公共操作函式部分****************/
/*************************************************************************************************************************
*函式 : int socketSend(int fdClient,unsigned char* buf, int len)
*功能 : 服傳送資料函式
*引數 : fdClient: socket檔案描述符
buf:資料儲存區
len:資料長度
*返回 : 返回寫入的位元組數
-1 寫入失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 無
*************************************************************************************************************************/
int socketSend(int fdClient,unsigned char* buf, int len)
{
int rtn = -1;
//Log("socketSend++: writing to socket fd %d\n", fdClient);
assert(NULL != buf);
if (fdClient)
{
rtn = write(fdClient, buf, len);
if (rtn < 0)
{
Log("ERROR writing to socket %d\n", fdClient);
return rtn;
}
}
//Log("socketSend--\n");
return rtn;
}
/*************************************************************************************************************************
*函式 : int SocketRecv(int fdClient,unsigned char *buf,int len)
*功能 : 接收資料函式
*引數 : fdClient: socket檔案描述符
buf:資料儲存區
len:資料長度
*返回 : 返回讀取的位元組數
-1 讀取失敗
*依賴 : 無
*作者 : edward
*時間 : 20141231
*最後修改時間: 20141231
*說明 : 無
*************************************************************************************************************************/
int SocketRecv(int fdClient,unsigned char *buf,int len)
{
int rtn;
assert(NULL != buf);
if(fdClient)
{
rtn = read(fdClient,buf,len);
if (rtn < 0)
{
Log("ERROR Reading to socket %d\n", fdClient);
return rtn;
}
}
return rtn;
}
socket.h標頭檔案定義
#ifndef __SOCKET_H__
#define __SOCKET_H__
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEBUG
#ifdef DEBUG
#define A_OUT printf("%s:%s:%d:", __FILE__, __FUNCTION__,__LINE__);fflush(stdout);
#define Log(fmt,args...) A_OUT printf(fmt, ##args)
#else
#define Log(fmt,args...) printf(fmt, ##args)
#endif
typedef struct {
void *next;
int socketFd;
socklen_t clilen;
struct sockaddr_in cli_addr;
} socketRecord_t;
#endif
相關文章
- Epoll多路I/O複用技術
- 伺服器程式設計——I/O複用(select、poll、epoll)伺服器程式設計
- 【Linux網路程式設計】I/O 多路複用技術Linux程式設計
- 網路程式設計學習——Linux epoll多路複用模型程式設計Linux模型
- 網路程式設計-I/O複用程式設計
- I/O多路複用技術(multiplexing)
- python網路程式設計——IO多路複用之epollPython程式設計
- Netty權威指南:I/O 多路複用技術Netty
- 一文搞懂I/O多路複用及其技術
- 從網路I/O模型到Netty,先深入瞭解下I/O多路複用模型Netty
- LinuxI/O多路複用Linux
- 詳解Go語言I/O多路複用netpoller模型Go模型
- IO多路複用與epoll機制淺析
- 細談 Linux 中的多路複用epollLinux
- 玩轉 PHP 網路程式設計全套之 I/O 複用PHP程式設計
- 流?I/O 操作?阻塞?epoll?
- 【面試】I/O 複用面試
- I/O Mutiplexing poll 和 epoll
- 一篇文章幫你徹底搞清楚“I/O多路複用”和“非同步I/O”的前世今生非同步
- 一篇文章讀懂Reactor和Proactor兩種I/O多路複用模式React模式
- 系統程式設計 - I/O模型程式設計模型
- Go netpoll I/O 多路複用構建原生網路模型之原始碼深度解析Go模型原始碼
- IO多路複用——深入淺出理解select、poll、epoll的實現
- 多路複用
- 計算機網路——多路複用與多路分解計算機網路
- Linux下的5種I/O模型與3組I/O複用Linux模型
- Oracle 多路複用Oracle
- Linux Shell程式設計(25)——I/O 重定向Linux程式設計
- 理解IO多路複用
- 計算機I/O與I/O模型計算機模型
- Java 程式設計要點之 I/O 流詳解Java程式設計
- Linux下套接字詳解(十)---epoll模式下的IO多路複用伺服器Linux模式伺服器
- 設計模式中巧記I/O設計模式
- IO多路複用詳解
- Redis 和 IO 多路複用Redis
- IO多路複用原理剖析
- 程式設計競賽中 C/C++ I/O 的使用程式設計C++
- 多路分支程式該如何設計?