背景

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;
}

这里有三个坑:

  1. 刚启动程序时候accept有可能接受不了数据,怀疑是using namespace std导致bind函数被std中的同名函数覆盖,使得服务端的socket没有绑定ip地址和商品,自然之后的accept函数也就不能正常阻塞。  
    对策有2种:
    1是把bind函数改为全局调用方式 ::bind(), 摆脱std命名空间的影响。
    2是放弃std命名空间在全局范围内的声明,改用指定使用函数的方式。 如 using std::cout 或using std::endl
  2. postman发送数据后程序成功接收但会执行不了exit(0),导致只能postman不能一直发送数据.原因是局部变量exit(0)后并没有释放,只释放了全局变量导致内存溢出,表现出卡住的情况,所以你要将局部变量变全局或者在fork()里面用一次,empty一次.(参考这位博主).
  3. 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编码有一些特殊字符是帮你转好的。对比如下面两个图
postman

在这里插入图片描述
发送的指令数据是:

{“aircraft_id”:“1ZNBHBR00C006R”,“transaction_id”:“20210810113024”,“payload_index”:0,“file_index”:7340050,“file_name”:“test12.jpg”}

因为我是要提取get后面的admin这个key的数据,所以我做法是分两部:

  1. 先将全部特殊字符编码还原成对应的字符(参考这位博主)
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;
}
  1. 然后再提取字符串
   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就可以了.

Logo

更多推荐