簡體   English   中英

如何使epoll在多個連接之間切換?

[英]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)在receiveread功能中,確保在獲取EWOULDBLOCK之后不要再次調用readreceive 返回以等待epoll命中。

3)不要試圖窺視數據或測量其中有多少數據。 請盡快閱讀。

4)在關閉epoll集之前,請先將其從epoll集中移除,除非您肯定沒有其他引用到基礎連接端點。

真的就是這么簡單。 如果您正確地完成了這四件事,那么您將不會有任何問題。 您很可能已經爛掉2

另外,在發送時如何處理“ EWOULDBLOCK”? 您的sendOut函數是什么樣的? (這樣做有很多正確的方法,但也有很多錯誤的方法。)

event.data.fd 您為什么要嘗試使用它? events[iter].data.fd是具有您要接收的值的events[iter].data.fd 您可能希望更明確地命名變量,以避免將來再出現此問題,這樣您就不會浪費每個人的時間。 顯然,這與epoll無關。

修改我的原始答案。

我看到了一些可疑的東西,並提出了一些建議。

  1. 當偵聽套接字發出信號時,代碼進入無限循環,直到接受失敗。 我想知道循環是否優先接受新連接而不是處理epoll事件。 也就是說,您始終准備好接受連接,並且永遠不會脫離內部while(1)循環。 不要循環接受。 相反,當添加到epoll時,使監聽套接字不觸發邊緣。 然后,一次只接受一個連接-這樣,在接受返回后將處理后續的epoll事件。 換句話說,將內部的“ while(1)”循環出去。

  2. 在您的accept調用返回一個有效的套接字之后(您已完成使其成為非阻塞狀態並通過邊緣觸發將其添加到epoll中),請繼續並在已接受的套接字上調用receive函數。 我假設您的接收函數可以處理EWOULDBLOCK和EAGAIN錯誤。 換句話說,對於邊緣觸發的套接字,不要假設您將獲得新套接字的EPOLLIN通知。 無論如何,只要嘗試接收即可。 如果沒有數據,則在數據到達后會收到EPOLLIN通知。

  3. 為什么不就sendOut功能收聽EPOLLOUT? sendOut是否將套接字改回阻塞狀態? 無論如何,當receive()返回成功時,請將套接字上的epoll偵聽器更改為EPOLLOUT,然后嘗試對sendOut函數進行機會性調用,就好像您剛剛獲得了EPOLLOUT通知一樣。

  4. 如果所有其他方法都失敗了,請考慮完全關閉邊沿觸發(EPOLLET)行為。 也許您的recevie函數沒有使用第一個EPOLLIN通知中的所有數據。

  5. 如果在添加新套接字時epoll_ctl失敗,則殺死整個應用程序似乎有些苛刻。 我只是關閉有問題的套接字,斷言,然后繼續。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM