最简单的http_post 客户端c代码

------------------------------------------------------------
author:hjjdebug
date:  2024年 09月 08日 星期日 09:28:15 CST
description: 最简单的http_post 客户端c代码
------------------------------------------------------------

背景: 一个视频录制项目,要求每秒中向服务器汇报当前视频bit率,音频bit率,
起初用的是Qt 的QNetworkAccessManager 对象, 版本QT5.14.0, 它每post一次状态,就会有4处内存泄漏
为88*3+8=272 bytes, 啊!我怎么知道的? 我有照妖镜看着它呢.并不是deleteLater 所能解决的.
虽然泄漏并不严重,但每小时近1M, 每天23.5M,还是不爽!
所以决定放弃qt 对象, 自己写 http_post 客户端. 下面转入正题.

------------------------------------------------------------
http_post 请求有2个要素,
第一个是请求的url.
第二个是你要post 的内容.
请求的url 会被分割为IP,端口,资源位置.
然后url 连同post 的内容message 按照http协议一块打包成request.
最后用tcp 把request 向服务器发送,再从服务器接受响应就可以了.
------------------------------------------------------------

具体还可以划分为以下步骤:
//分割url,提取出host,port,resouce, 参考SplitUrl()
//与服务器建立连接
//构建http 请求,将host:port resource连同post 的内容message 按照http协议一块打包成request.
//发送请求
//接受响应
//关闭套接字

这也是程序主流程:
if(!SplitUrl(url,host,&port,resource)) return 1;
if(!ConnectToServer(host, port)) return 1;
request = ConstructRequest(resource,host, port, message);
bool res=send(request.c_str());
res=receive();
close(sockfd);


具体实现请参考代码:

#include <arpa/inet.h>   // for inet_pton
#include <netinet/in.h>  // for sockaddr_in, htons
#include <stdio.h>       // for printf, perror, snprintf, NULL
#include <stdlib.h>      // for exit, atoi
#include <string.h>      // for memset, strcpy, strlen, strcmp
#include <sys/socket.h>  // for AF_INET, connect, recv, send, socket, SOCK_S...
#include <sys/time.h>    // for gettimeofday, timeval
#include <unistd.h>      // for close
#include <string>        // for string

using namespace std;

int sockfd;
//const char *url="http://192.168.6.50:89/api/daemon/record/v1/channel/";
bool SplitUrl(const char *url, char *host, int *port, char *resource)
{
	const char *p;
	char buf[64];
	char *dst;
	//查找协议
	for(p=url,dst=buf;*p!=0;p++,dst++)
	{
		if(*p==':'){p++;break;}
		*dst=*p;
	}
	*dst=0;
	if(strcmp(buf,"http")!=0) return false;
	p++;
	p++; //跳过<//>
	//填充ip
	dst=buf;
	for(;*p!=':'&&*p!='/';p++)
	{
		*dst++=*p;
	}
	*dst=0;
	strcpy(host, buf);

	if(*p==':')
	{
		p++;
		//计算port
		dst=buf;
		for(;*p!='/';p++)
		{
			*dst++=*p;
		}
		*dst=0;
		*port=atoi(buf);
	}
	strcpy(resource,p);
	return true;
}

bool ConnectToServer(char *host, int port)
{
//创建tcp 套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		printf("创建socket失败\n");
		exit(1);
	}
	printf("-> 创建socket成功\n");

//建立连接
	struct sockaddr_in serv_addr;
	memset(&serv_addr, '0', sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(port); // HTTP 默认端口为 80
// 将IP地址从点分十进制转换为网络字节顺序
	if(inet_pton(AF_INET, host, &serv_addr.sin_addr) <= 0)
	{
		printf("IP addr format error\n");
		exit(1);
	}
	// 连接到远端服务器
	int connectid = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	if (connectid == -1) {
		printf("连接远端服务器失败\n");
		exit(1);
	}
	printf("-> 连接远端服务器成功\n");
	return true;
}
// resource 是url资源地址
std::string ConstructRequest(char *resource, char *host, int port,const char *message)
{
//构建http 请求
	char request[1024];
	int msg_len=strlen(message);
	snprintf(request, 1024,
						   "POST %s HTTP/1.1\r\n"
						   "Host: %s:%d\r\n"
						   "Content-Type: application/json\r\n"
						   "Content-Length: %d\r\n"
						   "\r\n"
						   "%s",resource,host,port,msg_len,message);
	//这里std::string会自动分配内存
	std::string str(request);
	return str;
}
bool send(const char *request)
{
	int request_len=strlen(request);
//发送请求
	int total = 0;
	int bytesleft = request_len;
	while(total < request_len) {
		int n = send(sockfd, request+total, bytesleft, 0);
		if (n == -1) { // 处理错误
			perror("send error");
			return false;
		} 
		total += n;
		bytesleft -= n;
	}
	return true;
}

