[英]How do I make epoll switch between multiple connections?
我以我認為是TCP套接字的典型方式使用epoll(主要基於此示例 ,但略微適用於C ++); 一個綁定到該端口的主偵聽套接字,並且在准備好使用recv()時,還會添加每個新的連接套接字(來自accept())以發出警報。 我創建了一個測試腳本,基本上可以通過連接和發送/接收對其進行錘擊。 連接任何單個客戶端時,它將無休止地,無休止地工作。
但是,添加第二個同時測試客戶端將導致其中一個掛起並失敗。 經過幾天的調試,我最終決定只將它正在使用的套接字ID吐出到文件中,而我對發現的內容感到困惑。
當一個腳本啟動時,我只得到一個流,在這種情況下為6。但是,當第二個腳本啟動時,我得到了7的流。 只有 7。並且它保持為7, 完全與第二個客戶端進行通信。忽略第一個,直到第一個達到超時並關閉。 (然后,當客戶端2重新連接時,它將獲得ID6。)
值得注意的是,此測試腳本不使用持久連接,它會在來回一些消息后斷開連接並重新連接(以進行更精確的模擬)。 但是即使這樣,客戶端1仍會被忽略。 如果我將超時設置得足夠高,以至於客戶端2實際上有時間退出,它仍然不會在客戶端1上恢復,因為它正在等待的一切都會丟失。
這是正常的行為,以便epoll(或一般的套接字)在出現新任務時完全放棄先前的任務嗎? 我必須指定一些選項嗎?
編輯:這是我可以顯示的代碼; 我不一定期望“這是您做錯了”,更多的是“這些東西會破壞/修復類似的情況”。
#define EVENTMODE (EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLHUP)
#define ERRCHECK (EPOLLERR | EPOLLHUP | EPOLLRDHUP)
//Setup event buffer:
struct epoll_event* events = (epoll_event*)calloc(maxEventCount, sizeof(event));
//Setup done, main processing loop:
int iter, eventCount;
while (1) {
//Wait for events indefinitely:
eventCount = epoll_wait(pollID, events, maxEventCount, -1);
if (eventCount < 0) {
syslog(LOG_ERR, "Poll checking error, continuing...");
continue;
}
for (iter = 0; iter<eventCount; ++iter) {
int currFD = events[iter].data.fd;
cout << "Working with " << events[iter].data.fd << endl;
if (events[iter].events & ERRCHECK) {
//Error or hangup:
cout << "Closing " << events[iter].data.fd << endl;
close(events[iter].data.fd);
continue;
} else if (!(events[iter].events & EPOLLIN)) {
//Data not really ready?
cout << "Not ready on " << events[iter].data.fd << endl;
continue;
} else if (events[iter].data.fd == socketID) {
//Event on the listening socket, incoming connections:
cout << "Connecting on " << events[iter].data.fd << endl;
//Set up accepting socket descriptor:
int acceptID = accept(socketID, NULL, NULL);
if (acceptID == -1) {
//Error:
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
//NOT just letting us know there's nothing new:
syslog(LOG_ERR, "Can't accept on socket: %s", strerror(errno));
}
continue;
}
//Set non-blocking:
if (setNonBlocking(acceptID) < 0) {
//Error:
syslog(LOG_ERR, "Can't set accepting socket non-blocking: %s", strerror(errno));
close(acceptID);
continue;
}
cout << "Listening on " << acceptID << endl;
//Add event listener:
event.data.fd = acceptID;
event.events = EVENTMODE;
if (epoll_ctl(pollID, EPOLL_CTL_ADD, acceptID, &event) < 0) {
//Error adding event:
syslog(LOG_ERR, "Can't edit epoll: %s", strerror(errno));
close(acceptID);
continue;
}
} else {
//Data on accepting socket waiting to be read:
cout << "Receive attempt on " << event.data.fd << endl;
cout << "Supposed to be " << currFD << endl;
if (receive(event.data.fd) == false) {
sendOut(event.data.fd, streamFalse);
}
}
}
}
編輯:該代碼已被修改,並且消除邊緣觸發將確實阻止epoll鎖定到一個客戶端。 客戶仍然無法接收數據,這仍然存在問題; 正在調試,以查看是否是同一問題或其他問題。
編輯:似乎是在不同的西裝相同的錯誤。 它確實嘗試在第二個套接字上接收,但是進一步的日志記錄報告它實際上幾乎每次都碰到一個EWOULDBLOCK。 有趣的是,日志報告的活動遠遠超出了保證的數量-超過150,000行,而我希望達到60,000行。 刪除所有“ Would block”行將其減少到大約我所希望的數量……並且瞧瞧,結果行創建了完全相同的模式。 將邊沿觸發放回原處,將阻止可能的行為,顯然可以防止它在沒有明顯原因的情況下盡可能快地旋轉車輪。 仍然不能解決原始問題。
編輯:只是為了掩蓋我的想法,我認為我將在發送方進行更多調試,因為掛起的客戶端顯然正在等待一條永遠不會收到的消息。 但是,我可以確認服務器為它處理的每個請求都發送了響應。 掛起的客戶的請求完全丟失了,因此從未響應。
我還確保我的接收循環一直讀到它實際達到EWOULDBLOCK為止(這通常是不必要的,因為我的消息頭的前兩個字節包含消息的大小),但是它沒有任何改變。
“其他編輯:我可能應該澄清一下,該系統使用了請求/答復格式,並且接收,處理和發送都一口氣完成了。 您可能會猜到,這需要讀取接收緩沖區直到其為空,這是邊緣觸發模式的主要要求。 如果接收到的消息不完整(永遠不會發生),則服務器基本上將false返回給客戶端,從技術上講,這是一個錯誤,仍將允許客戶端繼續另一個請求。
調試已確認要掛起的客戶端將發出請求,並等待響應,但是該請求永遠不會觸發epoll中的任何內容-在連接第二個客戶端之后,它會完全忽略第一個客戶端。
我也刪除了接受后立即接收的嘗試; 在十萬次嘗試中,它一次都沒有准備好。
更多編輯:很好,很好-如果有一件事可以促使我完成任意任務,那就是對我能力的質疑。 因此,在這里,這里的函數必定會出錯:
bool receive(int socketID)
{
short recLen = 0;
char buff[BUFFERSIZE];
FixedByteStream received;
short fullSize = 0;
short diff = 0;
short iter = 0;
short recSoFar = 0;
//Loop through received buffer:
while ((recLen = read(socketID, buff, BUFFERSIZE)) > 0) {
cout << "Receiving on " << socketID << endl;
if (fullSize == 0) {
//We don't know the size yet, that's the first two bytes:
fullSize = ntohs(*(uint16_t*)&buff[0]);
if (fullSize < 4 || recLen < 4) {
//Something went wrong:
syslog(LOG_ERR, "Received nothing.");
return false;
}
received = FixedByteStream(fullSize);
}
diff = fullSize - recSoFar;
if (diff > recLen) {
//More than received bytes left, get them all:
for (iter=0; iter<recLen; ++iter) {
received[recSoFar++] = buff[iter];
}
} else {
//Less than or equal to received bytes left, get only what we need:
for (iter=0; iter<diff; ++iter) {
received[recSoFar++] = buff[iter];
}
}
}
if (recLen < 0 && errno == EWOULDBLOCK) {
cout << "Would block on " << socketID << endl;
}
if (recLen < 0 && errno != EWOULDBLOCK) {
//Had an error:
cout << "Error on " << socketID << endl;
syslog(LOG_ERR, "Connection receive error: %s", strerror(errno));
return false;
} else if (recLen == 0) {
//Nothing received at all?
cout << "Received nothing on " << socketID << endl;
return true;
}
if (fullSize == 0) {
return true;
}
//Store response, since it needs to be passed as a reference:
FixedByteStream response = process(received);
//Send response:
sendOut(socketID, response);
return true;
}
如您所見,它在遇到錯誤后不會循環。 我可能不太使用C ++,但是我已經進行了足夠長的編碼,可以在尋求幫助之前檢查此類錯誤。
bool sendOut(int socketID, FixedByteStream &output)
{
cout << "Sending on " << socketID << endl;
//Send to socket:
if (write(socketID, (char*)output, output.getLength()) < 0) {
syslog(LOG_ERR, "Connection send error: %s", strerror(errno));
return false;
}
return true;
}
如果是EWOULDBLOCK呢? 就像我的主板融化一樣-我會修復它。 但這還沒有發生,所以我不會修復它,只是確保我知道是否需要修復。
不,process()對套接字不做任何事情 ,它僅接受並返回固定長度的char數組。 同樣,此程序可以與一個客戶端完美配合,而不能與兩個或多個客戶端完美配合。
最后編輯:經過更多調試后,我找到了問題的根源。 我會繼續回答自己。
1)請勿使用EPOLLET。 它的方式更加復雜。
2)在receive
或read
功能中,確保在獲取EWOULDBLOCK之后不要再次調用read
或receive
。 返回以等待epoll
命中。
3)不要試圖窺視數據或測量其中有多少數據。 請盡快閱讀。
4)在關閉epoll
集之前,請先將其從epoll
集中移除,除非您肯定沒有其他引用到基礎連接端點。
真的就是這么簡單。 如果您正確地完成了這四件事,那么您將不會有任何問題。 您很可能已經爛掉2
。
另外,在發送時如何處理“ EWOULDBLOCK”? 您的sendOut
函數是什么樣的? (這樣做有很多正確的方法,但也有很多錯誤的方法。)
event.data.fd
? 您為什么要嘗試使用它? events[iter].data.fd
是具有您要接收的值的events[iter].data.fd
。 您可能希望更明確地命名變量,以避免將來再出現此問題,這樣您就不會浪費每個人的時間。 顯然,這與epoll無關。
修改我的原始答案。
我看到了一些可疑的東西,並提出了一些建議。
當偵聽套接字發出信號時,代碼進入無限循環,直到接受失敗。 我想知道循環是否優先接受新連接而不是處理epoll事件。 也就是說,您始終准備好接受連接,並且永遠不會脫離內部while(1)循環。 不要循環接受。 相反,當添加到epoll時,使監聽套接字不觸發邊緣。 然后,一次只接受一個連接-這樣,在接受返回后將處理后續的epoll事件。 換句話說,將內部的“ while(1)”循環出去。
在您的accept調用返回一個有效的套接字之后(您已完成使其成為非阻塞狀態並通過邊緣觸發將其添加到epoll中),請繼續並在已接受的套接字上調用receive函數。 我假設您的接收函數可以處理EWOULDBLOCK和EAGAIN錯誤。 換句話說,對於邊緣觸發的套接字,不要假設您將獲得新套接字的EPOLLIN通知。 無論如何,只要嘗試接收即可。 如果沒有數據,則在數據到達后會收到EPOLLIN通知。
為什么不就sendOut功能收聽EPOLLOUT? sendOut是否將套接字改回阻塞狀態? 無論如何,當receive()返回成功時,請將套接字上的epoll偵聽器更改為EPOLLOUT,然后嘗試對sendOut函數進行機會性調用,就好像您剛剛獲得了EPOLLOUT通知一樣。
如果所有其他方法都失敗了,請考慮完全關閉邊沿觸發(EPOLLET)行為。 也許您的recevie函數沒有使用第一個EPOLLIN通知中的所有數據。
如果在添加新套接字時epoll_ctl失敗,則殺死整個應用程序似乎有些苛刻。 我只是關閉有問題的套接字,斷言,然后繼續。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.