socket編程之 select、poll、kqueue、epoll

原生API

select

intselect(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函數參數

numfds:文件描述符的最大值+1(為了限制檢測文件描述符的范圍)

readfds:包含所有因為狀態變為可讀而觸發select函數返回文件描述符

writefds:包含所有因為狀態變為可寫而觸發select函數返回文件描述符

exceptfds:包含所有因為狀態發生特殊異常而觸發select函數返回文件描述符

timeout:表示阻塞超時時限

返回值

當為-1的時候表示出錯

當為0的時候表示超時

當大于0則成功

// 新增fd到set中FD_SET(intfd, fd_set *set);// 從set中移除fdFD_CLR(intfd, fd_set *set);// 判斷fd是否在set中FD_ISSET(intfd, fd_set *set);// 將set整個清0FD_ZERO(fd_set *set);

基本思路,把要檢測的文件描述符加載到?fd_set?類型的集合中,然后調用?select?函數檢測加載到集合中的文件描述符;

select?函數監視的文件描述符分為3類,分別是?writefds, readfds, exceptfds,調用之后select函數就會阻塞,直到有文件描述符就緒(有數據可讀,可寫或者except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數返回;當select函數返回之后,可以通過遍歷?fdset來找到就緒的描述符。

#include#include#include#include#include#includeconstintMAXSIZE =1024;intmain(){intsockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//sockfd為服務器的套接字sockaddr_insin;sin.sin_family = AF_INET;sin.sin_port = htons(4567);//1024 ~ 49151:普通用戶注冊的端口號sin.sin_addr.s_addr = INADDR_ANY;? ? sockaddr_in client_addr;// ...bind 和 listen操作socklen_tclen =sizeof(sockaddr_in);structtimeval tv;intfds[MAXSIZE];memset(fds,-1,sizeof(fds));? ? fd_set fdset;? ? fds[0] = sockfd;while(1) {? ? ? ? FD_ZERO(&fdset);inti =0;intfdmax = fds[0];for(; i < MAXSIZE; i++) {if(fds[i] !=-1) {? ? ? ? ? ? ? ? FD_SET(fds[i], &fdset);if(fdmax < fds[i]) {? ? ? ? ? ? ? ? ? ? fdmax = fds[i];? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }? ? ? ? tv.tv_sec =2;? ? ? ? tv.tv_usec =0;intres = select(fdmax +1, &fdset,NULL,NULL, &tv);? ? ? ? assert(res !=-1);if(res ==0) {printf("timeout\n");? ? ? ? }else{inti =0;for(; i < MAXSIZE; i++) {if(fds[i] ==-1) {continue;? ? ? ? ? ? ? ? }if(FD_ISSET(fds[i], &fdset)) {if(fds[i] == sockfd) {intc = accept(sockfd, (structsockaddr *)&client_addr, &clen);if(c >=0) {// 找到一個空的設置成新的套接字for(intk =0; k < MAXSIZE; k++) {if(fds[i] ==0) {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? fds[i] = c;break;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }else{charbuff[256] = {0};intn = read(fds[i], buff,255);if(n >0) {printf("read:%s\n", buff);? ? ? ? ? ? ? ? ? ? ? ? ? ? write(fds[i],"OK",2);? ? ? ? ? ? ? ? ? ? ? ? }elseif(n ==0) {// 刪除套接字fds[i] =0;? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }? ? }}

這個代碼中有不完善的地方:使用數組保存套接字,建議以鏈表的形式保存鏈表會更好一些;

優點:跨平臺

缺點:

單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內核的方式提升這個限制,但是這樣也會造成效率的降低;

每次都要調用 select ,都需要把?fd?集合從用戶態拷貝到內核態,在fd很多時開銷會很大;

每次調用 select 都需要在內核遍歷傳遞進來的所有fd,在fd很多時開銷也很大;

注意,每次調用select之前都要對fdset集合進行 FD_ZERO(&fdset) 操作,即清空。

參考文章

linux的I/O復用技術

poll

intpoll(struct pollfd *fds, unsigned int nfds, int timesout);

函數參數:

表示一個pollfd結構的數組。用來保存想要監聽的文件描述符及其注冊(綁定)的相應事件

表示監聽事件集合的大小

指定poll的超時值。當timeout為-1時,就會一直阻塞,直到某個事件發生;當timeout為0時,表示立即返回。

返回值:

當為-1的時候表示失敗,當為0的時候表示超時,當為大于0的整數的時候表示執行成功,表示文件描述符的個數。

不同與select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。

structpollfd {intfd;/* file descriptor */shortevents;/* requested events to watch */shortrevents;/* returned events witnessed */};

該結構里包含了要監視等待的event和實際發生的event;

經常檢測的事件標記:

POLLIN/POLLRDNORM:可讀

POLLOUT/POLLWRNORM:可寫

POLLERR:出錯

合法的事件標記如下:

POLLIN: 有數據可讀

POLLRDNORM: 有普通數據可讀

POLLRDBAND: 有優先數據可讀

POLLPRI: 有緊迫數據可讀

POLLOUT: 寫數據不會導致阻塞

POLLWRNORM: 寫普通數據不會導致阻塞

POLLWRBAND: 寫優先數據不會導致阻塞

POLLMSG SIGPOLL: 消息可用

POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM |POLLRDBAND,而POLLOUT則等價于POLLWRNORM。

從原理上看,select?和?poll?都需要在返回以后,通過遍歷文件描述符來獲取已經就緒的socket。但是和select不同的是,調用這個函數后,系統不用清空它所檢測的socket描述符集合;

因此select函數適合于只檢測少量socket描述符的情況,而poll函數適合于大量socket描述符的情況;

#include#include#include#include#include#include#include#defineOPEN_MAX 100intmain(int argc, char *argv[]){//1.創建tcp監聽套接字intsockfd = ::socket(AF_INET, SOCK_STREAM,0);//2.綁定sockfdstructsockaddr_in my_addr;? ? bzero(&my_addr,sizeof(my_addr));? ? my_addr.sin_family = AF_INET;? ? my_addr.sin_port = htons(8000);? ? my_addr.sin_addr.s_addr = htonl(INADDR_ANY);? ? bind(sockfd, (structsockaddr *)&my_addr,sizeof(my_addr));//3.監聽listenlisten(sockfd,10);//4.poll相應參數準備structpollfd client[OPEN_MAX];inti =0, maxi =0;for(;i maxi)? ? ? ? ? ? ? ? maxi = i;? ? ? ? }//5.2繼續響應就緒的描述符for(i=1; i<=maxi; i++)? ? ? ? {if(client[i].fd <0)continue;if(client[i].revents & (POLLIN | POLLERR))? ? ? ? ? ? {intlen =0;charbuf[128] ="";//5.2.1接受客戶端數據if((len = recv(client[i].fd, buf,sizeof(buf),0)) <0)? ? ? ? ? ? ? ? {if(errno == ECONNRESET)//tcp連接超時、RST{? ? ? ? ? ? ? ? ? ? ? ? close(client[i].fd);? ? ? ? ? ? ? ? ? ? ? ? client[i].fd =-1;? ? ? ? ? ? ? ? ? ? }elsecout<<"read error:"<

kqueue

intkqueue(void);

生成一個內核事件隊列,返回該隊列的文件描述符,其它API通過這個描述符操作這個?kqueue,結構如下:

structkevent {uintptr_tident;//事件ID,一般為文件描述符shortfilter;//事件過濾器u_short flags;//行為標示u_int fflags;//過濾器標識值intptr_tdata;//過濾器數據void*udata;//應用透傳數據};intkevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);

提供向內核注冊/反注冊事件和返回就緒事件或錯誤事件;在一個kqueue中,{ident,filter}確定一個唯一的事件;

函數參數:

kq:kqueue的文件描述符

changelist:注冊/反注冊的事件數組

nchanges:changelist的元素個數

eventlist:滿足條件的通知事件數組

nevents:eventlist的元素個數

timeout:等待事件到來時的超時時間

返回值為可用事件的個數

kqueue不光能夠處理socket的事件,同時還能處理異步io,信號,文件變化等等;

kqueue有兩個部分,分別是kqueue和kevent;kqueue主要是用來描述event的隊列,而kevent則是監聽的事件;

通過kevent提供三個主要的行為功能,分別是

注冊/反注冊

注意kevent中的neventlist這個輸入參數,當其設為0,且傳入合法的changelist和nchanges,就會將?changelist?中的事件注冊到 kqueue 中;

允許/禁止過濾器事件

通過flags EV_ENABLE 和 EV_DISABLE?使過濾器事件有效或者無效,這個功能在使用?EVFILT_WRITE發送數據時非常有用;

等待事件通知

將?nchangelist 和 nchanges?設置成?null和0?,當kevent非錯誤和超時返回時,在?eventlist和nevents?中保存可用事件集合。

實現

#include#include#include#include#include#include#include#definePORT 5001#defineMAX_EVENT_COUNT 64intcreateSocket(){intsock = socket(PF_INET, SOCK_STREAM,0);if(sock ==-1)? ? {printf("socket() failed:%d\n",errno);return-1;? ? }structsockaddr_in addr;? ? addr.sin_family = AF_INET;? ? addr.sin_port = htons(PORT);? ? addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);intoptval =1;? ? setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval,sizeof(optval));? ? optval =1;? ? setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &optval,sizeof(optval));if(bind(sock, (structsockaddr*)&addr,sizeof(structsockaddr)) ==-1)? ? {printf("bind() failed:%d\n",errno);return-1;? ? }if(listen(sock,5) ==-1)? ? {printf("listen() failed:%d\n",errno);return-1;? ? }returnsock;}intmain(int argc, const char * argv[]){intlistenfd = createSocket();if(listenfd ==-1)return-1;intkq = kqueue();if(kq ==-1)? ? {printf("kqueue failed:%d",errno);return-1;? ? }structkevent event = {listenfd,EVFILT_READ,EV_ADD,0,0,NULL};intret = kevent(kq, &event,1,NULL,0,NULL);if(ret ==-1)? ? {printf("kevent failed:%d",errno);return-1;? ? }while(true)? ? {structkevent eventlist[MAX_EVENT_COUNT];structtimespec timeout = {5,0};intret = kevent(kq,NULL,0, eventlist, MAX_EVENT_COUNT, &timeout);if(ret <=0)continue;for(inti=0; i0)? ? ? ? ? ? ? ? {structkevent changelist[2];? ? ? ? ? ? ? ? ? ? EV_SET(&changelist[0], clientfd, EVFILT_READ, EV_ADD,0,0,NULL);? ? ? ? ? ? ? ? ? ? EV_SET(&changelist[1], clientfd, EVFILT_WRITE, EV_ADD,0,0,NULL);? ? ? ? ? ? ? ? ? ? kevent(kq, changelist,1,NULL,0,NULL);? ? ? ? ? ? ? ? }continue;? ? ? ? ? ? }//異常事件if(flags & EV_ERROR)? ? ? ? ? ? {? ? ? ? ? ? ? ? close(sock);structkevent event = {sock,EVFILT_READ,EV_DELETE,0,0,NULL};? ? ? ? ? ? ? ? kevent(kq, &event,1,NULL,0,NULL);printf("socket broken,error:%ld\n",data);continue;? ? ? ? ? ? }//數據可讀if(filter == EVFILT_READ)? ? ? ? ? ? {charbuffer[data];memset(buffer,'\0', data);ssize_trecvlen = recv(sock, buffer, data,0);if(recvlen <=0)? ? ? ? ? ? ? ? {//鏈接斷開close(sock);structkevent event = {sock,EVFILT_READ,EV_DELETE,0,0,NULL};? ? ? ? ? ? ? ? ? ? kevent(kq, &event,1,NULL,0,NULL);printf("socket broken!\n");continue;? ? ? ? ? ? ? ? }printf("%s\n",buffer);? ? ? ? ? ? }//數據可寫if(filter == EVFILT_WRITE)? ? ? ? ? ? {charbuffer[data];memset(buffer,'a', data);ssize_tsendlen = send(sock, buffer, data,0);if(sendlen <=0)? ? ? ? ? ? ? ? {//鏈接斷開close(sock);structkevent event = {sock,EVFILT_READ,EV_DELETE,0,0,NULL};? ? ? ? ? ? ? ? ? ? kevent(kq, &event,1,NULL,0,NULL);printf("socket broken!\n");continue;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }? ? }return0;}

不同

和前面不同的是,kqueue不會像select或者poll一樣每隔一段事件就去輪詢所有的socket,當socket數量很多,但是很多socket都不活躍的時候,性能是有影響的,而kqueue只會關注事件發生的socket;

epoll

函數

創建事件表

intepoll_create(int size);

創建一個epoll的句柄,參數 size 并不是限制了epoll所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的建議,不同于select中的給出最大監聽的fd+1。

操作事件表

intepoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

函數參數

epfd:事件表的文件描述符

op:何種操作,包括?EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD,分別實現對fd的監聽事件進行添加、刪除、修改

fd:需要監聽的文件描述符

event:告訴內核需要監聽什么事

epoll_event 結構如下:

structepoll_event {__uint32_tevents;/* Epoll events */epoll_data_tdata;/* User data variable */};//events可以是以下幾個宏的集合:EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);EPOLLOUT:表示對應的文件描述符可以寫;EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);EPOLLERR:表示對應的文件描述符發生錯誤;EPOLLHUP:表示對應的文件描述符被掛斷;EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

監聽相應事件

intepoll_wait(intepfd,structepoll_event *events,intmaxevents,inttimeout)

函數參數:

epfd:事件表的文件描述符

events:從內核得到事件的集合

maxevents:事件集合的大小(不能大于創建時的size)

timeout:超時時間

工作模式

epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別如下:

LT模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。

ET模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。

ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

當使用epoll的ET模型來工作時,當產生了一個EPOLLIN事件后, 讀數據的時候需要考慮的是當recv()返回的大小如果等于請求的大小,那么很有可能是緩沖區還有數據未讀完,也意味著該次事件還沒有處理完,所以還需要再次讀取:

voidhandle_rev(){while(rs){? ? ? ? buflen = ::recv(activeevents[i].data.fd, buf,sizeof(buf),0);if(buflen <0){// 由于是非阻塞的模式,所以當errno為EAGAIN時,表示當前緩沖區已無數據可讀// 在這里就當作是該次事件已處理處.if(errno == EAGAIN){//EAGAIN經常出現在當應用程序進行一些非阻塞(non-blocking)操作(對文件或socket)的時候break;? ? ? ? ? ? }else{return;? ? ? ? ? ? }? ? ? ? }elseif(buflen ==0){// 這里表示對端的socket已正常關閉.}if(buflen ==sizeof(buf){? ? ? ? ? ? rs =1;// 需要再次讀取}else{? ? ? ? ? ? rs =0;? ? ? ? }? ? }}

有時候epoll不一定比select和poll的效率高,比如這樣的場景下:當活動連接數比較高的時候此時epoll會經常觸發回調函數 ,此時在性能上還是有一定的損失.epoll適用于連接數量多,但是活躍的連接少.

實現

epollserver::epollserver(intaf,inttype,intprotocol) : norserver(af, type, protocol) {this->_epollfd = ::epoll_create(MAX_SIZE);if(this->_epollfd == INVALID_SOCKTE) {cout<<"epoll create failed"<close(this->socket());}voidepollserver::wait_events() {structepoll_event _events[EPOLL_EVENTS_NUM];this->add_event(this->socket(), EPOLLIN);while(true) {intret = ::epoll_wait(this->_epollfd, _events, EPOLLEVENTS,-1);this->handle_events(_events, ret);? ? }}voidepollserver::handle_events(structepoll_event* events,intnum) {for(inti =0; i < num; i++) {intsocket = events[i].data.fd;// 服務器本身if(socket ==this->socket()) {this->handle_accept();? ? ? ? }elseif(events[i].events & EPOLLIN) {this->handle_read(socket);? ? ? ? }elseif(events[i].events & EPOLLOUT) {this->handle_write(socket);? ? ? ? }? ? }}voidepollserver::handle_accept() {this->accept();}voidepollserver::handle_read(intsocket) {intnread;charbuf[MAX_SIZE];? ? nread = ::read(socket, buf, MAX_SIZE);if(nread == SOCKET_ERROR)? ? {cout<<"read error:"<close(socket);//記住close fddelete_event(socket, EPOLLIN);//刪除監聽}elseif(nread ==0)? ? {fprintf(stderr,"client close.\n");this->close(socket);//記住close fddelete_event(socket, EPOLLIN);//刪除監聽}else{cout<<"read message is :"<< buf;//修改描述符對應的事件,由讀改為寫modify_event(socket, EPOLLOUT);? ? }}voidepollserver::handle_write(intsocket) {intnwrite;charbuf[MAX_SIZE];? ? nwrite = ::write(socket, buf,strlen(buf));if(nwrite ==-1){cout<<"write error:"<close(socket);//記住close fddelete_event(socket, EPOLLOUT);//刪除監聽}else{? ? ? ? modify_event(socket, EPOLLIN);? ? }memset(buf,0, MAX_SIZE);}boolepollserver::add_event(intsocket,intstate) {structepoll_event ev;? ? ev.events = state;? ? ev.data.fd = socket;if(!epoll_ctl(this->_epollfd, EPOLL_CTL_ADD, socket, fd, &ev)) {cout<<"epoll add event failed"<_epollfd, EPOLL_CTL_DEL, socket, fd, &ev)) {cout<<"epoll delete event failed"<_epollfd, EPOLL_CTL_MOD, socket, fd, &ev)) {cout<<"epoll modify event failed"<

參考文章

Linux IO模式及 select、poll、epoll詳解

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 大綱 一.Socket簡介 二.BSD Socket編程準備 1.地址 2.端口 3.網絡字節序 4.半相關與全相...
    VD2012閱讀 2,406評論 0 5
  • 大綱 一.Socket簡介 二.BSD Socket編程準備 1.地址 2.端口 3.網絡字節序 4.半相關與全相...
    y角閱讀 2,565評論 2 11
  • 看到網上有不少討論epoll,但大多不夠詳細準確,以前面試有被問到這個問題。不去更深入的了解,只能停留在知其然...
    電臺_Fang閱讀 11,898評論 0 8
  • 國慶理財學習如下 0.1了解基金定義:什么是基金基金是一種投資工具,就是把投資者的錢集中起來,交給專業的基金公司來...
    小惠_f2b8閱讀 416評論 3 2
  • 官微定位: 講述真實的用戶故事 獨具匠心的裝修設計和各項服務細節 真誠,務實,便捷 確定內容的構成和分類: 用戶真...
    直徑文摘閱讀 236評論 1 1