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文件即可。

发表回复