2012-03-22 63 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