版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/weixin_36750623/article/details/83547803
一.Linux下的I/O複用與epoll詳解linux
與select/poll不一樣的是,epoll採用回調函數機制,epoll只關心「活躍」的鏈接,無需遍歷所有的文件描述符數據庫
一.爲何引出epoll?
1.select的缺點
1.select所用到的FD_SET是有限的編程
/linux/posix_types.h:
#define __FD_SETSIZE 1024
1
2
3.select/poll都要進行不斷的將fd集合在內核空間和用戶空間的來回拷貝
2.內核中實現 select是用輪詢方法,即每次檢測都會遍歷全部FD_SET中的句柄,顯然,select函數執行時間與FD_SET中的句柄個數有一個比例關係,即 select要檢測的句柄數越多就會越費時數組
2.epoll高效的奧祕(實現原理)
三大關鍵因素:mmap/紅黑樹/鏈表
(1) epoll_create:epoll是經過內核與用戶空間mmap同一塊內存映射區實現的。mmap將用戶空間的一塊地址和內核空間的一塊地址映射到物理內存地址,使得這塊物理內存對內核和用戶都可見,減小用戶態和內核態之間的數據交換。
(2) epoll_ctl:紅黑樹將存儲epoll所監聽的套接字,當epoll_ctl添加/刪除一個套接字時,其實是在紅黑樹上進行節點的插入/刪除。
注意:當使用epoll_ctl函數將事件添加到紅黑樹上後,會完成更爲關鍵的異步(那就是該事件都會與相應的設備驅動程序創建回調關係)
(3) epoll_wait:一旦有事件發生,就會調用註冊的回調函數ep_poll_callback,該回調函數的做用是這個事件添加到就緒雙向鏈表rdlist中。調用epoll_wait時,epoll_wait只須要檢查雙向鏈表rdlist中是否有存在註冊的事件
epoll_wait的工做流程:
1.epoll_wait調用ep_poll,當rdlist爲空(無就緒fd)時掛起當前進程,直到rdlist不空時進程才被喚醒
2.當有就緒fd發生時,將調用ep_poll_callback,它將相應fd對應epitem加入rdlist,致使rdlist不爲空,進程被喚醒,epoll_wait將返回
3.ep_events_transfer函數將雙向鏈表rdlist中的epitem拷貝到txlist中,並將雙向鏈表rdlist清空
4.ep_send_event函數(很關鍵),它掃描txlist中的每一個epitem,調用其關聯fd對應的poll方法。此時對poll的調用僅僅是取得fd上較新的events(防止以前events被更新),以後將取得的events和相應的fd發送到用戶空間(封裝在struct epoll_event,從epoll_wait返回)。==以後若是這個epitem對應的fd是LT模式監聽且取得的events是用戶所關心的,則將其從新加入回rdlist(圖中藍線),不然(ET模式)不在加入rdlist。安全
二.epoll函數API
int epoll_create(int size); //哈希表
int epoll_create1(int flags); //紅黑樹服務器
int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );網絡
參數
epfd:epoll_create的返回值
fd:要操做的文件描述符
op:操做類型 EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL
event:指定事件,它是epoll_event結構指針類型
其中,epoll_event—>每個文件描述符都有一個對應的epoll_event結構,該結構爲 :異步
struct epoll_event{
__unit32_t events; // epoll事件類型:EPOLLET / EPOLLONESHOT
epoll_data_t data; // 存儲用戶數據
};
其中,epoll_data_t定義:
typedef union epoll_data{
void* ptr; //自定義的結構體(最經常使用)
int fd; //指定事件所從屬的目標文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
1
2
3
4
5
6
7
8
9
10
11
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );socket
返回值:成功時返回就緒的文件描述符的個數,失敗時返回-1並設置errno
參數
timeout:指定epoll的超時時間,單位是毫秒。
maxevents:指定最多監聽多少個事件
events:傳出參數,是一個數組,epoll_wait函數返回後,全部就緒的事件
三.使用epoll接口的通常操做流程爲:
(1)使用epoll_create()建立一個epoll對象,該對象與epfd關聯,後續操做使用epfd來使用這個epoll對象,這個epoll對象纔是紅黑樹,epfd做爲描述符只是能關聯而已。
(2)調用epoll_ctl()向epoll對象中進行增長、刪除等操做。
(3)調用epoll_wait()能夠阻塞(或非阻塞或定時) 返回待處理的事件集合。
(3)處理事件。分佈式
/*
* -[ 通常epoll接口使用描述01 ]-
*/
int main(void)
{
/*
* 此處省略網絡編程經常使用初始化方式(從申請到最後listen)
* 而且部分的錯誤處理省略,我會在後面放上全部的源碼,這裏只放重要步驟
* 部分初始化也沒寫
*/
// [1] 建立一個epoll對象
ep_fd = epoll_create(OPEN_MAX); /* 建立epoll模型,ep_fd指向紅黑樹根節點 */
listen_ep_event.events = EPOLLIN; /* 指定監聽讀事件 注意:默認爲水平觸發LT */
listen_ep_event.data.fd = listen_fd; /* 注意:通常的epoll在這裏放fd */
// [2] 將listen_fd和對應的結構體設置到樹上
epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &listen_ep_event);
while(1) {
// [3] 爲server阻塞(默認)監聽事件,ep_event是數組,裝知足條件後的全部事件結構體
n_ready = epoll_wait(ep_fd, ep_event, OPEN_MAX, -1);
for(i=0; i<n_ready; i++) {
temp_fd = ep_event[i].data.fd;
if(ep_event[i].events & EPOLLIN){
if(temp_fd == listen_fd) { //說明有新鏈接到來
connect_fd = accept(listen_fd, (struct sockaddr *)&client_socket_addr, &client_socket_len);
// 給即將上樹的結構體初始化
temp_ep_event.events = EPOLLIN;
temp_ep_event.data.fd = connect_fd;
// 上樹
epoll_ctl(ep_fd, EPOLL_CTL_ADD, connect_fd, &temp_ep_event);
}
else { //cfd有數據到來
n_data = read(temp_fd , buf, sizeof(buf));
if(n_data == 0) { //客戶端關閉
epoll_ctl(ep_fd, EPOLL_CTL_DEL, temp_fd, NULL) //下樹
close(temp_fd);
}
else if(n_data < 0) {}
do {
//處理數據
}while( (n_data = read(temp_fd , buf, sizeof(buf))) >0 ) ;
}
}
else if(ep_event[i].events & EPOLLOUT){
//處理寫事件
}
else if(ep_event[i].events & EPOLLERR) {
//處理異常事件
}
}
}
close(listen_fd);
close(ep_fd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
四. 水平觸發LT / 邊緣觸發ET
evt.events = EPOLLIN | EPOLLET; /*邊沿觸發 */
evt.events = EPOLLIN | EPOLLIN; /*水平觸發 */
1
2
1.ET和LT在本質上的區別
[1] 水平觸發LT
從圖中能夠看到:只要有數據,LT方式epoll_wait就會返回
1.若是用戶在監聽epoll事件,當內核有事件的時候,會拷貝給用戶態事件,可是若是用戶只處理了一次,那麼剩下沒有處理的會在下一次epoll_wait再次返回該事件。
2.這樣若是用戶永遠不處理這個事件,就致使每次都會有該事件從內核到用戶的拷貝,耗費性能,可是水平觸發相對安全,最起碼事件不會丟掉,除非用戶處理完畢。
[2]邊緣觸發ET
從圖中能夠看到:儘管還有數據未被處理,可是ET方式epoll_wait也不會返回
1.邊緣觸發,相對跟水平觸發相反,當內核有事件到達, 只會通知用戶一次,至於用戶處理仍是不處理,之後將不會再通知。
2.這樣減小了拷貝過程,增長了性能,可是相對來講,若是用戶馬虎忘記處理,將會產生事件丟的狀況。
2.ET和LT的區別
if ET:當且僅當有新到來的數據,epoll_wait才返回
if LT:只要有數據,epoll_wait就返回
3.那麼,爲何說邊沿觸發(ET) 的效率更高呢?*
(1) 邊沿觸發只在數據到來的一刻才觸發,不少時候服務器在接受大量數據時會先接受數據頭部(水平觸發在此觸發第一次,邊沿觸發第一次)。
(2) 接着服務器經過解析頭部決定要不要接這個數據。此時,若是不接受數據,水平觸發須要手動清除,而邊沿觸發能夠將清除工做交給一個定時的清除程序去作,本身馬上返回。
(3) 若是接受,兩種方式均可以用while接收完整數據。
4.邊緣觸發ET的使用技巧:epoll + 非阻塞fd+ET
舉例說明:Client向Server一次性發送10個字節的數據;服務器一次接受5個字節的數據,下面Server使用兩種方式去讀取數據:
方式1:阻塞+LT觸發模式
代碼分析:讀取10字節的數據:(1)先執行step1的epoll_wait,再執行step2的read讀取5個字節;(2)再執行step1的epoll_wait,再執行step2的read讀取5個字節
結論:讀取10個字節,須要調用2次epoll_wait
while (1){
epoll_wait(epfd, resevent, maxi+1, -1); //step1
if (resevent[0].data.fd == connfd){
len = read(connfd, buf, 5); //step2
write(STDOUT_FILEND, buf, len);
}
}
1
2
3
4
5
6
7
方式2:非阻塞+ET觸發模式+while(read)
代碼分析:(1)先執行step1的epoll_wait,再執行step2的read2讀取5個字節(2)繼續調用step2的read讀取5個字節
結論:讀取10個字節,只須要調用1次epoll_wait
先用fcntl將鏈接的套接字connfd設置爲非阻塞O_NOBLOCK
while (1){
epoll_wait(epfd, resevent, maxi+1, -1);
if (resevent[0].data.fd == connfd){
while ((len = read(connfd, buf, 5))){//非阻塞讀,有數據就輪詢讀,直到讀完緩衝區中全部的數據
write(STDOUT_FILEND, buf, len);
}
}
}
1
2
3
4
5
6
7
8
9
總結:採用[非阻塞fd+邊緣觸發ET+while循環讀]的方式,比採用[阻塞fd+水平觸發LT]的方式調用epoll_wait的次數大大減小!效率更高
二. libevent核心思想:epoll反應堆模型
一.epoll的struct epoll_event結構體
自定義結構體
struct epoll_event{
__unit32_t events; // epoll事件類型:EPOLLET / EPOLLONESHOT
epoll_data_t data; // 存儲用戶數據
};
其中,epoll_data_t定義:
typedef union epoll_data{
void* ptr; //自定義的結構體(最經常使用)
int fd; // 通常
uint32_t u32;
uint64_t u64;
} epoll_data_t;
1
2
3
4
5
6
7
8
9
10
11
還記得每個在紅黑樹上的文件描述符所對應的結構體epoll_event嗎?
1.通常在epoll_event結構體中的聯合體data上傳入的是文件描述符fd自己
2.可是在epoll模型中,傳入聯合體的是一個自定義結構體指針,該結構體的基本結構至少包括:
struct my_events {
int m_fd; //監聽的文件描述符
void *m_arg; //泛型參數
void (*call_back)(void *arg); //回調函數
/*
* 你能夠在此處封裝更多的數據內容
* 例如用戶緩衝區、節點狀態、節點上樹時間等等
*/
};
/*
* 注意:用戶須要自行開闢空間存放my_events類型的數組,並在每次上樹前用epoll_data_t裏的
* ptr指向一個my_events元素。
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
根據該模型,在程序中可讓全部的事件都擁有本身的回調函數,只須要使用ptr傳入便可。示例代碼:
while(1) {
int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); /* 監聽紅黑樹, 1秒沒事件知足則返回0 */
if (n_ready > 0) {
for (i=0; i<n_ready; i++)
events[i].data.ptr->call_back(/* void *arg */); //調用回調函數
}
else
/*
* 這裏能夠作不少不少其餘的工做,例如定時清除沒讀完的不要的數據
* 也能夠作點和數據庫有關的設置
* 玩大點你在這裏搞搞分佈式的代碼也能夠
*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
二.epoll反應堆模型
1.傳統的epoll服務器模型
監聽可讀事件(ET) ⇒ 數據到來 ⇒ 觸發讀事件 ⇒
epoll_wait()返回 ⇒ read消息 ⇒ write回射信息 ⇒ 繼續epoll_wait()
⇒ 直到程序中止前都是這麼循環
2.epoll反應堆服務器模型
監聽可讀事件(ET) ⇒ 數據到來 ⇒ 觸發讀事件 ⇒
epoll_wait()返回 ⇒
read完數據; 節點下樹; 設置監聽寫事件和對應寫回調函數; 節點上樹(可讀事件回調函數內)
⇒ 監聽可寫事件(ET) ⇒ 對方可讀 ⇒ 觸發事件 ⇒
epoll_wait()返回 ⇒
write數據; 節點下樹; 設置監聽讀事件和對應可讀回調函數; 節點上樹(可寫事件回調函數內)
⇒ 直到程序中止前一直這麼交替循環
3.爲何epoll反應堆模型要這樣設計?
①如此頻繁的在紅黑樹上增添/刪除節點是否是浪費CPU資源?
答:epoll反應堆模型中,對於同一個socket而言,完成收發信息至少佔用兩個樹上的位置。而傳統的epoll服務器模型中,完成收發信息只須要一個樹上位置。任何一種設計方式都會浪費CPU資源,關鍵看浪費的值不值,此處的耗費可否換來更大的收益是決定是否浪費的標準。
②爲何要可讀之後設置可寫?而後一直交替?
服務器向客戶端write數據,並不必定能write成功,緣由有二
(1) 滑動窗口機制
服務器向客戶端write數據,假設恰好此時客戶端的接收滑動窗口滿,將致使當前服務器將阻塞在send函數處,致使服務器程序阻塞。
解決方案:設置可寫事件,當客戶端的接收緩衝區有空閒時,將致使該socket可寫,在可寫回調函數中調用send函數
(2) SIGPIPE信號
客戶端send完數據後,忽然因爲異常中止,這將致使一個FIN發送給服務器。若是服務器不設置可寫事件監聽,那麼服務器在read完數據後,直接向沒有讀端的套接字中寫入數據,TCP協議棧將會給服務器發送RST分節+SIGPIPE信號,致使服務器進程終止。
三.epoll反應堆模型代碼
Server
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define MAX_EVENTS 1024
#define SERVER_PORT 8888
struct my_events {
int m_fd; //監聽的文件描述符
int m_event; //監聽的事件
void *m_arg; //泛型參數
void (*call_back)(int fd, int event, void *arg); //回調函數
char m_buf[BUFSIZ];
int m_buf_len;
int m_status; //是否在紅黑樹上, 1->在, 0->不在
time_t m_lasttime; //最後放入紅黑樹的時間
};
int ep_fd; //紅黑樹根
struct my_events ep_events[MAX_EVENTS];
/*初始化監聽socket*/
void initlistensocket(int ep_fd, unsigned short port);
/*將結構體成員變量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int fd, int event, void *arg), void *event_arg);
/*向紅黑樹添加 文件描述符和對應的結構體*/
void eventadd(int ep_fd, int event, struct my_events *my_ev);
/*從紅黑樹上刪除 文件描述符和對應的結構體*/
void eventdel(int ep_fd, struct my_events *ev);
/*發送數據*/
void senddata(int client_fd, int event, void *arg);
/*接收數據*/
void recvdata(int client_fd, int event, void *arg);
/*回調函數: 接收鏈接*/
void acceptconnect(int listen_fd, int event, void *arg);
int main(void)
{
unsigned short port = SERVER_PORT;
ep_fd = epoll_create(MAX_EVENTS); //建立紅黑樹,返回給全局變量ep_fd;
if (ep_fd <= 0)
printf("create ep_fd in %s error: %s \n", __func__, strerror(errno));
/*初始化監聽socket*/
initlistensocket(ep_fd, port);
int checkpos = 0;
int i;
struct epoll_event events[MAX_EVENTS]; //epoll_wait的傳出參數(數組:保存就緒事件的文件描述符)
while (1)
{
/*超時驗證,每次測試100個鏈接,60s內沒有和服務器通訊則關閉客戶端鏈接*/
long now = time(NULL); //當前時間
for (i=0; i<100; i++,checkpos++) //一次循環檢測100個,使用checkpos控制檢測對象
{
if (checkpos == MAX_EVENTS-1)
checkpos = 0;
if (ep_events[i].m_status != 1) //不在紅黑樹上
continue;
long spell_time = now - ep_events[i].m_lasttime; //客戶端不活躍的時間
if (spell_time >= 60) //若是時間超過60s
{
printf("[fd= %d] timeout \n", ep_events[i].m_fd);
close(ep_events[i].m_fd); //關閉與客戶端鏈接
eventdel(ep_fd, &ep_events[i]); //將客戶端從紅黑樹摘下
}
}
/*監聽紅黑樹,將知足條件的文件描述符加至ep_events數組*/
int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); //1秒沒事件知足則返回0
if (n_ready < 0)
{
printf("epoll_wait error, exit \n");
break;
}
for (i=0; i<n_ready; i++)
{
//將傳出參數events[i].data的ptr賦值給"自定義結構體ev指針"
struct my_events *ev = (struct my_events *)(events[i].data.ptr);
if ((events[i].events & EPOLLIN) && (ev->m_event & EPOLLIN)) //讀就緒事件
ev->call_back(ev->m_fd, events[i].events, ev->m_arg);
if ((events[i].events & EPOLLOUT) && (ev->m_event & EPOLLOUT)) //寫就緒事件
ev->call_back(ev->m_fd, events[i].events, ev->m_arg);
}
}
return 0;
}
/*初始化監聽socket*/
void initlistensocket(int ep_fd, unsigned short port)
{
int listen_fd;
struct sockaddr_in listen_socket_addr;
printf("\n initlistensocket() \n");
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//端口複用
/*申請一個socket*/
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(listen_fd, F_SETFL, O_NONBLOCK); //將socket設置爲非阻塞模式,好處自行百度
/*綁定前初始化*/
bzero(&listen_socket_addr, sizeof(listen_socket_addr));
listen_socket_addr.sin_family = AF_INET;
listen_socket_addr.sin_port = htons(port);
listen_socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*綁定*/
bind(listen_fd, (struct sockaddr *)&listen_socket_addr, sizeof(listen_socket_addr));
/*設置監聽上限*/
listen(listen_fd, 128);
/*將listen_fd初始化*/
eventset(&ep_events[MAX_EVENTS-1], listen_fd, acceptconnect, &ep_events[MAX_EVENTS-1]);
/*將listen_fd掛上紅黑樹*/
eventadd(ep_fd, EPOLLIN, &ep_events[MAX_EVENTS-1]);
return ;
}
/*將結構體成員變量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int, int, void *), void *event_arg)
{
my_ev->m_fd = fd;
my_ev->m_event = 0; //開始不知道關注的是什麼事件,所以設置爲0
my_ev->m_arg = event_arg;
my_ev->call_back = call_back;
my_ev->m_status = 0; //0表示沒有在紅黑樹上
my_ev->m_lasttime = time(NULL);//調用eventset函數的絕對時間
return ;
}
/*向紅黑樹添加文件描述符和對應的結構體*/
void eventadd(int ep_fd, int event, struct my_events *my_ev)
{
int op;
struct epoll_event epv;
epv.data.ptr = my_ev;
epv.events = my_ev->m_event = event; //EPOLLIN或EPOLLOUT
if (my_ev->m_status == 0)
{
op = EPOLL_CTL_ADD;
}
else
{
printf("\n add error: already on tree \n");
return ;
}
if (epoll_ctl(ep_fd, op, my_ev->m_fd, &epv) < 0) //實際添加/修改
{
printf("\n event add/mod false [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);
}
else
{
my_ev->m_status = 1;
printf("\n event add ok [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);
}
return ;
}
/*從紅黑樹上刪除 文件描述符和對應的結構體*/
void eventdel(int ep_fd, struct my_events *ev)
{
if(ev->m_status != 1)
return ;
epoll_ctl(ep_fd, EPOLL_CTL_DEL, ev->m_fd, NULL);
ev->m_status = 0;
return ;
}
/*回調函數: 接收鏈接*/
void acceptconnect(int listen_fd, int event, void *arg)
{
int connect_fd;
int i;
int flag=0;
char str[BUFSIZ];
struct sockaddr_in connect_socket_addr;
socklen_t connect_socket_len;
if ( (connect_fd=accept(listen_fd, (struct sockaddr *)&connect_socket_addr, &connect_socket_len)) <0 )
{
if (errno != EAGAIN && errno != EINTR)
{/*暫時不處理*/}
printf("\n %s: accept, %s \n", __func__, strerror(errno));
return ;
}
do
{
for(i=0; i<MAX_EVENTS; i++) //從全局數組ep_events中找一個空閒位置i(相似於select中找值爲-1的位置)
if(ep_events[i].m_status == 0)
break;
if(i >= MAX_EVENTS)
{
printf("\n %s : max connect [%d] \n", __func__, MAX_EVENTS);
break;
}
/* 設置非阻塞 */
if((flag = fcntl(connect_fd, F_SETFL, O_NONBLOCK)) <0)
{
printf("\n %s: fcntl nonblocking false, %s \n", __func__, strerror(errno));
break;
}
eventset(&ep_events[i], connect_fd, recvdata, &ep_events[i]);
eventadd(ep_fd, EPOLLIN, &ep_events[i]);
}while(0);
printf("\n new connection [%s:%d] [time:%ld] [pos:%d] \n", inet_ntop(AF_INET, &connect_socket_addr.sin_addr, str, sizeof(str)),
ntohs(connect_socket_addr.sin_port), ep_events[i].m_lasttime, i);
return ;
}
/*接收數據*/
void recvdata(int client_fd, int event, void *arg)
{
int len;
struct my_events *ev = (struct my_events *)arg;
len = recv(client_fd, ev->m_buf, sizeof(ev->m_buf), 0);
//1.將ep_fd從紅黑樹拿下
eventdel(ep_fd, ev);
if (len >0)
{
ev->m_buf_len = len;
ev->m_buf[len] = '\0'; //手動添加結束標記
printf("\n Client[%d]: %s \n", client_fd, ev->m_buf);
eventset(ev, client_fd, senddata, ev); //2.設置client_fd對應的回調函數爲senddata
eventadd(ep_fd, EPOLLOUT, ev); //3.將ep_fd放上紅黑樹,監聽寫事件EPOLLOUT
}
else if (len == 0)
{
close(ev->m_fd);
eventdel(ep_fd, ev);
printf("\n [Client:%d] disconnection \n", ev->m_fd);
}
else
{
close(ev->m_fd);
eventdel(ep_fd, ev);
printf("\n error: [Client:%d] disconnection\n", ev->m_fd);
}
return ;
}
/*發送數據*/
void senddata(int client_fd, int event, void *arg)
{
int len;
struct my_events *ev = (struct my_events *)arg;
len = send(client_fd, ev->m_buf, ev->m_buf_len, 0); //回寫
if (len > 0)
{
printf("\n send[fd=%d], [len=%d] %s \n", client_fd, len, ev->m_buf);
eventdel(ep_fd, ev); //1.將ep_fd從紅黑樹拿下
eventset(ev, client_fd, recvdata, ev); //2.設置client_fd對應的回調函數爲recvdata
eventadd(ep_fd, EPOLLIN, ev); //3.將ep_fd放上紅黑樹,監聽讀事件EPOLLIN
}
else
{
close(ev->m_fd);
eventdel(ep_fd, ev);
printf("\n send[fd=%d] error \n", client_fd);
}
return ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_LINE (1024)
#define SERVER_PORT (7778)
void setnoblocking(int fd)
{
int opts=0;
opts=fcntl(fd,F_GETFL);
opts=opts|O_NONBLOCK;
fcntl(fd,F_SETFL);
}
int main(int argc,char* argv[])
{
int sockfd;
char recvbuf[MAX_LINE+1]={0};
struct sockaddr_in server_addr;
/*
if(argc!=2)
{
fprintf(stderr,"usage ./cli <SERVER_IP> \n");
exit(0);
}
*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
fprintf(stderr,"socket error");
exit(0);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
/*
if(inet_pton(AF_INET,argv[1],&server_addr.sin_addr)<=0)
{
fprintf(stderr,"inet_pton error for %s",argv[1]);
exit(0);
}
*/
if(connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)
{
perror("connect");
fprintf(stderr,"connect error\n");
exit(0);
}
setnoblocking(sockfd);
char input[100];
int n=0;
int count=0;
while(fgets(input,100,stdin)!=NULL)
{
printf("[send]:%s\n",input);
n=send(sockfd,input,strlen(input),0);
if(n<0)
{
perror("send");
}
n=0;
count=0;
while(1)
{
n=read(sockfd,recvbuf+count,MAX_LINE);
if(n==MAX_LINE)
{
count+=n;
continue;
}
else if(n<0)
{
perror("recv");
break;
}
else
{
count+=n;
recvbuf[count]='\0';
printf("[recv]:%s\n",recvbuf);
break;
}
}
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
———————————————— 版權聲明:本文爲CSDN博主「guojawee」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/weixin_36750623/article/details/83547803