【问题标题】:Read line by line from a socket buffer从套接字缓冲区逐行读取
【发布时间】:2012-03-22 18:58:59
【问题描述】:

我想编写一个函数,从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."。为什么会这样,我该如何解决?

【问题讨论】:

  • 如何获得Notable question 徽章 0 点赞哈哈。
  • 查看TLPI's readline 实现。

标签: c string sockets split


【解决方案1】:

一次读取一个字节是不行的,因为你进行了太多的系统调用 - 更好的是使用缓冲区,读取一个块并检查你是否得到\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;
}

【讨论】:

    【解决方案2】:

    试试这个实现:

    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);
    

    【讨论】:

    • 在我看来,这会在第一次出现“\r\n”而不是第一次出现“\r\n\r\n”时中断。如果我遗漏了什么,那么这对于缺少 cmets 来说过于聪明了。
    • 函数只读取1行和1行。它应该在遇到\n 字符时退出,并且它同时支持\r\n 和\n 样式的换行符(HTTP 说只使用\r\n,但一些第三方实现使用\n 代替) . HTTP 标头以空行终止。该函数返回返回行中的字符数,不包括换行符。代码循环调用函数,直到遇到长度为 0 的行。
    • 我已经添加了 cmets。您不会一起检查 \r\n\r\n,而是检查仅是 \r\n 的行。
    • 如果每次调用 read() 只读取 1 个字节,性能会怎样?是否在后台应用了任何优化,因为该函数会被非常频繁地调用?
    • 套接字有一个内部缓冲区,read() 从中读取。但是,如果您担心性能(并且您应该使用分析器来判断),您始终可以使用中间缓冲区到 read() 一次超过 1 个字节,扫描该缓冲区以获取所需字节并复制它们到输出缓冲区,只有在需要时才返回套接字获取更多字节。这将减少对read() 的调用次数,并从套接字中获取更多字节以保持其内部缓冲区滚动,从而减少传输阻塞的可能性。
    【解决方案3】:

    你让事情变得比他们需要的更困难。您真的不需要执行 strcats 来获取您在当前位置添加的每次读取时读取的单个字符。

    但是你的错误是例程一看到\n就返回,所以它返回的字符串永远不能包含第一个\n之后的任何内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-25
      • 2021-07-31
      • 2016-04-22
      • 2013-06-02
      • 1970-01-01
      • 2020-09-30
      • 1970-01-01
      相关资源
      最近更新 更多