2012-03-22 57 views
2

那麼,我需要幫助:我想寫一個函數,從一個套接字緩衝區中逐行讀取,從read()函數的第三個參數中獲取第unistd.h表頭。從套接字緩衝區逐行讀取

我已經寫了這個:

int sgetline(int fd, char ** out) 
{ 
    int buf_size = 128; 
    int bytesloaded = 0; 
    char buf[2]; 
    char * buffer = malloc(buf_size); 
    char * newbuf; 
    int size = 0; 

    assert(NULL != buffer); 

    while(read(fd, buf, 1) > 0) 
    { 
     strcat(buffer, buf); 
     buf[1] = '\0'; 
     bytesloaded += strlen(buf); 
     size = size + buf_size; 

     if(buf[0] == '\n') 
     { 
      *out = buffer; 
      return bytesloaded; 
     } 

     if(bytesloaded >= size) 
     { 
      size = size + buf_size; 
      newbuf = realloc(buffer, size); 

      if(NULL != newbuf) 
      { 
       buffer = newbuf; 
      } 
      else 
      { 
       printf("sgetline() allocation failed!\n"); 
       exit(1); 
      } 
     } 
    } 

    *out = buffer; 
    return bytesloaded; 
} 

但我有一些問題,與此功能,例如,如果輸入的是一樣的東西:

HTTP/1.1 301 Moved Permanently\r\n 
Cache-Control:no-cache\r\n 
Content-Length:0\r\n 
Location\r\nhttp://bing.com/\r\n 
\r\n\r\n 

和我做

int sockfd = socket(...); 
//.... 
char* tbuf; 
while(sgetline(sockfd, &tbuf) > 0) 
{ 
    if(strcmp(tbuf,"\r\n\r\n") == 0) 
    { 
     printf("End of Headers detected.\n"); 
    } 
} 

上面的C應用程序不輸出"End of Header detected."。爲什麼?如何解決這個問題?非常感謝提高這個功能的提示。提前致謝。 :)

+0

怎樣才能得到**值得注意的問題徽章** ** upvotes哈哈。 – Jack 2013-05-08 00:52:58

+0

