簡體   English   中英

使用具有非空讀緩沖區的套接字流時出現“非法搜索”錯誤

[英]“Illegal seek” error when working with socket streams with non-empty read buffers

我目前正在使用<sys/socket.h>Linux x86_64上編寫服務器應用程序。 在通過accept()接受連接后,我使用fdopen()將檢索到的套接字包裝到FILE*流中。

寫入和讀取FILE*流通常可以很好地工作,但是當它寫入時,套接字變得不可用,同時它具有非空讀取緩沖區。

出於演示目的,我編寫了一些偵聽連接的代碼,然后使用fgetc()逐行讀取輸入到讀緩沖區。 如果該行太長而無法放入緩沖區,則它不會被完全讀取,而是在下一次迭代期間讀取。

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

FILE* listen_on_port(unsigned short port) {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in name;
        name.sin_family = AF_INET;
        name.sin_port = htons(port);
        name.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0)
                perror("bind failed");
        listen(sock, 5);
        int newsock = accept(sock, 0, 0);
        return fdopen(newsock, "r+");
}

int main(int argc, char** argv) {
        int bufsize = 8;
        char buf[9];
        buf[8] = 0; //ensure null termination

        int data;
        int size;

        //listen on the port specified in argv[1]
        FILE* sock = listen_on_port(atoi(argv[1]));
        puts("New connection incoming");

        while(1) {
                //read a single line
                for(size = 0; size < bufsize; size++) {
                        data = fgetc(sock);
                        if(data == EOF)
                                break;
                        if(data == '\n') {
                                buf[size] = 0;
                                break;
                        }
                        buf[size] = (char) data;
                }

                //check if the read failed due to an EOF
                if(data == EOF) {
                        perror("EOF: Connection reset by peer");
                        break;
                } else {
                        printf("Input line: '%s'\n", buf);
                }

                //try to write ack
                if(fputs("ack\n", sock) == EOF)
                        perror("sending 'ack' failed"); 

                //try to flush
                if(fflush(sock) == EOF)
                        perror("fflush failed");        
        }

        puts("Connection closed");
}

代碼應該在沒有任何特殊參數的gcc中編譯。 使用端口號作為參數運行它,並使用netcat在本地連接到它。

現在,如果你嘗試發送短於8個字符的字符串,這將運行完美。 但是如果發送包含10個以上字符的字符串,程序將失敗。 此示例輸入:

ab
cd
abcdefghij

將創建此輸出:

New connection incoming
Input line: 'ab'
Input line: 'cd'
Input line: 'abcdefgh'
fflush failed: Illegal seek
EOF: Connection reset by peer: Illegal seek
Connection closed

如您所見,(正確地)只讀取abcdefgh的前8個字符,但是當程序嘗試發送'ack'字符串(客戶端從不接收),然后刷新輸出緩沖區時,我們會收到Illegal seek錯誤,下一次調用fgetc()返回EOF。

如果fflush()部分被注釋掉,仍會出現相同的錯誤,但是

fflush failed: Illegal seek

服務器輸出中缺少行。

如果fputs(ack)部分被注釋掉,一切似乎都按預期工作,但是從gdb手動調用的perror()仍會報告“非法搜索”錯誤。

如果fputs(ack)fflush()都被注釋掉了,那么一切按預期工作。

不幸的是,我找不到任何好的文檔,也沒有關於這個問題的任何互聯網討論,所以非常感謝你的幫助。

編輯

我最終解決的解決方案是使用fdopen()FILE* ,因為似乎沒有干凈的方法將套接字fd轉換為可以在r+模式下可靠使用的FILE* 相反,我直接在socket fd上工作,為fputsfprintf編寫自己的替換代碼。

如果有人需要它, 這里是代碼

顯然,“r +”(讀/寫)模式在這個實現中的套接字上不起作用,毫無疑問,因為底層代碼假設它必須尋求在讀寫之間切換。 這是stdio流的一般情況(你必須進行某種同步操作),因為在Dim Time中,實際的stdio實現每個流只有一個計數器,它是一個“剩下的字符數”的計數器通過getc宏讀取流緩沖區“(在讀模式下)或”可以通過putc宏安全地寫入流緩沖區的字符數(在寫入模式下)。對於單個計數器重新設置的那個,你必須做一個尋求型操作。

管道和套接字上不允許搜索(因為“文件偏移”在那里沒有意義)。

一種解決方案是不要用stdio包裝套接字。 另一個,可能更容易/更好的目的,是用一個,而不是一個,但兩個 stdio流包裝它:

FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");

這里還有另一個缺陷,因為當你去fclose一個流時,會關閉另一個流的文件描述符。 要解決這個問題,你需要dup一次套接字描述符(在上面的兩個調用中,無論哪一個都無關緊要)。

如果你打算在某些時候在套接字上使用selectpoll或類似的東西,你通常應該選擇“不要用stdio包裝”解決方案,因為沒有一個很好的干凈便攜方式來跟蹤stdio緩沖。 (有特定於實現的方式)。

不要在網絡套接字上使用fflush() 它們是無緩沖的流。

此外,這段代碼:

//read a single line
for(size = 0; size < bufsize; size++) {
    data = fgetc(sock);
    if(data == EOF)
        break;
    if(data == '\n') {
        buf[size] = 0;
        break;
    }
    buf[size] = (char) data;
}

不讀一行。 它只讀取緩沖區大小,你定義為8.在你用fputs寫入流之前, sock仍然會有你接收的數據。 順便說一句,你可以用。替換整個塊

fgets(buf, bufsize, sock);

是的,您可以使用一個文件流來處理套接字,至少在Linux上是這樣。 但是你應該小心它:你必須只使用ferror()來測試錯誤。 我有一些代碼使用它,並在法國主要網站上的生產中完美運行。

如果您使用errno或perror(),您將捕獲流將遇到的任何內部錯誤,即使它想要隱藏它。 而“非法尋求”就是其中之一。

另外,要測試真實的EOF條件,你應該使用feof(),因為當返回true時,它與ferror()互斥,返回非零值。 這是因為,當使用fgetc()時,你沒有任何意義來區分錯誤和真實的EOF條件。 所以你應該更好地使用fgets()作為另一個用戶指出。

所以,你的測試:

if(data == EOF) {
    perror("EOF: Connection reset by peer");
    break;
} else {
    printf("Input line: '%s'\n", buf);
}

應該寫成:

int sock_error = ferror(sock);
if (sock_error) {
    fprintf(stderr, "Error while reading: %s", strerror(sock_error));
} else {
    printf("Input line: '%s'\n", buf);
}

試試這個 :

#define BUFSIZE 88

FILE* listen_on_port(unsigned short port) {
 ... 
}

int main(int argc, char** argv) {
    int bufsize = BUFSIZE;
    char buf[ BUFSIZE ];

暫無
暫無

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

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