libcurl实现http客户端POST长连接–keepalive

libcurl实现http客户端POST长连接–keepalive

http每次连接性能开销非常大,对于服务端可以快速返回的请求,长连接可以提高几十倍甚至上百倍的处理性能

此项目需要搭建一个支持POST的http测试服务器,参看往期文章:https://www.madbull.site/?p=565。按照步骤就可以搭建起来测试服务器。

对于不需要高性能处理的服务,也就不需要长连接,可以参看往期文章:https://www.madbull.site/?p=939,对于资源的申请释放比较方便,也不用考虑并发问题。

注意:此次示例如果用在并发中使用,curl句柄是临界资源,需要做好互斥访问,或者为每个线程申请不同的curl句柄,也可以,需要灵活处理。

头文件 httpclient.h

#ifndef __HTTP_CLIENT_H__
#define __HTTP_CLIENT_H__
#include <stdio.h>
#include <curl/curl.h>

typedef struct http_resp_data_t {
    char * data;
    size_t size;
} http_resp_data;

CURL * create_keep_alive_post_curl() ;

int http_post_by_json(CURL *curl, const char * proto, const char* host, const char* method, const char* data, size_t size, http_resp_data* user_data) ;

#endif

代码文件 httpclient.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <curl/easy.h>

#include "httpclient.h"


/*
 * post请求的返回结果回调函数
 * 把content的数据copy到userdata给定的空间
 * 注意:需要在外部释放申请的 userdata.data 空间
 */
size_t post_callback(void* contents, size_t size, size_t nmemb, void* userdata) {
    // 判断contents是否返回了空值
    if (contents == NULL) {
        return 0;
    }

    // 一次回调返回的数据量
    size_t realsize = size * nmemb;  
    if (userdata == NULL) {
        return realsize ;
    }

    // 类型转换
    http_resp_data * resp_data = (http_resp_data *) userdata;

    resp_data->data= (char*)calloc(1, realsize );
    if (resp_data->data== NULL) {
        printf("not enough memory (realloc returned NULL)\n");
        return 0;
    }
    memcpy(resp_data->data, contents, realsize);
    resp_data->size = realsize;

    return realsize;  //必须返回真实的数据
}


#define MAX_URL_LEN 256

/*
 * 创建一个长连接的curl句柄
 */
CURL * create_keep_alive_post_curl() {
    // 1、初始化
    CURL* curl = curl_easy_init();
    if (curl == NULL) {
        printf("curl easy init failed!\n");
        return NULL ;
    }

    // 2、设置参数
    // 2.1、设置使用POST方法(默认使用GET方法发送请求),
    curl_easy_setopt(curl, CURLOPT_POST, 1);
    // 返回完整的HTTP响应头
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);


    // 2.2、设置请求超时时间
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);

    // 2.3、允许重定向地址
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) ;

    // 2.4、设置 keep-alive 选项
    curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L) ;
    curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L) ;
    curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 2L) ;
    // curl_easy_setopt(curl, CURLOPT_TCP_KEEPCNT, 2L) ;

    return curl ;
}

/*
 * 示例 :
 * proto : http  or   https
 * host : 127.0.0.1:3456   host:port
 * method : /xx/yyy
 * data : {"aa":"aaaaaaaaaaa","bb","bbbbbbbbbbbbbbbb", .... }
 * size : data的字节数
 * user_data : 回调函数待处理的数据
 */
int http_post_by_json(CURL *curl, const char * proto, const char* host, const char* method, const char* data, size_t size, http_resp_data* user_data) {
    int ret = 0;

    // 1、拼写完整的URL
    char new_url[MAX_URL_LEN] = {0} ;
    if (method[0] == '/') {
        sprintf(new_url, "%s://%s%s", proto, host, method);
    } else {
        sprintf(new_url, "%s://%s/%s", proto, host, method);
    }

    // 2、设置header
    // 2.1、设置url地址
    curl_easy_setopt(curl, CURLOPT_URL, new_url);

    struct curl_slist* headers = NULL;
    // 2.2、设置 header.Host
    char host_header[MAX_URL_LEN] = {0};
    sprintf(host_header, "Host: %s", host);
    headers = curl_slist_append(headers, host_header);

    // 2.3、header.Content-Type
    headers = curl_slist_append(headers, "Content-Type: application/json") ;
    // 2.4、header.Accept 信息
    headers = curl_slist_append(headers, "Accept: */*") ;

    // 2.5、添加header列表
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);


    // 3、设置body
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, size);

    // 4、设置回调函数
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, post_callback);

    // 5、设置向回调函数传递的数据
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)user_data);

    // 6、发起请求
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        const char* error = curl_easy_strerror(res);
        printf("http post request failure, url=%s curl code=%d error=%s\n", new_url, res, error);
        ret = -1;
        goto over;
    } 
    // 7、判断返回码
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
    if (http_code >= 400) {
        printf("http post request failure, url=%s curl code=%ld\n", new_url, http_code) ;
        ret = -1;
        goto over;
    } else {
        printf("success http_code=%ld\n", http_code) ;
    }

over :
    curl_slist_free_all(headers) ;
    return ret;
}

测试文件 test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#include "httpclient.h"


int main(){
    http_resp_data resp_data ;

    memset(&resp_data, 0x00, sizeof(resp_data)) ;

    CURL * curl = create_keep_alive_post_curl() ;

    
    char * data1 = "{\"aa\":\"aaaaaaaaaaaaaaa\"}" ;
    http_post_by_json(curl, "http", "127.0.0.1:3456", "/tt/test/t1", data1, strlen(data1), &resp_data) ;
    printf("size [%lu] message [%s]\n", resp_data.size, resp_data.data) ;
    free(resp_data.data) ;

    memset(&resp_data, 0x00, sizeof(resp_data)) ;
    char * data2 = "{\"bb\":\"bbbbbbbbbbbbbbb\"}" ;
    http_post_by_json(curl, "http", "127.0.0.1:3456", "/tt/test/t1", data2, strlen(data2), &resp_data) ;
    printf("size [%lu] message [%s]\n", resp_data.size, resp_data.data) ;
    free(resp_data.data) ;

    return 0 ;
}

编译及测试。

首先把之前说的 http 服务运行起来,以备测试。此次把httpclient.c编译成动态库,使用时更灵活一些。

执行 export LD_LIBRARY_PATH=./ 是为了给当前终端,添加运行时的动态库查找路径。

通过抓包,查看是否真的是长连接,命令是:tcpdump -i lo port 3456 -w aa.pcap,按ctrl+C结束,查看aa.pcap文件即可。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注