linux curl发送文件和socket tcp通讯
linux curl发送文件背景web方式终端curl方式libcurl方式背景linux 下要实现传输图片到服务器,然后想到了用curl 上传到go-fastdfs(基于http的分布式文件里系统)gitee链接,这个不同于服务器,是和服务器独立开来的.上传文件的话有web方式和终端curl方式和libcurl方式.下面将介绍这三种方法.web方式upload后终端curl方式需要output
背景
linux 下要实现传输图片到服务器,然后想到了用curl 上传到go-fastdfs(基于http的分布式文件里系统)gitee链接,这个不同于服务器,是和服务器独立开来的.
上传文件的话有web方式和终端curl方式和libcurl方式.下面将介绍这三种方法.
web方式
下面这个是用gofastdfs创建的一个图片资源库,至于怎么用gofastdfs创建我也没接触.接触后再补充.
upload后
终端curl方式
需要output = json,才能得到返回的数据.
如果想在代码调用终端命令
但你要获取返回的json,你需要popen.看这篇文章,这一步我没试过.
libcurl方式
一开始要验证libcurl是否正常,那么我可以创建一个http1.c文件,然后gcc http1.c -o http1 -l curl
验证一下.
#include <stdio.h>
#include <curl/curl.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
CURL *curl; //定义CURL类型的指针
CURLcode res; //定义CURLcode类型的变量,保存返回状态码
if(argc!=2)
{
printf("Usage : file <url>;/n");
exit(1);
}
curl = curl_easy_init(); //初始化一个CURL类型的指针
if(curl!=NULL)
{
//设置curl选项. 其中CURLOPT_URL是让用户指定url. argv[1]中存放的命令行传进来的网址
curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
//调用curl_easy_perform 执行我们的设置.并进行相关的操作. 在这里只在屏幕上显示出来.
res = curl_easy_perform(curl);
//清除curl操作.
curl_easy_cleanup(curl);
}
return 0;
}
post上传图片代码(转载这位博主):
#include <iostream>
#include <curl/curl.h>
#include <string.h>
using namespace std;
//回调函数 得到响应内容
int write_data(void* buffer, int size, int nmemb, void* userp){
std::string * str = dynamic_cast<std::string *>((std::string *)userp);
str->append((char *)buffer, size * nmemb);
return nmemb;
}
int upload(string url, string &body, string* response);
int main(int argc, char** argv){
std::string body;
std::string response;
int status_code = upload("http://172.28.28.17:80/files/deviceupload", body, &response);
if (status_code != CURLcode::CURLE_OK) {
return -1;
}
cout << body << endl;
cout << response << endl;
return 0;
}
int upload(string url, string &body, string* response)
{
CURL *curl;
CURLcode ret;
curl = curl_easy_init();
struct curl_httppost* post = NULL;
struct curl_httppost* last = NULL;
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, (char *)url.c_str()); //指定url
curl_formadd(&post, &last, CURLFORM_PTRNAME, "path", CURLFORM_PTRCONTENTS, "device_cover", CURLFORM_END);//form-data key(path) 和 value(device_cover)
curl_formadd(&post, &last, CURLFORM_PTRNAME, "file", CURLFORM_FILE, "./test.jpg",CURLFORM_FILENAME, "hello.jpg", CURLFORM_END);// form-data key(file) "./test.jpg"为文件路径 "hello.jpg" 为文件上传时文件名
curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); //构造post参数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); //绑定相应
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response); //绑定响应内容的地址
ret = curl_easy_perform(curl); //执行请求
if(ret == 0){
curl_easy_cleanup(curl);
return 0;
}
else{
return ret;
}
}
else{
return -1;
}
}
//提醒:对于CURLFORM_FILE和CURLFORM_FILENAME,要传c里面的字符串,所以c++ string要c_str()才行.
上传完,我们可以通过获取response,拿到返回的结果
因为服务器和gofastdfs是两个不一样的东西,所以我还需要将返回的数据再用http以post形式的json格式传回给服务器(代码转载这位博主)
// libcurlPostJson.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <sstream>
//json
#include "json/json.h"
using namespace std;
//http://blog.csdn.net/wyansai/article/details/50764315
wstring AsciiToUnicode(const string& str)
{
// 预算-缓冲区中宽字节的长度
int unicodeLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0);
// 给指向缓冲区的指针变量分配内存
wchar_t *pUnicode = (wchar_t*)malloc(sizeof(wchar_t)*unicodeLen);
// 开始向缓冲区转换字节
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, pUnicode, unicodeLen);
wstring ret_str = pUnicode;
free(pUnicode);
return ret_str;
}
string UnicodeToUtf8(const wstring& wstr)
{
// 预算-缓冲区中多字节的长度
int ansiiLen = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
// 给指向缓冲区的指针变量分配内存
char *pAssii = (char*)malloc(sizeof(char)*ansiiLen);
// 开始向缓冲区转换字节
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, pAssii, ansiiLen, nullptr, nullptr);
string ret_str = pAssii;
free(pAssii);
return ret_str;
}
string AsciiToUtf8(const string& str)
{
return UnicodeToUtf8(AsciiToUnicode(str));
}
size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
string data((const char*)ptr, (size_t)size * nmemb);
*((stringstream*)stream) << data << endl;
return size * nmemb;
}
//POST json
int main()
{
CURL *curl;
CURLcode res;
char tmp_str[256] = { 0 };
std::stringstream out;
//HTTP报文头
struct curl_slist* headers = NULL;
char *url = "http://localhost:10098/inbound/test/getTestInfo";
curl = curl_easy_init();
if (curl)
{
//构建json
Json::Value item;
item["mobile"] = Json::Value("weidong0925@126.com");
item["pageNo"] = Json::Value("梅西&内马尔&苏亚雷斯.txt");
std::string jsonout = item.toStyledString();
jsonout = AsciiToUtf8(jsonout);
printf("Echo:\t request:\t %s", jsonout.c_str());
//设置url
curl_easy_setopt(curl, CURLOPT_URL, url);
//设置http发送的内容类型为JSON
//构建HTTP报文头
sprintf_s(tmp_str, "Content-Length: %s", jsonout.c_str());
headers = curl_slist_append(headers, "Content-Type:application/json;charset=UTF-8");
//headers=curl_slist_append(headers, tmp_str);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
//curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");//自定义请求方式
curl_easy_setopt(curl, CURLOPT_POST, 1);//设置为非0表示本次操作为POST
// 设置要POST的JSON数据
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonout.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, jsonout.size());
// 设置接收数据的处理函数和存放变量
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);//设置回调函数
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out);//设置写数据
res = curl_easy_perform(curl);//执行
string str_json = out.str(); //清空stringstream的状态
printf("Echo: \t response: \t %s", str_json.c_str());
/* always cleanup */
curl_easy_cleanup(curl);
}
return 0;
}
//当然,不是传中文的话把那几个函数删掉就行了
//对了,catkin_make编译时候记得将curl加入到cmakelist里.如下图
不想这样的话你就要在find_package里加入curl,如下图
socket 长链接方式接收数据
参考链接
是的,我又遇到问题了,上面只是发送自己指定json上去,但是我要的是通过服务器请求,拿到json,再以http发回去。
我之前的思路是: 在局域网内.我作为客户端以一定频率去循环监听某个函数,函数里面不断get请求MQTT服务器,后台服务器也向MQTT发送get请求,服务器那边get请求是为了从response再发回给MQTT.我这边get请求是为了MQTT拿返回带指令字符串的response结果.最后再将结果以http形式发回给服务器另一个地址,服务器也会返回对应处理结果给我
这个会有一个问题,这种短连接会导致有时候接收不到数据。我应该变成服务器,接收请求,而不是主动循环去请求。所以就要用到socket长链接,一直等待服务器发过来的get请求,然后我接收里面的数据。这样就少了MQTT这个中间东西了.
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#define MAXSIZE 1024
using namespace std;
class tcp_server
{
private:
int socket_fd,accept_fd;
sockaddr_in myserver;
sockaddr_in remote_addr;
string& replace_all(string& str, const string& old_value, const string& new_value);
public:
tcp_server(int listen_port);
int recv_msg();
};
string& tcp_server::replace_all(string& str, const string& old_value, const string& new_value)
{
string::size_type pos=0;
while((pos=str.find(old_value))!= string::npos)
{
str=str.replace(str.find(old_value),old_value.length(),new_value);
}
return str;
}
tcp_server::tcp_server(int listen_port) {
if(( socket_fd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0 ){ //SOCK_STREAM是TCP套接字,返回的是socket_fd套接字
throw "socket() failed";
}
memset(&myserver,0,sizeof(myserver)); // myserver清0
myserver.sin_family = AF_INET; // 地址族,AF_xxx 在socket编程中只能是AF_INET
myserver.sin_addr.s_addr = htonl(INADDR_ANY);
myserver.sin_port = htons(listen_port);
// 设置套接字选项避免地址使用错误 , 方法:bind绑定地址之前,以 SO_REUSEADDR 选项调用 setsockopt,允许地址重用
int on=1;
if((setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
if( bind(socket_fd,(sockaddr*) &myserver,sizeof(myserver)) < 0 ) {
throw "bind() failed";
}
if( listen(socket_fd,10) < 0 ) {
throw "listen() failed";
}
}
int tcp_server::recv_msg() {
while(1) {
socklen_t sin_size = sizeof(struct sockaddr_in);
if(( accept_fd = accept(socket_fd,(struct sockaddr*) &remote_addr,&sin_size)) == -1 ){
throw "Accept error!";
continue;
}
printf("Received a connection from %s : %d\n",(char*) inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port));
if( !fork() ) { //成功创建一样的进程则有>0的pid,新进程pid = 0
cout << "pid "<< getpid << endl;
char buffer[MAXSIZE];
memset(buffer,0,MAXSIZE);
if( ( read(accept_fd,buffer,MAXSIZE)) < 0 ) {
throw("Read() error!");
} else {
printf("Received message: %s\n",buffer);
// 替换url的特殊字符串
string test_str = buffer;
cout << "test_str:" << endl<< test_str.c_str() << endl;
string newstr;
newstr = replace_all(test_str, "%22", "\"");
newstr = replace_all(newstr, "%20", "");
cout << "new_str:" << endl<< newstr.c_str() << endl;
break;
}
exit(0); //在调用处强行退出程序,运行一次程序就结束,不断fork又不断exit(0)重新accept
}
close(accept_fd);
}
return 0;
}
int main()
{
// tcp_server ts(atoi(argv[1]));
tcp_server ts(19800);
ts.recv_msg();
return 0;
}
这里有三个坑:
- 刚启动程序时候accept有可能接受不了数据,怀疑是using namespace std导致bind函数被std中的同名函数覆盖,使得服务端的socket没有绑定ip地址和商品,自然之后的accept函数也就不能正常阻塞。
对策有2种:
1是把bind函数改为全局调用方式 ::bind(), 摆脱std命名空间的影响。
2是放弃std命名空间在全局范围内的声明,改用指定使用函数的方式。 如 using std::cout 或using std::endl - postman发送数据后程序成功接收但会执行不了exit(0),导致只能postman不能一直发送数据.原因是局部变量exit(0)后并没有释放,只释放了全局变量导致内存溢出,表现出卡住的情况,所以你要将局部变量变全局或者在fork()里面用一次,empty一次.(参考这位博主).
- ctrl+c结束服务器端程序的话,再次启动服务器就会出现绑定地址失效问题,这是因为bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。 解决方法就是代码里面setsockopt,让地址可以重复绑定.(参考这位博主)
值得注意的是,当以get发送请求,使用HTTP协议(使用URI来传输数据和建立连接),会对URL中的特殊字符进行编码。
比如我常用的:
字符 | 特殊编码 |
---|---|
空格 | %20 |
" | %22 |
{ | %7B |
: | %3A |
, | %2C |
} | %7D |
具体URL编码看:该链接
ASCII,Unicode和UTF-8编码请看:该链接
还有一个问题就是:
postman 的编码是gzip,deflate,br,而通过Mozilla的话只有gzip,deflate
这会导致postman编码有一些特殊字符是帮你转好的。对比如下面两个图
发送的指令数据是:
{“aircraft_id”:“1ZNBHBR00C006R”,“transaction_id”:“20210810113024”,“payload_index”:0,“file_index”:7340050,“file_name”:“test12.jpg”}
因为我是要提取get后面的admin这个key的数据,所以我做法是分两部:
- 先将全部特殊字符编码还原成对应的字符(参考这位博主)
newstr = replace_all(test_str, "%22", "\"");
newstr = replace_all(newstr, "%20", "");
newstr = replace_all(newstr, "%7B", "{");
newstr = replace_all(newstr, "%3A", ":");
newstr = replace_all(newstr, "%2C", ",");
newstr = replace_all(newstr, "%7D", "}");
string& tcp_server::replace_all(string& str, const string& old_value, const string& new_value)
{
string::size_type pos=0;
while((pos=str.find(old_value))!= string::npos)
{
str=str.replace(str.find(old_value),old_value.length(),new_value);
}
return str;
}
- 然后再提取字符串
int nPos_1 = newstr.find("admin=", 0, 6);
int nPos_2 = newstr.find("HTTP", 0, 4);
string new_new_str;
new_new_str = newstr.substr(nPos_1+6,nPos_2-(nPos_1+6));
cout << "substr_str:" << endl<< new_new_str.c_str() << endl;
题外话:ros topic 发布一次可能会接收不到数据
参考链接
发布之后等1s再publish就可以了.
更多推荐
所有评论(0)