姜鹏辉的个人博客 GreyNius

【C】C语言编写http服务器记录-socket开发篇

2019-11-07

GET请求实现

To be finish…

实现文件的下载

设置下载的响应头

Content-Type:application/octet-stream\r\n
Content-Disposition:attachment;filename=[文件名]\r\n

代码:

/*
Description:
    处理下载文件的请求/?download=[文件名]
Parameters:
    int client_sock [IN] 客户端的socket
    char *arg: [IN] 解析的参数
Return:
    NULL
*/
void response_download(int client_sock,char *arg)
{
    int fd;
    ssize_t size = -1;
    char buf[MAX_SIZE],header[MAX_SIZE];
    char file_name[NAME_LEN];
    if(sscanf(arg,"/?download=%s",file_name)==EOF)
    {
        //匹配失败!
        printf("error:%s\n",arg);
        construct_header(header,404,"text/html");
        write(client_sock,header,strlen(header));
        return;
    }
    fd = open(file_name,O_RDONLY);
    if(fd == -1)
    {
        construct_header(header,404,"text/html");
        write(client_sock,header,strlen(header));
        return;
    }
    //构建下载的相应头部
    printf("\tdownloading %s\n",file_name);
    construct_download_header(header,200,file_name); 
    write(client_sock,header,strlen(header));
    while(size)
    {
        //size代表读取的字节数
        size = read(fd,buf,MAX_SIZE);
        if(size > 0)
        {
            send(client_sock,buf,size,0);
        }
    }
    printf("\n");
}

Keep-Alive

tcp和http对比

HTTP的Keep-Alive与TCP的Keep Alive,有些不同,两者意图不一样。前者主要是TCP连接复用,避免建立过多的TCP连接。而TCP的Keep Alive的意图是在于保持TCP连接的存活,就是发送心跳包。隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。

关于tcp的keep-alive可以参考这篇文章https://blog.csdn.net/weicao1990/article/details/81740014
可以看到使用了setsockopt这个函数来规定Socket的keep-alive属性。

工作原理

写的非常好的一篇文章 HTTP Keep-Alive工作原理

HTTP分块传输

简介

分块传输编码(Chunked transfer encoding) 是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。分块传输编码在HTTP/1.1中提供。

通常,HTTP应答消息中发送的数据是整个发送的,Content-Length消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。

HTTP 1.1引入分块传输编码提供了以下几点好处:

  1. HTTP分块传输编码允许服务器为动态生成的内容维持HTTP持久连接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。[动态内容,content-length无法预知]
  2. 分块传输编码允许服务器在最后发送消息头字段。对于那些头字段值在内容被生成之前无法知道的情形非常重要,例如消息的内容要使用散列进行签名,散列的结果通过HTTP消息头字段进行传输。没有分块传输编码时,服务器必须缓冲内容直到完成后计算头字段的值并在发送内容前发送这些头字段的值。[散列签名,需缓冲完成才能计算]
  3. HTTP服务器有时使用压缩 (gzip或deflate)以缩短传输花费的时间。分块传输编码可以用来分隔压缩对象的多个部分。在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小。[gzip压缩,压缩与传输同时进行]

编码

在进行Chunked编码传输时,在回复消息的Headers有transfer-coding域值为chunked,表示将用chunked编码传输内容。即

Transfer-Encoding: chunked\r\n

下图为用wireshark抓取的一个使用了chunk的包,过滤规则为http contains "Transfer-Encoding: chunked",是从网易云音乐这个应用里捕获的包。

可以看到,每一个chunk都是以\r\n结尾的。 CRLF即回车换行(代表\r\n),对应的ASCII码值分别为13、10,换算成16进制是0d0a。

代码:

/*
Description:
    处理下载文件的请求/?download=[文件名]
    以chunk分块传输
Parameters:
    int client_sock [IN] 客户端的socket
    char *arg: [IN] 解析的参数
Return:
    NULL
*/
void response_download_chunk(int client_sock,char *arg)
{
    int fd;
    ssize_t size = -1;
    char buf[MAX_SIZE],header[MAX_SIZE],*chunk_head;
    char file_name[NAME_LEN];
    if(sscanf(arg,"/?download=%s",file_name)==EOF)
    {
        //匹配失败!
        printf("error:%s\n",arg);
        construct_header(header,404,"text/html");
        write(client_sock,header,strlen(header));
        return;
    }
    fd = open(file_name,O_RDONLY);
    if(fd == -1)
    {
        construct_header(header,404,"text/html");
        write(client_sock,header,strlen(header));
        return;
    }
    //构建下载的相应头部
    printf("\tdownloading %s\n",file_name);
    construct_download_header(header,200,file_name); 
    write(client_sock,header,strlen(header));
    while(size)
    {
        //size代表读取的字节数
        size = read(fd,buf,MAX_SIZE);
        chunk_head = (char *)malloc(MIN_SIZE*sizeof(char));
        sprintf(chunk_head,"%x\r\n",size);//需要转换为16进制
        send(client_sock,chunk_head,strlen(chunk_head),0);
        if(size > 0)
        {
            send(client_sock,buf,size,0);
        }
        send(client_sock,CRLF,strlen(CRLF),0);
        free(chunk_head);
    }
    send(client_sock,CRLF,strlen(CRLF),0);
    printf("\n");
}

实现:

参考

Linux实现一个简单的HTTP服务器

HTTP协议之chunk编码(分块传输编码)


Similar Posts

Comments