六、通訊錄4.0實(shí)現(xiàn)—?絡(luò)版
簡(jiǎn)易版本
服務(wù)端完整版本
客戶端完整版本
Protobuf還常?于通訊協(xié)議、服務(wù)端數(shù)據(jù)交換場(chǎng)景。那么在這個(gè)?例中,我們將實(shí)現(xiàn)?個(gè)?絡(luò)版本的
通訊錄,模擬實(shí)現(xiàn)客?端與服務(wù)端的交互,通過(guò)Protobuf來(lái)實(shí)現(xiàn)各端之間的協(xié)議序列化。
需求如下:
-
客?端可以選擇對(duì)通訊錄進(jìn)?以下操作:
-
- 新增?個(gè)聯(lián)系?
-
- 刪除?個(gè)聯(lián)系?
-
- 查詢通訊錄列表
-
- 查詢?個(gè)聯(lián)系?的詳細(xì)信息
-
服務(wù)端提供增刪查能?,并需要持久化通訊錄。
-
客?端、服務(wù)端間的交互數(shù)據(jù)使?Protobuf來(lái)完成。
如下圖:
- 客戶端要有一個(gè)菜單,新增一個(gè)聯(lián)系人…
- 每個(gè)功能都有一對(duì)請(qǐng)求和響應(yīng)協(xié)議.
- 例如實(shí)現(xiàn)新增一個(gè)聯(lián)系人,首先我們要設(shè)計(jì)message, 中間是網(wǎng)絡(luò)傳輸.
- 然后安裝圖形序號(hào)執(zhí)行,客戶端完成:1,2,3,7 ; 服務(wù)端完成:1,4.5,6
1. 環(huán)境搭建
1.1 安裝Httplib庫(kù)
Httplib庫(kù):cpp-httplib是個(gè)開(kāi)源的庫(kù),是?個(gè)c++封裝的http庫(kù),使?這個(gè)庫(kù)可以在linux、windows平臺(tái)下完成http客?端、http服務(wù)端的搭建。
使?起來(lái)?常?便,只需要包含頭?件 httplib.h
即可。編譯程序時(shí),需要帶上-lpthread
選項(xiàng)。
源碼庫(kù)地址:https://github.com/yhirose/cpp-httplib
鏡像倉(cāng)庫(kù):https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_github_accelerator
1.1升級(jí) gcc
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-
bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-
zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --
enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-
c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --
with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --
with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install -
-enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-
redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
cpp-httplib 用老的編譯器,要么編譯不通過(guò),要么直接運(yùn)行報(bào)錯(cuò)
百度搜索:scl gcc devsettool升級(jí)gcc
//安裝scl
$ sudo yum install centos-release-scl scl-utils-build
//安裝新版本gcc,這里也可以把7換成8或者9,我用的是9,也可以都安裝
$ sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
$ ls /opt/rh/
//啟動(dòng): 細(xì)節(jié),命令行啟動(dòng)只能在本會(huì)話有效
$ scl enable devtoolset-7 bash
$ gcc -v
//可選:如果想每次登陸的時(shí)候,都是較新的gcc,需要把上面的命令添加到你的~/.bash_profile中
$ cat ~/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
#添加下面的命令,每次啟動(dòng)的時(shí)候,都會(huì)執(zhí)行這個(gè)scl命令
scl enable devtoolset-7 bash
or
scl enable devtoolset-8 bash
or
scl enable devtoolset-9 bash
2. 搭建簡(jiǎn)單的服務(wù)器
用來(lái)測(cè)試httplib
Client.cpp
#include"httplib.h"
#include<iostream>
#include<string>
using namespace std;
using namespace httplib;
const string IP = "127.0.0.1";// 監(jiān)聽(tīng)所有的ip
//const string IP = "192.139.99.192";
const int PORT = 6666;
int main()
{
Client client(IP.c_str(),PORT);
// 傳輸./test-Post 資源
Result res1 = client.Post("./test-Post");
if(res1->status ==200){
cout<<"post sucess Post"<<endl;
}
// 請(qǐng)求./test-Post 資源
Result res2 = client.Get("./test-Get");
if(res2->status ==200){
cout<<"get sucess Get"<<endl;
}
}
Server.cpp
#include"httplib.h"
#include<iostream>
#include<string>
using namespace std;
using namespace httplib;
const string IP = "127.0.0.1";
//const string IP = "0.0.0.0";
//const string IP = "192.139.99.192";
const int PORT = 6666;
int main()
{
Server svr;
// 注冊(cè)post處理方法
svr.Post("./test-Post",[](const Request& req ,Response& res){
cout<< "server test Post"<<endl;
res.status=200;
});
svr.Get("./test-Get",[](const Request& req ,Response& res){
cout<< "server test Get"<<endl;
res.status=200;
});
svr.listen(IP.c_str(),PORT);
cout<<"sucess"<<endl;
}
3. 約定雙端交互接?
定制http協(xié)議
新增?個(gè)聯(lián)系?:
[請(qǐng)求]
Post /contacts/add
Content-Type: application/protobuf
AddContactRequest
[響應(yīng)]
Content-Type: application/protobuf
AddContactResponse
刪除?個(gè)聯(lián)系?:
[請(qǐng)求]
Post /contacts/del
Content-Type: application/protobuf
DelContactRequest
[響應(yīng)]
Content-Type: application/protobuf
DelContactResponse
查詢通訊錄列表:
[請(qǐng)求]
GET /contacts/find-all
[響應(yīng)]
Content-Type: application/protobuf
FindAllContactsResponse
查詢?個(gè)聯(lián)系?的詳細(xì)信息:
[請(qǐng)求]
Post /contacts/find-one
Content-Type: application/protobuf
FindOneContactRequest
[響應(yīng)]
Content-Type: application/protobuf
FindOneContactResponse
4. 代碼實(shí)現(xiàn)客戶端
這里我們只實(shí)現(xiàn)了新增?個(gè)聯(lián)系?模塊,完整代碼在碼云.
add_contact.proto
syntax="proto3";
package add_contact;
message AddContactReq{
string name = 1 ;
int32 age = 2 ;
message Phone{
string number = 1;
enum PhoneType{
MP=0;
TEL=1;
}
PhoneType type =2;
}
repeated Phone phones = 3;
}
message AddContactResp{
bool success = 1; // 添加聯(lián)系人是否成功
string error_desc =2 ;// 錯(cuò)誤信息
string uid =3 ; // 聯(lián)系人序號(hào)
}
ContactException.h:定義異常類
#include<string>
class ContactException
{
private:
std::string message;
public:
ContactException(std::string str="A problem"):message(str){}
std::string what()const {return message; }
};
main.cc
#include "httplib.h"
#include "contactException.h"
#include "add_contact.pb.h"
#include <iostream>
#include <string>
using namespace std;
using namespace httplib;
const string IP = "127.0.0.1"; // 監(jiān)聽(tīng)所有的ip
// const string IP = "192.139.99.192";
const int PORT = 6666;
void menu();
void addContact();
void buildAddContactReq(add_contact::AddContactReq *req);
int main()
{
while (true)
{
enum OPTION{QUIT = 0,ADD,DEL,FIND_ALL,FIND_ONE};
menu();
cout << "--------請(qǐng)選擇:";
int choose;
cin >> choose;
cin.ignore(256, '\n');
try
{
switch (choose)
{
case ADD:
addContact();
break;
case DEL:
break;
case FIND_ALL:
break;
case FIND_ONE:
break;
case QUIT:
cout << "程序退出" << endl;
exit(0);
break;
default:
break;
}
}
catch (ContactException &e)
{
cout << "--->操作通訊錄時(shí)發(fā)生異常" << endl
<< "--->異常信息:" << e.what() << endl;
}
}
}
void addContact()
{
Client client(IP, PORT);
//構(gòu)造 req
add_contact::AddContactReq req;
buildAddContactReq(&req);
// 序列化 req
string req_str;
if (!req.SerializePartialToString(&req_str))
{
throw ContactException("req 序列化失敗");
}
// 發(fā)起post調(diào)用
auto ret = client.Post("/contacts/add", req_str, "Content-Type: application/protobuf");
if (!ret)
{
string err_desc;
err_desc.append("Post /contacts/add 請(qǐng)求失敗! 錯(cuò)誤信息:")
.append(/*httplib::to_string(ret.error())當(dāng)前httplib沒(méi)有該函數(shù)*/
to_string(ret.error()));
throw ContactException(err_desc);
}
// 方序列號(hào)resp
add_contact::AddContactResp resp;
bool parse = resp.ParseFromString(ret->body);
if(!parse){
throw ContactException("反序列化失敗!");
}
if (ret->status != 200 && !parse)
{
string err_desc;
err_desc.append("/contacts/add 調(diào)用失敗")
.append(std::to_string(ret->status))
.append("(")
.append(ret->reason)
.append(")")
.append(resp.error_desc());
throw ContactException(err_desc);
}
else if (ret->status != 200)
{
string err_desc;
err_desc.append("/contacts/add 調(diào)用失敗")
.append(std::to_string(ret->status))
.append("(")
.append(ret->reason)
.append(")")
.append("錯(cuò)誤信息:")
.append(resp.error_desc());
throw ContactException(err_desc);
}
else if (!resp.success())
{
string err_desc;
err_desc.append("/contacts/add 結(jié)果異常\t異常原因")
.append(std::to_string(ret->status))
.append("(")
.append(resp.error_desc())
.append(")");
throw ContactException(err_desc);
}
// 結(jié)果打印
cout<<"新添加的聯(lián)系人"<<resp.uid()<<"成功"<<endl;
}
void buildAddContactReq(add_contact::AddContactReq* req)
{
cout << "請(qǐng)輸入姓名:";
string name;
getline(std::cin,name);
cout << "請(qǐng)輸入年齡:";
int age;
cin >> age;
cin.ignore(256, '\n');
int i = 1;
req->set_age(age);
req->set_name(name);
while (true)
{
cout << "請(qǐng)輸入手機(jī)號(hào)碼" << i++ << ":";
string number;
getline(std::cin,number);
if (number.empty())
{
//cout << "輸入聯(lián)系人完成\n";
break;
}
add_contact::AddContactReq_Phone *phone = req->add_phones();
phone->set_number(number);
cout << "請(qǐng)輸入手機(jī)類型(ML:0,TEL:1):";
int type;
cin >> type;
cin.ignore(256, '\n');
phone->set_type((add_contact::AddContactReq_Phone_PhoneType)type);
}
}
void menu()
{
std::cout << "-----------------------------------------------------" << std::endl
<< "--------------- 請(qǐng)選擇對(duì)通訊錄的操作 ----------------" << std::endl
<< "------------------ 1、新增聯(lián)系? --------------------" << std::endl
<< "------------------ 2、刪除聯(lián)系? --------------------" << std::endl
<< "------------------ 3、查看聯(lián)系?列表 ----------------" << std::endl
<< "------------------ 4、查看聯(lián)系?詳細(xì)信息 ------------" << std::endl
<< "------------------ 0、退出 --------------------------" << std::endl
<< "-----------------------------------------------------" << std::endl;
}
5. 代碼實(shí)現(xiàn)服務(wù)端
add_contact.proto
syntax="proto3";
package add_contact;
message AddContactReq{
string name = 1 ;
int32 age = 2 ;
message Phone{
string number = 1;
enum PhoneType{
MP=0;
TEL=1;
}
PhoneType type =2;
}
repeated Phone phone = 3;
}
message AddContactResp{
bool success = 1; // 添加聯(lián)系人是否成功
string error_desc =2 ;// 錯(cuò)誤信息
string uid =3 ; // 聯(lián)系人唯一序號(hào)
}
utils.h 工具類文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-518101.html
#include <iostream>
#include <sstream>
#include <random>
namespace Contact_Utils
{
class Utils
{
public:
/// 生成一個(gè)唯一標(biāo)識(shí)符,用于賦值uid
static std::string generateUUID(size_t len)
{
// 使用隨機(jī)數(shù)生成器生成隨機(jī)數(shù)種子
std::random_device device;
std::mt19937 generator(device());
// 使用16進(jìn)制表示的48位的隨機(jī)數(shù)
// std::hex是C++中的一個(gè)std::ios_base標(biāo)志,用于指定輸出流以十六進(jìn)制形式輸出整數(shù)。當(dāng)使用該標(biāo)志時(shí),輸出流中的整數(shù)將以十六進(jìn)制表示。例如,當(dāng)輸出整數(shù)0x123時(shí),使用std::hex標(biāo)志會(huì)將其輸出為字符串"123",而不是"291"。在上述示例代碼中,std::hex被用于指定std::ostringstream以十六進(jìn)制形式輸出整數(shù),從而生成16進(jìn)制表示的48位唯一標(biāo)識(shí)符。
std::ostringstream uuid;
uuid << std::hex;
for (size_t i = 0; i < len; ++i)
{
uuid << (generator() & 0xf);
}
return uuid.str();
}
};
}
main.cc文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-518101.html
#include "httplib.h"
#include "add_contact.pb.h"
#include "contactException.h"
#include <iostream>
#include <string>
#include"utils.h"
using namespace std;
using namespace httplib;
const string IP = "127.0.0.1";
// const string IP = "0.0.0.0";
// const string IP = "192.139.99.192";
const int PORT = 6666;
void printContact(add_contact::AddContactReq &request);
int main()
{
// 接收請(qǐng)求
Server svr;
// 處理請(qǐng)求
// 注冊(cè)post的回調(diào)函數(shù)
svr.Post("/contacts/add", [](const Request &req, Response &resp)
{
cout<<"收到Post請(qǐng)求!"<<endl;
add_contact::AddContactResp response;
add_contact::AddContactReq request;
try
{
if(!request.ParseFromString(req.body)){
throw ContactException("方序序列化失敗!");
}
// 持久化聯(lián)系人
printContact(request);
// 構(gòu)造 response : res.body
response.set_success(true);
response.set_uid(Contact_Utils::Utils::generateUUID(12));
// 序列化 response
string response_str;
if(!response.SerializePartialToString(&response_str))
{
throw ContactException("序列化失敗!");
}
resp.status=200;
resp.body=response_str;
resp.set_header("Content-Type","application/protobuf");
}
catch (ContactException &e)
{
resp.status=500;
response.set_success(false);
response.set_error_desc(e.what());
string response_str;
if(response.SerializePartialToString(&response_str)){
resp.body=response_str;
resp.set_header("Content-Type","application/protobuf");
}
cout<<"/contacts/add 發(fā)生異常,異常信息:"<<e.what()<<endl;
} });
// 生成resp,
// 并發(fā)送resp,
svr.listen(IP.c_str(), PORT);
}
void printContact(add_contact::AddContactReq &req)
{
cout << "添加聯(lián)系人成功\n-->姓名:" << req.name() << "-->年齡:" << req.age() << endl;
cout << "-----電話號(hào)碼-------" << endl;
for (int i = 0; i < req.phone_size(); i++)
{
cout << "電話" << i << ":" << req.phone(i).number() << "(type:" <<req.phone(i).PhoneType_Name(req.phone(i).type())<< ")" << endl;
}
}
到了這里,關(guān)于網(wǎng)絡(luò)通訊錄服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!