請參閱[TLPI的readline](http://man7.org/tlpi/code/online/dist/sockets/read_line.c.html)實現。 – mmoya 2013-09-28 16:02:28

回答

2

你讓自己比自己所需要的更加困難。你實際上不需要用strcats來獲取在當前位置添加的每次讀取中讀取的單個字符。

但是你的錯誤是程序在看到一個\ n後立即返回,所以它返回的字符串在第一個\ n之後永遠不會包含任何東西。

3

試試這個實現,而不是:

int sgetline(int fd, char ** out) 
{ 
    int buf_size = 0; 
    int in_buf = 0; 
    int ret; 
    char ch; 
    char * buffer = NULL; 
    char * new_buffer; 

    do 
    { 
     // read a single byte 
     ret = read(fd, &ch, 1); 
     if (ret < 1) 
     { 
      // error or disconnect 
      free(buffer); 
      return -1; 
     } 

     // has end of line been reached? 
     if (ch == '\n') 
      break; // yes 

     // is more memory needed? 
     if ((buf_size == 0) || (in_buf == buf_size)) 
     { 
      buf_size += 128; 
      new_buffer = realloc(buffer, buf_size); 

      if (!new_buffer) 
      { 
       free(buffer); 
       return -1; 
      } 

      buffer = new_buffer; 
     } 

     buffer[in_buf] = ch; 
     ++in_buf; 
    } 
    while (true); 

    // if the line was terminated by "\r\n", ignore the 
    // "\r". the "\n" is not in the buffer 
    if ((in_buf > 0) && (buffer[in_buf-1] == '\r')) 
     --in_buf; 

    // is more memory needed? 
    if ((buf_size == 0) || (in_buf == buf_size)) 
    { 
     ++buf_size; 
     new_buffer = realloc(buffer, buf_size); 

     if (!new_buffer) 
     { 
      free(buffer); 
      return -1; 
     } 

     buffer = new_buffer; 
    } 

    // add a null terminator 
    buffer[in_buf] = '\0'; 

    *out = buffer; // complete line 

    return in_buf; // number of chars in the line, not counting the line break and null terminator 
} 

int sockfd = socket(...);   
//....   
char* tbuf;   
int ret; 

// keep reading until end of headers is detected. 
// headers are terminated by a 0-length line 
do 
{ 
    // read a single line 
    ret = sgetline(sockfd, &tbuf); 
    if (ret < 0) 
     break; // error/disconnect 

    // is it a 0-length line? 
    if (ret == 0) 
    { 
     printf("End of Headers detected.\n");   
     free(tbuf); 
     break; 
    } 

    // tbuf contains a header line, use as needed... 

    free(tbuf); 
} 
while (true); 
+0

在我看來,這樣會在第一次出現「\ r \ n」而不是第一次出現「\ r \ n \ r \ n」時破裂。如果我錯過了一些東西,那麼對於缺乏評論這是非常聰明的。 – DRVic 2012-03-23 01:18:10

+0

該功能只讀取1行和1行。它應該在遇到'\ n'字符時退出,它支持'\ r \ n'和'\ n'樣式換行符(HTTP表示只使用'\ r \ n',第三方實現使用'\ n'代替)。 HTTP標頭以空行結束。該函數返回返回行中的字符數,不包括換行符。代碼循環調用函數,直到遇到0長度的行。 – 2012-03-23 01:31:03

+0

我已添加評論。你不一起檢查'\ r \ n \ r \ n',你自己檢查一個只是'\ r \ n'的行。 – 2012-03-23 01:38:29

3

這也不行,在一次讀取一個字節,因爲你做了太多的系統調用 - 更好的方法是使用一個緩衝,閱讀大塊並檢查是否有\ n。獲取一行後,剩餘的字節數將保留在緩衝區中,因此不能將read/recv與read_line混合使用。使用這種緩衝區讀取n個字節的另一個版本可以寫入...

我的版本讀取一行,以及一個小例子來使用它。

#include <stdio.h> 
#include <errno.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <unistd.h> 
#include <netinet/in.h> 
#include <netdb.h> 
#include <arpa/inet.h> 
#include <string.h> 

#define CBSIZE 2048 

typedef struct cbuf { 
    char buf[CBSIZE]; 
    int fd; 
    unsigned int rpos, wpos; 
} cbuf_t; 


int read_line(cbuf_t *cbuf, char *dst, unsigned int size) 
{ 
    unsigned int i = 0; 
    ssize_t n; 
    while (i < size) { 
     if (cbuf->rpos == cbuf->wpos) { 
      size_t wpos = cbuf->wpos % CBSIZE; 
      //if ((n = read(cbuf->fd, cbuf->buf + wpos, (CBSIZE - wpos))) < 0) { 
      if((n = recv(cbuf->fd, cbuf->buf + wpos, (CBSIZE - wpos), 0)) < 0) { 
       if (errno == EINTR) 
        continue; 
       return -1; 
      } else if (n == 0) 
       return 0; 
      cbuf->wpos += n; 
     } 
     dst[i++] = cbuf->buf[cbuf->rpos++ % CBSIZE]; 
     if (dst[i - 1] == '\n') 
      break; 
    } 
    if(i == size) { 
     fprintf(stderr, "line too large: %d %d\n", i, size); 
     return -1; 
    } 

    dst[i] = 0; 
    return i; 
} 

int main() 
{ 
    cbuf_t *cbuf; 
    char buf[512]; 
    struct sockaddr_in saddr; 
    struct hostent *h; 
    char *ip; 
    char host[] = "www.google.com"; 

    if(!(h = gethostbyname(host))) { 
     perror("gethostbyname"); 
     return NULL; 
    } 
    ip = inet_ntoa(*(struct in_addr*)h->h_addr); 

    cbuf = calloc(1, sizeof(*cbuf)); 

    fprintf(stdout, "Connecting to ip: %s\n", ip); 
    if((cbuf->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 
     perror("socket"); 
     return 1; 
    } 
    memset(&saddr, 0, sizeof(saddr)); 
    saddr.sin_family = AF_INET; 
    saddr.sin_port = htons(80); 
    inet_aton(ip, &saddr.sin_addr); 
    if(connect(cbuf->fd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) { 
     perror("connect"); 
     return 1; 
    } 

    snprintf(buf, sizeof(buf), "GET/HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", host); 
    write(cbuf->fd, buf, strlen(buf)); 
    while(read_line(cbuf, buf, sizeof(buf)) > 0) { 
     // if it's an empty \r\n on a line, header ends // 
     if(buf[0]=='\r' && buf[1] == '\n') { 
      printf("------------------------\n"); 
     } 
     printf("[%s]", buf); 
    } 
    close(cbuf->fd); 
    free(cbuf); 
    return 0; 
} 
+0

請注意,一次讀取一個字節非常常見 – JSON 2017-06-24 04:24:17