网络通讯录服务器

news2025/1/11 12:01:45

文章目录

  • 六、通讯录4.0实现---⽹络版
    • 1. 环境搭建
      • 1.1 安装Httplib库
      • 1.1升级 gcc
    • 2. 搭建简单的服务器
    • 3. 约定双端交互接⼝
    • 4. 代码实现客户端
    • 5. 代码实现服务端

六、通讯录4.0实现—⽹络版

简易版本

服务端完整版本

客户端完整版本

Protobuf还常⽤于通讯协议、服务端数据交换场景。那么在这个⽰例中,我们将实现⼀个⽹络版本的
通讯录,模拟实现客⼾端与服务端的交互,通过Protobuf来实现各端之间的协议序列化。
需求如下:

  • 客⼾端可以选择对通讯录进⾏以下操作:

    • 新增⼀个联系⼈
    • 删除⼀个联系⼈
    • 查询通讯录列表
    • 查询⼀个联系⼈的详细信息
  • 服务端提供增删查能⼒,并需要持久化通讯录。

  • 客⼾端、服务端间的交互数据使⽤Protobuf来完成。

如下图:

  • 客户端要有一个菜单,新增一个联系人…
  • 每个功能都有一对请求和响应协议.
  • 例如实现新增一个联系人,首先我们要设计message, 中间是网络传输.
  • 然后安装图形序号执行,客户端完成:1,2,3,7 ; 服务端完成:1,4.5,6

在这里插入图片描述

1. 环境搭建

1.1 安装Httplib库

Httplib库:cpp-httplib是个开源的库,是⼀个c++封装的http库,使⽤这个库可以在linux、windows平台下完成http客⼾端、http服务端的搭建。

使⽤起来⾮常⽅便,只需要包含头⽂件 httplib.h即可。编译程序时,需要带上-lpthread选项。

源码库地址:https://github.com/yhirose/cpp-httplib
镜像仓库:https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_github_accelerator

1.1升级 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 用老的编译器,要么编译不通过,要么直接运行报错
百度搜索:scl gcc devsettool升级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/
//启动: 细节,命令行启动只能在本会话有效
$ scl enable devtoolset-7 bash
$ gcc -v
//可选:如果想每次登陆的时候,都是较新的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
#添加下面的命令,每次启动的时候,都会执行这个scl命令
scl enable devtoolset-7 bash
or
scl enable devtoolset-8 bash
or
scl enable devtoolset-9 bash

2. 搭建简单的服务器

用来测试httplib

Client.cpp

#include"httplib.h"
#include<iostream>
#include<string>
using namespace std;
using namespace httplib;

const string IP = "127.0.0.1";// 监听所有的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;
  }
  // 请求./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;
  // 注册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协议

新增⼀个联系⼈:

[请求]
	Post /contacts/add 
	Content-Type: application/protobuf
	AddContactRequest
[响应]
	Content-Type: application/protobuf
	AddContactResponse

删除⼀个联系⼈:

[请求]
	Post /contacts/del 
	Content-Type: application/protobuf
	DelContactRequest
[响应]
	Content-Type: application/protobuf
	DelContactResponse

查询通讯录列表:

[请求]
	GET /contacts/find-all
[响应]
	Content-Type: application/protobuf
	FindAllContactsResponse

查询⼀个联系⼈的详细信息:

[请求]
	Post /contacts/find-one 
	Content-Type: application/protobuf
	FindOneContactRequest
[响应]
	Content-Type: application/protobuf
	FindOneContactResponse

4. 代码实现客户端

