赞
踩
1.当要实现网络数据传输或持久化存储的时候:需要按照指定的数据格式组织,这样才能在使用数据的时候能更好的解析出来。
2.json的格式:json是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
具体格式内容我们用下面一个例子来解释:
例如:这是同学小明的信息如下:
char* name = "小明";
int age = 18;
char* sex = "男";
float score[] = {60.5,99,68};
那么用json这种数据交换格式是将这多种数据对象组织成为一个文本字符串格式,如下:
[
{
"姓名":"小明",
"年龄":18,
"性别":"男",
"成绩":[60.5,99,68]
}
]
组织成这种格式,例如上例,如果有多位同学,那么每个同学都会将自己的信息在{}中以键值对的形式(名称:数值)进行显示,并且不同的同学在[]中以逗号间隔。
其中:
1.jsoncpp库就是用于实现json格式的序列化和反序列化的,完成多个数据对象组织成为json格式字符串,以及将json格式字符串解析得到多个数据对象的功能。
2.其中这是josn的类,这些类成员函数都可以在jsoncpp库中找到,如下:
//json数据对象类 class Json::Value { Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过 Value& operator[](const std::string& key);//简单的方式完成 val["姓名"] = "小明"; Value& operator[](const char* key); Value removeMember(const char* key);//移除元素 const Value& operator[](ArrayIndex index) const; //val["成绩"][0] Value& append(const Value& value);//添加数组元素val["成绩"].append(88),就是由于数组成员都是一个一个的,所以要用append进行加入; ArrayIndex size() const;//获取数组元素个数 val["成绩"].size(); std::string asString() const;//转string string name = val["name"].asString(); const char* asCString() const;//转char* char *name = val["name"].asCString(); Int asInt() const;//转int int age = val["age"].asInt(); float asFloat() const;//转float bool asBool() const;//转 bool };
这只是一个json的类,这个类主要的作用就是用来对数据进行保存的,但是对数据进行序列化和反序列化必须使用与其对应的类,如下:
1.对于jsoncpp实现序列化,要使用的是如下的类(不同的版本使用的类也是不同的,有些低版本不支持一些内容):
//json序列化类,低版本用这个更简单 class JSON_API Writer { virtual std::string write(const Value& root) = 0; } class JSON_API FastWriter : public Writer { virtual std::string write(const Value& root); } class JSON_API StyledWriter : public Writer { virtual std::string write(const Value& root); } //json序列化类,高版本推荐,如果用低版本的接口可能会有警告 class JSON_API StreamWriter { virtual int write(Value const& root, std::ostream* sout) = 0; } class JSON_API StreamWriterBuilder : public StreamWriter::Factory { virtual StreamWriter* newStreamWriter() const; }
(这里我们实现的时候是用高版本实现的)
我们可以看出来,上面的库函数代码参数都是Value对象类型的,所以说在进行序列化的时候,我们是将已经保存好数据的Value对象传入该类函数中,然后这个函数对其进行序列化。
注意:
①:由上面的代码我们可以看出来,StreamWriter类是一个父类,而StreamWriterBuilder类是其子类。
②:使用的时候,我们不是直接使用父类对象然后去调用其write函数(因为write函数是一个虚函数,并且该类为一个抽象类,是无法直接实例化出对象的),进行操作的时候,是先实例化出其子类StreamWriterBuilder类的对象,然后再用父类对象的指针指向该子类的对象,通过父类指针调用子类的newStreamWriter函数,去是实现父类的对象。
③:write函数的参数:
2.对于上面的例子进行简单的实现:
1 #include<iostream> 2 #include<jsoncpp/json/json.h> 3 #include<string> 4 #include<sstream> 5 using namespace std; 6 int main() 7 { 8 //这是我们要进行序列化操作的数据 9 const char* name = "小明"; 10 int age = 18; 11 const char* sex = "男"; 12 float score[] = {60.5,99,68}; 13 //1.先将数据保存在json对象中 14 Json::Value val; 15 val["姓名"] = name; 16 val["年龄"] = age; 17 val["性别"] = sex; 18 val["分数"].append(score[0]); 19 val["分数"].append(score[1]); 20 val["分数"].append(score[2]); 21 //2.使用StreamWriter对象进行序列化(注意:必须要用StreamWriter的指针去调用StreamWriterBuilder 对象的newStreamWriter函数进行实例化出对象后,才能进行使用write) 22 Json::StreamWriterBuilder swb; 23 Json::StreamWriter* sw = swb.newStreamWriter(); 24 stringstream ss; 25 sw->write(val,&ss); 26 //3.将序列化后的数据打印出来 27 cout<<ss.str()<<endl; 28 delete sw; 29 return 0; 30 }
然后运行结果如下:
注意:由于是第三方库,所以必须在后面链接一下库。
1.对于jsoncpp实现反序列化,要使用如下类:
//json反序列化类,低版本用起来更简单
class JSON_API Reader
{
bool parse(const std::string& document, Value& root, bool collectComments = true);
}
//json反序列化类,高版本更推荐
class JSON_API CharReader
{
virtual bool parse(char const* beginDoc, char const* endDoc,Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory
{
virtual CharReader* newCharReader() const;
}
(实现的时候是用高版本实现的)
注意:反序列化和序列化的实现步骤差不多都是要先实例化出子类对象,然后用父类对象的指针去指向子类对象调用newCharReader函数返回的对象,然后再用父类对象去调用parse函数,完成操作。
其中,parse函数的参数如下:
2.对上面的例子进行简单实现如下:
1 #include<iostream> 2 #include<jsoncpp/json/json.h> 3 #include<string> 4 #include<sstream> 5 using namespace std; 6 int main() 7 { 8 //这是我们要进行序列化操作的数据 9 const char* name = "小明"; 10 int age = 18; 11 const char* sex = "男"; 12 float score[] = {60.5,99,68}; 13 //1.先将数据保存在json对象中 14 Json::Value val; 15 val["姓名"] = name; 16 val["年龄"] = age; 17 val["性别"] = sex; 18 val["分数"].append(score[0]); 19 val["分数"].append(score[1]); 20 val["分数"].append(score[2]); 21 //2.使用StreamWriter对象进行序列化(注意:必须要用StreamWriter的指针去调用StreamWriterBuilder 对象的newStreamWriter函数进行实例化出对象后,才能进行使用write) 22 Json::StreamWriterBuilder swb; 23 Json::StreamWriter* sw = swb.newStreamWriter(); 24 stringstream ss; 25 sw->write(val,&ss); 26 //3.将序列化后的数据打印出来 27 cout<<ss.str()<<endl; 28 //4.进行反序列化 29 Json::CharReaderBuilder crb; 30 Json::CharReader* cr = crb.newCharReader(); 31 Json::Value Val; 32 string err; 33 string str = ss.str(); 34 bool res = cr->parse(str.c_str(),str.c_str()+str.size(),&Val,&err); 35 if(res == false) 36 { 37 cout<<"error:"<<err<<endl; 38 delete cr; 39 return 0; 40 } 41 //5.查看反序列化后的内容 42 cout<<Val["姓名"].asString()<<endl; 43 cout<<Val["年龄"].asInt()<<endl; 44 cout<<Val["性别"].asString()<<endl; 45 int sz = Val["分数"].size(); 46 for(int i = 0;i < sz;++i) 47 { 48 cout<<Val["分数"][i].asFloat()<<endl; 49 } 50 delete sw; 51 delete cr; 52 return 0; 53 }
然后执行结果如下:
反序列化后,数据还是可以打印出来的。
1.bundle库:bundle是一个嵌入式压缩库,支持23中压缩算法和2种存档格式。使用的时候只需要加入两个文件bundle.h和bundle.cpp即可。
其中:
①:2种存档格式如下:
特点:
②:23种压缩算法以及效率如下:
压缩速率和压缩效率是不可兼得的,所以使用的时候根据需求进行使用即可。
2.常用压缩数据的程序,如下:
namespace bundle { // low level API (raw pointers) bool is_packed( *ptr, len ); bool is_unpacked( *ptr, len ); unsigned type_of( *ptr, len ); size_t len( *ptr, len ); size_t zlen( *ptr, len ); const void *zptr( *ptr, len ); bool pack( unsigned Q, *in, len, *out, &zlen ); bool unpack( unsigned Q, *in, len, *out, &zlen ); // medium level API, templates (in-place) bool is_packed( T ); bool is_unpacked( T ); unsigned type_of( T ); size_t len( T ); size_t zlen( T ); const void *zptr( T ); bool unpack( T &, T ); bool pack( unsigned Q, T &, T ); // high level API, templates (copy) T pack( unsigned Q, T ); T unpack( T ); }
这是库文件中的一部分,这些一般是使用这个库的接口,如果要看所有代码,可以去库中去查看。
注意:上述代码中有高级中级和低级的模板,我们在实现的时候使用的是高级一点的接口,就是上面最后两行的两个函数。
1.首先,我们使用bundle库的时候,只使用的是bundle.h这里面的函数,但是bundle.h使用的代码都在bundle.cpp中,由于所以链接的时候将其bundle.cpp要一起链接,但是这个里面的代码非常多,大概有12万多行,所以在链接的时候会很难,所以我们先将其生成静态库文件,然后链接的时候效率就快了,如下:
2.实现文件压缩,请看如下程序:
1 #include<iostream> 2 #include<string> 3 #include<fstream> 4 #include"bundle.h" 5 using namespace std; 6 bool Read(const string &name,string *body) 7 { 8 ifstream ifs;//文件指针 9 ifs.open(name,std::ios::binary);//第一个参数为要打开文件名,第二个参数为要使用什么方式打开, 本次使用的方式为以二进制的方式打开。 10 if(ifs.is_open() == false)//是用来查看是否打开文件成功的接口. 11 { 12 cout<<"read open false"<<endl; 13 return false; 14 } 15 ifs.seekg(0,std::ios::end);//表示从末尾位置开始偏移,偏移0个大小 16 size_t fsize = ifs.tellg();//获取当前位置相对于文件起始位置的偏移量。(这也是为什么有上一步> 的原因,找到文件的大小) 17 ifs.seekg(0,std::ios::beg);//表示从起始位置开始偏移,偏移0个大小 18 body->resize(fsize); 19 ifs.read(&(*body)[0],fsize);//由于string.c_str()的返回值是一个const char*的类型的,但是第一> 个参数我们要的是要读入文件的起始位置,所以用一步取地址的方法得到。 20 if(ifs.good() == false)//用来查看文件读取数据是否成功. 21 { 22 cout<<"read read fasle"<<endl; 23 return false; 24 } 25 ifs.close(); 26 return true; 27 } 28 bool Write(const string &name,const string &body) 29 { 30 ofstream ofs;//文件指针. 31 ofs.open(name,std::ios::binary); 32 if(ofs.is_open() == false) 33 { 34 cout<<"write open false"<<endl; 35 return false; 36 } 37 ofs.write(body.c_str(),body.size()); 38 if(ofs.good() == false) 39 { 40 cout<<"write write false"<<endl; 41 return false; 42 } 43 ofs.close(); 44 return true; 45 } 46 void compress(const string &filename,const string &packname) 47 { 48 string body; 49 Read(filename,&body);//将文件中的内容放入body中。 50 string packed = bundle::pack(bundle::LZIP,body);//这个函数第一个参数是要压缩的类型,第二个参 数是要压缩的文件。 51 Write(packname,packed);//将压缩后的内容放入在packname中 52 } 53 void uncompress(const string &filename,const string &packname) 54 { 55 string packed; 56 Read(packname,&packed);//从压缩包中将数据读取到packed中 57 string body = bundle::unpack(packed);//对要压缩的数据进行解压缩,并将解压缩后的数据放在body> 中。 58 Write(filename,body);//从body中将数据放在新文件中。 59 } 60 61 int main() 62 { 63 compress("./hello.cpp","./hello.zip"); 64 uncompress("./hi.txt","./hello.zip"); 65 return 0; 66 }
然后运行程序如下:
实现了将100M的文件压缩到了15k。
注意:dd if=/dev/zero of=./hello.txt bs=100M count=1
:是在当前文件下创建一个大小为100M的文件,并且文件名为hello.txt。
3.查看压缩和解压缩后文件是否有错。
①:要验证两个文件是否相同,那么就计算两个文件的MD5值,如果MD5值相同,那么这两个文件就相同,要是不同,就出现差错了。
②:MD5:是一种散列算法,会根据数据进行大量的运算,终止得到一个结果,而这个结果是字符串,但是只要这两个文件有一点不同,产生的MD5值是完全不一样的。
③:操作:md5sum 文件名
就上面的压缩和解压缩的文件我们进行操作:
两个文件对应的md5的值一模一样,证明压缩和解压缩是完美的。
1.httplib库:一个c++11单文件的跨平台HTTP/HTTPS库。安装起来非常容易。只需要包含httplib.h引入代码中即可。
2.优点:作用于搭建一个简单的http服务器或者客户端的库,而这种第三方的库,可以免去我们在搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。
3.httplib库中代码的认识:
httplib库中有两个结构体分别为请求与响应,如下:
①:请求结构体:
struct Request { std::string method;//请求方法 std::string path;//请求的资源路径 Headers headers;//存放头部字段的地方,由于头部字段一般是以键值对来进行存储,所以这个存储方式是容map来进行的 std::string body;//存放正文 // for server std::string version;//协议版本 Params params;//存放URL中的查询字符串,也是以键值对的方式存储 MultipartFormDataMap files;//用于存放上传时正文的数据信息 Ranges ranges; //用于实现断点续传的数据请求范围区间,也是键值对。 bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0)const; void set_header(const char *key, const char *val); bool has_file(const char *key) const; MultipartFormData get_file_value(const char *key) const; };
②:响应结构体:
struct Response
{
std::string version;//协议版本
int status = -1; //响应状态码
std::string reason;//状态信息
Headers headers;//用哈希表的方式存储头部字段
std::string body;//响应正文信息
std::string location; //源地址
void set_header(const char *key, const char *val);//设置头部字段
void set_content(const std::string &s, const char *content_type);//设置正文
};
响应结构体和请求结构体中保存的大多都是http协议的格式。(在使用的时候,我们只需要上传其结构体的对象就好了,具体进行使用的是下面的客户端程序和服务端程序)
③:服务端程序
class Server
{
using Handler = std::function<void(const Request &, Response &)>;//函数指针类型,就是对于不同的请求,我们要使用不同的函数对请求进行处理
using Handlers = std::vector<std::pair<std::regex, Handler>>;//请求-处理函数映射表,一个请求对应一个映射,用键值对的方式存储
std::function<TaskQueue *(void)> new_task_queue;//线程池
Server &Get(const std::string &pattern, Handler handler);//这类函数都是建立请求与映射的关系。
Server &Post(const std::string &pattern, Handler handler);
Server &Put(const std::string &pattern, Handler handler);
Server &Patch(const std::string &pattern, Handler handler);
Server &Delete(const std::string &pattern, Handler handler);
Server &Options(const std::string &pattern, Handler handler);
bool listen(const char *host, int port, int socket_flags = 0);//启动服务器,开始监听。
};
④:客户端程序
class Client
{
Client(const std::string &host, int port); //传入服务端信息(构造函数)
Result Get(const char *path, const Headers &headers);//向服务器发送get请求
Result Post(const char *path, const char *body, size_t content_length,const char *content_type);//向服务器发送post请求(数据提交)
Result Post(const char *path, const MultipartFormDataItems &items);//向服务器发送post请求(文件上传)
}
⑤:MultipartFormDataItems结构体,主要保存有一些数据的信息
struct MultipartFormData
{
std::string name; //区域名称
std::string content; //区域正文
std::string filename; //文件名称
std::string content_type; //正文类型
};
1.搭建简单服务器,如下:
1 #include<iostream> 2 #include"httplib.h" 3 using namespace std; 4 void Upload(const httplib::Request &req,const httplib::Response &rsp) 5 { 6 if(req.has_file("file"))//判断有没有name字段是file的标识区,请求的这个文件名存在不 7 { 8 httplib::MultipartFormData data = req.get_file_value("file");//返回这个文件一些内容,有d ata保存 9 std::cout << data.name << std::endl; //区域字段的标识名 10 std::cout << data.filename << std::endl; //如果是文件上传,则是文件名 11 std::cout << data.content << std::endl; //区域正文数据,如果是文件上传,就是文件的内容 12 } 13 } 14 void Numbers(const httplib::Request &req,httplib::Response &rsp) 15 { 16 //这就是业务处理函数 17 rsp.body = req.path; 18 rsp.body += "-----------------"; 19 rsp.body += req.matches[1]; 20 rsp.status = 200; 21 rsp.set_header("Content-Type","text/plain"); 22 } 23 int main() 24 { 25 //1.实例化出Serve对象 26 httplib::Server serve; 27 //2.添加映关,告诉服务器,对于客户端的什么请求,用什么函数进行处理,如下添加了两个处理方法 ,为Get和Post 28 serve.Get("/numbers/(\\d+)",Numbers);//第一个参数为正则表达式也就是请求的资源路径,第二个参数为Numbers,是对应 的处理函数 29 serve.Post("/upload",Upload); 30 serve.listen("0.0.0.0",9090); 31 return 0; 32 }
运行结果后,然后用netstat命令查看如下:
展现出这个服务器已经处于监听状态了。
然后我们将服务端ip地址和端口号和请求资源路径(就是请求的第一个参数),然后函数会利用回调函数进行处理,然后在网页上会显示如下:
1.搭建简单客户端如下:
1 #include<iostream> 2 #include"httplib.h" 3 int main() 4 { 5 httplib::Client client("192.168.136.130",9090);//其中,这里填写的是服务端的ip地址和端口号 6 //就是Result Get(const char* path,const Headers& headers); 7 httplib::Headers headers = {{"connection","Close"}}; 8 auto res = client.Get("/number/1234",headers); 9 if(res && res->status == 200) 10 { 11 std::cout<< res->body << std::endl; 12 } 13 /// 14 //Result post(const char* path,const MultipartFormDataItems &items); 15 httplib::MultipartFormDataItems items; 16 httplib::MultipartFormData item; 17 item.name = "file"; //文件域 18 item.filename = "zhang.txt";//文件名 19 item.content = "hello"; //正文数据 20 item.content_type = "application/octet-stream";//以二进制流 21 items.push_back(item); 22 23 res = client.Post("/upload",items); 24 return 0; 25 }
与服务端配合,使用如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。