使用Protobuf进行socket通信
本文实现在Linux系统上客户端与服务端通过protobuf进行socket通信的简单实例。Protobuf介绍
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
Protobuf下载和安装在这里就不再赘述了,具体方法可以参见官网安装说明。
注意:第一次安装后直接用(python)应该会提示:ImportError: No module named google.protobuf,这是因为找不到对应的库路径导致,到下载的pb路径下,找到python路径,执行sudo python setup.py install,执行完后可以通过执行sudo python3 setup.py test检查是否有安装成功,如果最后提示
那么就是安装成功了,此时再导入对应的pb2.py文件即可使用。
boost库安装
boost是C++的一个准标准库,相当于STL的延续和扩充,它的设计理念和STL比较接近,都是利用泛型让复用达到最大化。不过对比STL,boost更加实用。STL集中在算法部分,而boost包含了不少工具类,可以完成比较具体的工作。
在后续的程序中使用到了boost库的部分功能,因此在这里先安装一下boost库
Linux下编译使用boost库:
[*]先去Boost官网下载最新的Boost版本, 我下载的是boost_1_78_0版本, 解压。
[*]进入解压后目录: cd boost_1_78_0, 执行下面的命令:
./bootstrap.sh --prefix=path/to/installation/prefixprefix的值是你希望安装boost的路径, 不开启此参数的话默认安装在 /usr/local 下. 我安装在 /home/boost/boost_1_78_0目录下:
./bootstrap.sh --prefix=/home/boost/boost_1_78_03.接着执行:
注意: home目录不要用 ~ 表示, 编译脚本不识别 ~, 会在当前目前新建一个名为 ‘~’ 的目录。
./b2 install这条命令把boost的头文件文件夹 include/ 安装在prefix定义的目录中, 并且会编译所有的boost模块, 并将编译好的库文件夹 lib/ 也放在prefix定义的目录中。
socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
图 socket在网络层次结构中的位置
图 socket交互流程
使用protobuf进行socket通信
项目结构
参考protobuf官网的教程,使用C++编写socket的服务端,python编写socket的客户端,最终实现服务端接收来自客户端的protobuf数据流。总共有以下4个文件需要编写,其它文件通过命令自动生成。
创建工作空间文件夹protobuf_socket,并在protobuf_socket下创建必要的文件。
mkdir protobuf_socket
touch addressbook.proto
touch server.cpp
touch client.py
touch CMakeLists.txt
文件目录如下所示
向四个文件中写入如下内容:
addressbook.proto
syntax = "proto2";
package tutorial;
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 ;
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}server.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <boost/asio.hpp>
#include &#34;addressbook.pb.h&#34;
using namespace boost::asio;
using namespace std;
#define MAX_SIZE 128 * 1024
// 打印输出
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.people_size(); i++) {
const tutorial::Person& person = address_book.people(i);
cout << &#34;Person ID: &#34; << person.id() << endl;
cout << &#34;Name: &#34; << person.name() << endl;
if (person.has_email()) {
cout << &#34;E-mail address: &#34; << person.email() << endl;
}
for (int j = 0; j < person.phones_size(); j++) {
const tutorial::Person::PhoneNumber& phone_number = person.phones(j);
switch (phone_number.type()) {
case tutorial::Person::MOBILE:
cout << &#34;Mobile phone #: &#34;;
break;
case tutorial::Person::HOME:
cout << &#34;Home phone #: &#34;;
break;
case tutorial::Person::WORK:
cout << &#34;Work phone #: &#34;;
break;
}
cout << phone_number.number() << endl;
}
}
}
int main(int argc, char* argv[]) {
// 验证库的版本与头文件编译的内容是否一致
GOOGLE_PROTOBUF_VERIFY_VERSION;
// 所有asio类都需要io_service对象
io_service iosev;
ip::tcp::acceptor acceptor(iosev, ip::tcp::endpoint(ip::tcp::v4(), 5000));
while(1) {
// socket对象
ip::tcp::socket socket(iosev);
// 等待直到客户端连接进来
acceptor.accept(socket);
// 显示连接进来的客户端
std::cout << socket.remote_endpoint().address() << std::endl;
boost::system::error_code ec;
// 接收数据
char buf;
size_t len = socket.read_some(buffer(buf), ec);
if(len >= MAX_SIZE) {
std::cout << &#34;data is too big, receive failed!&#34; << std::endl;
continue;
}
// 如果出错,打印出错信息
if(ec) {
std::cout << &#34;receive error: &#34; << boost::system::system_error(ec).what() << std::endl;
continue;
}
tutorial::AddressBook address_book;
if(address_book.ParseFromArray(buf, len) != 1) { //序列化
std::cout << &#34;Failed to parse address book.&#34; << std::endl;
}
ListPeople(address_book);
}
// 可选项:删除所有protobuf分配的全局对象
google::protobuf::ShutdownProtobufLibrary();
return 0;
}client.py
#! /usr/bin/python
#coding=utf-8
import socket
import addressbook_pb2
import sys
MAX_SIZE = 128 * 1024
#创建socket通信的对象
client = socket.socket()
#连接服务器的IP地址和端口号
client.connect((&#34;localhost&#34;, 5000))
# 手动输入消息并打印
def PromptForAddress(person):
person.id = int(input(&#34;Enter person ID number: &#34;))
person.name = input(&#34;Enter name: &#34;)
email = input(&#34;Enter email address (blank for none): &#34;)
if email != &#34;&#34;:
person.email = email
while True:
number = input(&#34;Enter a phone number (or leave blank to finish): &#34;)
if number == &#34;&#34;:
break
phone_number = person.phones.add()
phone_number.number = number
type = input(&#34;Is this a mobile, home, or work phone? &#34;)
if type == &#34;mobile&#34;:
phone_number.type = addressbook_pb2.Person.PhoneType.MOBILE
elif type == &#34;home&#34;:
phone_number.type = addressbook_pb2.Person.PhoneType.HOME
elif type == &#34;work&#34;:
phone_number.type = addressbook_pb2.Person.PhoneType.WORK
else:
print (&#34;Unknown phone type; leaving as default value.&#34;)
if __name__ == &#34;__main__&#34;:
address_book = addressbook_pb2.AddressBook()
PromptForAddress(address_book.people.add())
msg = address_book.SerializeToString()
if len(msg) >= MAX_SIZE:
print(&#39;data is too big, server will not receive data!&#39;)
client.send(msg)CMakeLists.txt
# Minimum CMake required
cmake_minimum_required(VERSION 3.1.3)
if(protobuf_VERBOSE)
message(STATUS &#34;Protocol Buffers Configuring...&#34;)
endif()
# CMake policies
cmake_policy(SET CMP0022 NEW)
# On MacOS use @rpath/ for target&#39;s install name prefix path
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif ()
# Clear VERSION variables when no VERSION is given to project()
if(POLICY CMP0048)
cmake_policy(SET CMP0048 NEW)
endif()
# Project
project(protobuf_test C CXX)
find_package(Boost COMPONENTS regex system REQUIRED)
# Add c++11 flags
if (CYGWIN)
set(CMAKE_CXX_FLAGS &#34;${CMAKE_CXX_FLAGS} -std=gnu++11&#34;)
else()
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
add_executable(server server.cpp addressbook.pb.cc)
target_link_libraries(server protobuf ${Boost_LIBRARIES})编译文件
在protobuf_socket文件夹下编译addressbook.proto文件
protoc -I=. --cpp_out=. addressbook.proto
protoc -I=. --python_out=. addressbook.proto“-I=”指定编译的文件(addressbook.proto)所处文件夹。“-cpp_out=.”表示将生成的文件放在指定文件夹,cpp与python分别生成基于cpp和python的编译产物 .表示当前文件夹。
生成的文件目录如下图所示。
在protobuf_socket文件夹下编译客户端server.cpp文件
mkdir build
cd build
cmake ..
make最终生成的文件目录如下图所示。
运行程序!
在protobuf_socket文件夹下运行服务端程序
./build/server 在protobuf_socket文件夹下运行客户端程序
python3 client.py根据提示输入信息,可以在服务端看到最后的输出信息。
最后运行结果!
参考大神文档
转载自 原文地址:https://blog.csdn.net/Stefanie_ttr/article/details/123892272
LinuxC/C++开发/架构师 面试题、学习资料、教学视频和学习路线图免费分享有需要的可以自行添加学习交流群973961276获取
C/C++Linux服务器开发/高级架构师系统性学习:https://ke.qq.com/course/417774?flowToken=1031343
页:
[1]