这里我们只实现了新增⼀个联系⼈模块,完整代码在码云.

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; // 添加联系人是否成功
  string error_desc =2 ;// 错误信息
  string uid =3 ; // 联系人序号
}

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"; // 监听所有的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 << "--------请选择:";
    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 << "--->操作通讯录时发生异常" << endl
           << "--->异常信息:" << e.what() << endl;
    }
  }
}
void addContact()
{
  Client client(IP, PORT);
  //构造 req
  add_contact::AddContactReq req;
  buildAddContactReq(&req);

  // 序列化 req
  string req_str;
  if (!req.SerializePartialToString(&req_str))
  {
    throw ContactException("req 序列化失败");
  }

  // 发起post调用
  auto ret = client.Post("/contacts/add", req_str, "Content-Type: application/protobuf");
  if (!ret)
  {
    string err_desc;
    err_desc.append("Post /contacts/add 请求失败! 错误信息:")
        .append(/*httplib::to_string(ret.error())当前httplib没有该函数*/
                to_string(ret.error()));
    throw ContactException(err_desc);
  }
  // 方序列号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 调用失败")
        .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 调用失败")
        .append(std::to_string(ret->status))
        .append("(")
        .append(ret->reason)
        .append(")")
        .append("错误信息:")
        .append(resp.error_desc());

    throw ContactException(err_desc);
  }
  else if (!resp.success())
  {
    string err_desc;
    err_desc.append("/contacts/add 结果异常\t异常原因")
        .append(std::to_string(ret->status))
        .append("(")
        .append(resp.error_desc())
        .append(")");
    throw ContactException(err_desc);
  }

  // 结果打印

  cout<<"新添加的联系人"<<resp.uid()<<"成功"<<endl;

}

void buildAddContactReq(add_contact::AddContactReq* req)
{
  cout << "请输入姓名:";
  string name;
  getline(std::cin,name);
  cout << "请输入年龄:";
  int age;
  cin >> age;
  cin.ignore(256, '\n');
  int i = 1;
  req->set_age(age);
  req->set_name(name);
  while (true)
  {
    cout << "请输入手机号码" << i++ << ":";
    string number;
     getline(std::cin,number);
    if (number.empty())
    {
      //cout << "输入联系人完成\n";
      break;
    }
    add_contact::AddContactReq_Phone *phone = req->add_phones();
    phone->set_number(number);
    cout << "请输入手机类型(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
            << "--------------- 请选择对通讯录的操作 ----------------" << std::endl
            << "------------------ 1、新增联系⼈ --------------------" << std::endl
            << "------------------ 2、删除联系⼈ --------------------" << std::endl
            << "------------------ 3、查看联系⼈列表 ----------------" << std::endl
            << "------------------ 4、查看联系⼈详细信息 ------------" << std::endl
            << "------------------ 0、退出 --------------------------" << std::endl
            << "-----------------------------------------------------" << std::endl;
}

5. 代码实现服务端

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; // 添加联系人是否成功
  string error_desc =2 ;// 错误信息
  string uid =3 ; // 联系人唯一序号
}

utils.h 工具类


#include <iostream>
#include <sstream>
#include <random>

namespace Contact_Utils
{
  class Utils
{
public:

	/// 生成一个唯一标识符,用于赋值uid
  static std::string generateUUID(size_t len)
  {
    // 使用随机数生成器生成随机数种子
    std::random_device device;
    std::mt19937 generator(device());

    // 使用16进制表示的48位的随机数

    // std::hex是C++中的一个std::ios_base标志,用于指定输出流以十六进制形式输出整数。当使用该标志时,输出流中的整数将以十六进制表示。例如,当输出整数0x123时,使用std::hex标志会将其输出为字符串"123",而不是"291"。在上述示例代码中,std::hex被用于指定std::ostringstream以十六进制形式输出整数,从而生成16进制表示的48位唯一标识符。
    std::ostringstream uuid;
    uuid << std::hex;
    for (size_t i = 0; i < len; ++i)
    {
      uuid << (generator() & 0xf);
    }

    return uuid.str();
  }
};
}

main.cc

#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()
{
  // 接收请求
  Server svr;

  // 处理请求
  // 注册post的回调函数
  svr.Post("/contacts/add", [](const Request &req, Response &resp)
           {
            cout<<"收到Post请求!"<<endl;
            add_contact::AddContactResp response;
            add_contact::AddContactReq  request;
             try
             {
              if(!request.ParseFromString(req.body)){
                throw ContactException("方序序列化失败!");
              }
              // 持久化联系人
              printContact(request);

              // 构造 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 发生异常,异常信息:"<<e.what()<<endl;
             } });
  // 生成resp,

  // 并发送resp,

  svr.listen(IP.c_str(), PORT);
}

