[英]“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上工作,為fputs
和fprintf
編寫自己的替換代碼。
如果有人需要它, 這里是代碼 。
顯然,“r +”(讀/寫)模式在這個實現中的套接字上不起作用,毫無疑問,因為底層代碼假設它必須尋求在讀寫之間切換。 這是stdio流的一般情況(你必須進行某種同步操作),因為在Dim Time中,實際的stdio實現每個流只有一個計數器,它是一個“剩下的字符數”的計數器通過getc
宏讀取流緩沖區“(在讀模式下)或”可以通過putc
宏安全地寫入流緩沖區的字符數(在寫入模式下)。對於單個計數器重新設置的那個,你必須做一個尋求型操作。
管道和套接字上不允許搜索(因為“文件偏移”在那里沒有意義)。
一種解決方案是不要用stdio包裝套接字。 另一個,可能更容易/更好的目的,是用一個,而不是一個,但兩個 stdio流包裝它:
FILE *in = fdopen(newsock, "r");
FILE *out = fdopen(newsock, "w");
這里還有另一個缺陷,因為當你去fclose
一個流時,會關閉另一個流的文件描述符。 要解決這個問題,你需要dup
一次套接字描述符(在上面的兩個調用中,無論哪一個都無關緊要)。
如果你打算在某些時候在套接字上使用select
或poll
或類似的東西,你通常應該選擇“不要用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.