string receive()
{
	std::string str;
	std::string nullStr;
	char response[1024];
	memset(response, 0, sizeof(response));
	int n,size;
	int remain,left,block_size,read_size;
	char *p;
	//接受什么时候停止呢? 数据量小(post 的响应),一次就可完全接受
	//数据量大时,可以一直接受,直到对方关闭
	//较好的办法是依据协议,根据数据块长度来关闭
	n = recv(sockfd, response, sizeof(response)-1, 0);
	if (n < 0) {perror("receive error"); return nullStr;}
	p=strstr(response,"\r\n\r\n");//后边跟block_size
	p+=4;
	sscanf(p, "%x", &block_size); //根据协议,获取块协议大小,此例是0x2c
	for(size=0;*p!='\r';p++)
	{
		size++; //计算长度描述本身所占大小,此例2c就是2个byte
	}
	//p+size+2-response 是协议头长度,n是本次读取的长度,相减为本次读取的数据体长度
	remain=block_size-(n-(p+size+2-response)); //计算剩余数据体字节数,为0或负数表示已读完
	printf("n is %d,remain:%d\n",n,remain);
	response[n]=0;
	str+=response;
	left=remain;
	read_size=left>(int)sizeof(response)-1 ? (int)sizeof(response)-1 : left;
	while(read_size>0)
	{
		n =recv(sockfd, response, read_size, 0);
		if (n < 0) {perror("receive error"); return nullStr;}
//		printf("n is %d\n",n);
		response[n]=0;
		str+=response;
		left-=n;
		read_size=left>(int)sizeof(response)-1 ? (int)sizeof(response)-1 : left;
	}
	return str;
}


long getTimeUs()
{
	struct timeval tv;
	gettimeofday(&tv,NULL);
	long res=tv.tv_sec*1000000+tv.tv_usec;
	return res;
}

int http_post(const char *url, const char *message) // 0 成功
{
	char host[16];
	int port=80;
	char resource[256];
	string request; 
	for(int i=0;i<1;i++) //循环,仅为测试
	{
// resource 是url 资源位置,例如:/api/daemon/record/v1/channel/100,服务器会用它
		if(!SplitUrl(url,host,&port,resource)) return 1;
		printf("connet begin at %ld\n",getTimeUs());
		if(!ConnectToServer(host, port)) return 1;
		printf("connet end at %ld\n",getTimeUs());
		//土洋结合,用上了c++的类,它自己管理内存,还是挺方便的
//		string request; 
		//如果request 是括号内定义的局部变量,
		//出了大括号,request 会被析构,在这个for循环中,它会被析构n次
		//如果request 是括号外定义的变量,
		//在等号赋值时,这个request会先被析构(此时释放内存),再被赋值
		//就是说虽然流程大体一样,但释放内存的时机不同.
		//局部变量是在出作用域时被释放,虽然它在=赋值时也调用析构,但已无内存可释放
		//非括号内局部变量是在等号赋值时释放内存的.
		request = ConstructRequest(resource,host, port, message);
		//这个send 是我们写的send,就是其名称是send后还和参数组合共同构建一个函数名称
		//不同于函数内的那个send(属于c函数的send),
		//编译器是分得清的.不会报错.
		bool res=send(request.c_str());
		printf("send result:%s\n",res==true? "ok":"fail");
		printf("receiv begin at %ld\n",getTimeUs());
		string recv_str=receive();
		printf("receiv end at %ld\n",getTimeUs());
		printf("recv:%s\n",recv_str.c_str());
	
//关闭套接字
		close(sockfd);
	}
	return 0;
}
//测试服务器地址及发送的消息块
const char *url="http://192.168.6.50:89/api/daemon/record/v1/channel/100";
const char *message=R"--({"audioBit": 195, "videoBit": 7014})--";
int main()
{
	http_post(url, message);
	return 0;
}

//如果你要拿来用,改一下url及message 即可,当然也可以放到命令行上,就是小意思了.
//看一下程序执行结果.

 ./http_post 
connet begin at 1727229556873479
-> 创建socket成功
-> 连接远端服务器成功
connet end at 1727229556874010
send result:ok
receiv begin at 1727229556874089
n is 296,remain:-5
receiv end at 1727229556876663
recv:HTTP/1.1 200 
Date: Wed, 25 Sep 2024 01:59:15 GMT
Server: Apache/2.4.59 (codeit) OpenSSL/3.0.13+quic
Vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked

2c
{"code":400,"errorMsg":"没有这个任务"}
0


//结果分析
//远程服务器是局域网中的一个IP
创建连接用时4010-3479=531us=0.5ms
发送用时: 4089-4010=79us=0.07ms (可忽略)
接收用时: 6663-4089=2574us=2.6ms
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值