void printContact(add_contact::AddContactReq &req)
{
  cout << "添加联系人成功\n-->姓名:" << req.name() << "-->年龄:" << req.age() << endl;
  cout << "-----电话号码-------" << 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;
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/705630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【物联网无线通信技术】802.11无线安全认证

本文由简入繁介绍了IEEE802.11i无线局域网安全技术的前世今生&#xff0c;帮助路由器开发者对WLAN的加密安全策略有一个概念上的认知&#xff0c;能够更好地分析STA掉线以及漫游等问题。 目录 WEP WPA WPA/WPA2-PSK认证过程 802.11i WEP WEP是Wired Equivalent Privacy的简…

基于matlab使用扩张卷积的语义分割(附源码)

一、前言 使用扩张卷积训练语义分割网络。语义分割网络对图像中的每个像素进行分类&#xff0c;从而生成按类分割的图像。语义分割的应用包括自动驾驶的道路分割和医疗诊断的癌细胞分割。 二、加载训练数据 该示例使用 32 x 32 个三角形图像的简单数据集进行说明。数据集包括…

Quiz 14_2-2: Using Web Services | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Quiz 14_2-2: Using Web Services单选题&#xff08;1-15&#xff09;操作题Autograder 1: Extract Data from JSONAutograder 2: Calling a JSON API Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;P…

12 | 领域建模:如何用事件风暴构建领域模型?

还记得微服务设计为什么要选择 DDD 吗&#xff1f; 其中有一个非常重要的原因&#xff0c;就是采用 DDD 方法建立的领域模型&#xff0c;可以清晰地划分微服务的逻辑边界和物理边界。可以说&#xff0c;在 DDD 的实践中&#xff0c;好的领域模型直接关乎微服务的设计水平。因此…

代码随想录算法训练营第4天| 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

今日学习的文章链接&#xff0c;或者视频链接 第二章 链表part02 自己看到题目的第一想法 看完代码随想录之后的想法 24: 注意链表的操作&#xff1a; class Solution { public:ListNode* swapPairs(ListNode* head) {auto dummyhead new ListNode(0,head);auto prev …

Apache Doris (五) :Doris分布式部署(二) FE扩缩容

目录 1. 通过MySQL客户端连接Doris ​​​​​​​​​​​​​​2. FE Follower扩缩容 ​​​​​​​3. FE Observer 扩缩容 ​​​​​​​​​​​​​​4. FE扩缩容注意点 进入正文之前&#xff0c;欢迎订阅专题、对博文点赞、评论、收藏&#xff0c;关注IT贫道&#…

超详细|粒子群优化算法及其MATLAB实现

本文主要介绍粒子群算法的背景与理论&#xff0c;并结合对应部分的MATLAB程序对其实现流程做了阐述&#xff0c;代码获取方式见文末。 00 文章目录 1 粒子群优化算法 2 问题导入 3 MATLAB程序实现 4 改进策略 5 展望 01 粒子群优化算法 1.1 粒子群优化算法背景 近年来&…

亿发软件:智慧中药房信息化建设,中医药安全煎煮解决方案

传统的中药饮片煎煮服用较为繁琐&#xff0c;局限了诸多人群的使用。为了优化医疗服务,并满足患者不断增长的中医药需求&#xff0c;智慧中药房的概念应运而生。智慧化中药房通过信息化和自动化相结合&#xff0c;旨在提高中药处方的管理和效率。下面就让我们了解一下中药配方颗…

创建一个nuxt项目

yarn create nuxt-app ssr 启动项目 如果使用npm run start 可能会报错&#xff0c;提示需要配置为开发环境 可以先执行npm run dev 看看

HPM6750系列--第三篇 搭建MACOS编译和调试环境

一、目的 在上一篇《HPM6750系列--第二篇 搭建Ubuntu开发环境》我们介绍了Ubuntu上开发HPM6750&#xff0c;本篇主要介绍MAC系统上的开发环境的搭建过程&#xff0c;整个过程和Ubuntu上基本类似。 二、准备 首先我们在Mac电脑上打开一个terminal&#xff0c;然后创建一个…

苹果Vision Pro手势+眼球融合交互的奥秘

毫无疑问&#xff0c;Vision Pro在眼球追踪手势的融合交互体验上&#xff0c;给AR/VR头戴设备带来了新突破&#xff0c;在用户体验上的提升非常明显。 ​那么&#xff0c;为什么Vision Pro上这一功能会被如此值得关注呢&#xff1f;为了弄清楚&#xff0c;我们先来看看主流VR设…

037:mapboxGL输入经纬度,地址坐标转换,弹出位置点地址信息

第037个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中输入经纬度,地址坐标转换,弹出位置点地址信息. 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共158行)相关API参考:专栏目标示例效果 配置方式…

