2011-05-22 46 views
9

我正在編寫一個IRC bot在C中,並遇到了一個障礙。c recv()讀直到換行發生

在我的主要功能,我創建我的套接字和連接,所有的快樂的東西。然後我有一個(幾乎)無限循環來讀取從服務器發回的內容。然後我將所讀取的內容傳遞給幫助函數processLine(char *line) - 問題是,下面的代碼讀取直到我的緩衝區已滿 - 我希望它只能讀取文本,直到出現換行符(\ n)或回車符(\ r) (從而結束了該行)

while (buffer[0] && buffer[1]) { 
     for (i=0;i<BUFSIZE;i++) buffer[i]='\0'; 
     if (recv(sock, buffer, BUFSIZE, 0) == SOCKET_ERROR) 
      processError(); 

     processLine(buffer); 
    } 

什麼最終情況是,許多線路卡住都在一起,我不能正確處理線當這種情況發生。

如果你不熟悉IRC協議,簡要總結將是,當發送消息時,它往往是這樣的::[email protected] PRIVMSG #someChannel :The rest on from here is the message sent... 和登錄通知,例如,是這樣的::the.hostname.of.the.server ### bla some text bla以# ##是用於處理的代碼(?) - 即372是以下文本是「每日消息」的一部分的指示符。

當它們全部卡在一起時,我無法知道哪一行是什麼數字,因爲我無法找到某一行開始或結束的位置!

我非常感謝您的幫助!

P.S .:這是在linux上編譯/運行的,但我最終希望將它移植到windows上,所以我儘可能多地利用它,因爲我可以使用多平臺。

P.S.S:這是我的ProcessLine從()代碼:

void processLine(const char *line) { 
    char *buffer, *words[MAX_WORDS], *aPtr; 
    char response[100]; 
    int count = 0, i; 
    buffer = strdup(line); 

    printf("BLA %s", line); 

    while((aPtr = strsep(&buffer, " ")) && count < MAX_WORDS) 
     words[count++] = aPtr; 
     printf("DEBUG %s\n", words[1]); 
    if (strcmp(words[0], "PING") == 0) { 
     strcpy(response, "PONG "); 
     strcat(response, words[1]); 
     sendLine(NULL, response); /* This is a custom function, basically it's a send ALL function */ 
    } else if (strcmp(words[1], "376") == 0) { /* We got logged in, send login responses (i.e. channel joins) */ 
     sendLine(NULL, "JOIN #cbot"); 
    } 
} 

回答

11

通常的方法來處理這是recv到應用程序中的持久緩衝區,再拉一條線出來並進行處理。稍後,您可以再次調用recv之前處理緩衝區中的剩餘行。請記住,緩衝區中的最後一行可能只能部分接收;您必須通過重新輸入recv來完成該行處理此案。

下面是一個例子(沒有經過測試也將查找\n,不\r\n!):

#define BUFFER_SIZE 1024 
char inbuf[BUFFER_SIZE]; 
size_t inbuf_used = 0; 

/* Final \n is replaced with \0 before calling process_line */ 
void process_line(char *lineptr); 
void input_pump(int fd) { 
    size_t inbuf_remain = sizeof(inbuf) - inbuf_used; 
    if (inbuf_remain == 0) { 
    fprintf(stderr, "Line exceeded buffer length!\n"); 
    abort(); 
    } 

    ssize_t rv = recv(fd, (void*)&inbuf[inbuf_used], inbuf_remain, MSG_DONTWAIT); 
    if (rv == 0) { 
    fprintf(stderr, "Connection closed.\n"); 
    abort(); 
    } 
    if (rv < 0 && errno == EAGAIN) { 
    /* no data for now, call back when the socket is readable */ 
    return; 
    } 
    if (rv < 0) { 
    perror("Connection error"); 
    abort(); 
    } 
    inbuf_used += rv; 

    /* Scan for newlines in the line buffer; we're careful here to deal with embedded \0s 
    * an evil server may send, as well as only processing lines that are complete. 
    */ 
    char *line_start = inbuf; 
    char *line_end; 
    while ((line_end = (char*)memchr((void*)line_start, '\n', inbuf_used - (line_start - inbuf)))) 
    { 
    *line_end = 0; 
    process_line(line_start); 
    line_start = line_end + 1; 
    } 
    /* Shift buffer down so the unprocessed data is at the start */ 
    inbuf_used -= (line_start - inbuf); 
    memmove(innbuf, line_start, inbuf_used); 
} 
+0

看起來很簡單。但是,我將如何重新輸入recv()?我會傳遞一個字符指針到部分讀取文本的末尾,即如果recv()只讀取10個字符中的5個,而是將指針傳遞給第6個位置? – FurryHead 2011-05-22 20:45:24

+0

@FurryHead:增加了一個(未經測試的)示例 – bdonlan 2011-05-22 20:52:54

+2

哦,哇。我很久以前就放棄了這個項目,感覺大部分情況都在我的頭上(這是它的)。現在我終於回到了一個非常相似的項目(irc bot再次,但有點不同),我甚至沒有意識到這是我的線索通讀這一點。在過去的兩天裏,我一直把頭撞到桌子上,試圖實現這一點(幾乎和你寫的一樣),但奇怪的是,我最終只從一行中隨機選擇了一個角色。奇。無論如何,只是想再次感謝你!這非常有幫助! – FurryHead 2011-07-29 18:10:35

7

TCP不提供那種任何測序。正如@bdonlan已經說了,你應該實現這樣的:從插座

  • 不斷recv到緩衝區
  • 在每個recv,檢查是否收到字節包含\n
  • 如果\n使用一切達從緩衝區點(和清除)

我沒有這個感覺很好(我讀的地方,你不應該混合低級別的I/O 10 I/O),但您可能可以使用fdopen

所有你需要做的是

  • 使用fdopen(3)到您的插座與FILE *
  • 使用setvbuf關聯告訴標準輸入輸出,你希望它行緩衝(_IOLBF),而不是默認的塊-緩衝的。

在這一點上,你應該有效地將工作從你的手中移到stdio。然後你可以繼續在FILE *上使用fgets之類的東西。

+0

好主意,我試過了,效果很好。我確實有兩個問題:如何檢查Windows上的錯誤?通常情況下,我會使用WSAGetLastError()作爲Windows套接字使用,而不是錯誤...並且會fdopen()/ setvbuf()在Windows上工作? – FurryHead 2011-05-22 21:36:42

+0

(更新,當我嘗試使用它在linux上處理errno時,它給了我一個0的錯誤代碼 - 我還不知道對應的是什麼) – FurryHead 2011-05-22 21:40:15

+0

@FurryHead'setvbuf'是標準的; Windows有'_fdopen'。關於'errno'部分,當使用'stdio'檢查'ferror'錯誤時,'feof'。顯然,這比「recv」或「read」沒有提供更多的細節。該標準說,沒有函數應該把'errno'設置爲0,但我相信它意味着「成功」。所以,即使'recv'失敗,實際的fgets也會成功。 – cnicutar 2011-05-22 21:48:20