配置Jenkins slave agent(通过jnlp)方式连接

上一章&#xff0c;使用ssh的方式添加了两个agent&#xff0c;并都成功完成了构建任务&#xff0c;这一章使用jnlp的方式配置agent&#xff0c;jnlp方式配置agent有个好处&#xff0c;就是agent是主动去找到Master请求连接的&#xff0c;master->agent的通道可以配置一个age…

【AUTOSAR】BMS开发实际项目讲解(二十九)----电池管理系统电池充放电功率控制与SOC

电池充放电功率控制 关联的系统需求 Sys_Req_3901、Sys_Req_3902、Sys_Req_3903、Sys_Req_3904; 功能实现描述 电池充放电功率控制主要包括以下内容&#xff1a; 60S可用功率 参见[CELL] 30S可用功率 参见[CELL] 10S可用功率 参见[CELL] SOP算法 ID Description ASI…

LVDS接口ADC要点数据采集流程

一&#xff1a;要点 1.如果两片AD&#xff0c;四路输出做了同步化处理之后&#xff0c;一定只用同步化模块读时钟&#xff08;一片AD的时钟&#xff09;去上传数据&#xff0c;到DDR3模块。 2.ADS42和LTC2208的ADC的数据伴随时钟都来源与输入的采样时钟&#xff08;有些采样时…

虚拟机上用docker + nginx跑前端并支持https和http

情况是这样&#xff0c;我在虚拟机上&#xff0c;使用docker跑前端&#xff0c;需要这个前端支持https&#xff0c;原http的话自动跳转到https。另外&#xff0c;前端部署使用了负载均衡&#xff0c;即使用了3个docker跑前端&#xff1a;1个入口&#xff0c;另外2个是前端&…

Wi-Fi模块(ESP8266)详解

Wi-Fi模块——ESP8266 0. Wi-Fi模块概述1. 常见的Wi-Fi模块2. ESP8266模块2.1 概念2.2 特点 3. STM32F103C8T6使用ESP8266进行无线通信的示例代码 0. Wi-Fi模块概述 Wi-Fi模块是一种用于无线通信的设备&#xff0c;它能够通过Wi-Fi技术实现设备之间的无线数据传输和互联网连接…

java面试Day18

1.什么是 MySQL 执行计划&#xff1f;如何获取执行计划并对其进行分析&#xff1f; MySQL 执行计划是指 MySQL 查询优化器生成的一份详细的查询执行计划&#xff0c;它展示了 MySQL 在执行查询时所采取的具体执行计划&#xff0c;包括表的访问顺序、数据读取方式、使用的索引、…

大模型入局传统算法,LLMZip基于LLaMA-7B实现1MB文本压缩率90%!

论文链接&#xff1a; https://arxiv.org/abs/2306.04050 随着以ChatGPT、GPT-4为代表的AI大模型逐渐爆火进入公众视野&#xff0c;各行各业都开始思考如何更好的使用和发展自己的大模型&#xff0c;有一些评论甚至认为大模型是以人工智能为标志的第四次产业革命的核心竞争产品…

11.窗口看门狗-WWGD

1.窗口看门狗概述&#xff1a; &#xff08;1&#xff09;之所以称为窗口是因为其喂狗时间是一个有上下限的范围内(窗口)&#xff0c;可以通过设定相关寄存器&#xff0c;设定其上限时间(下限时间固定)。喂狗的时间不能过早也不能过晚。而独立看门狗限制喂狗时间在0-x内&#…