protobuf和socket通信简单实例

  protobuf是 Google 公司内部的混合语言数据标准,可以用来定义通信的协议,由于其有序列化和反序列化的操作,减小了存储或通信的数据量,从而达到高效运行的目的。
  此实例在ubuntu18.04下正常运行,其它操作系统没有经过测试,无法保证正常运行。

protobuf安装

  protobuf的下载地址,如图1所示的安装包,安装可参考官网官网安装说明







图1

  下载完毕解压后,在主文件夹下运行以下命令进行安装。

 ./configure
make
make check
sudo make install
sudo ldconfig # refresh shared library cache.

编辑程序及编译

  本文参考protobuf官网的教程,使用C++编写socket的服务端,python编写socket的客户端,最终实现服务端接收来自客户端的protobuf数据流。总共有4个文件(addressbook.proto、server.cpp、client.py、CMakeLists.txt)需要编写,其它文件通过命令自动生成,步骤如下:

  1. 创建工作空间文件夹protobuf_socket,并在protobuf_socket下创建必要的文件。

mkdir protobuf_socket
touch addressbook.proto
touch server.cpp
touch client.py
touch CMakeLists.txt

  生成的文件目录如图2所示。







图2

  1. 在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 [default = MOBILE];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

  1. 在server.cpp文件中添加如下内容:

#include <iostream>
#include <fstream>
#include <string>
#include <boost/asio.hpp>

#include "addressbook.pb.h"

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 << "Person ID: " << person.id() << endl;
        cout << "  Name: " << person.name() << endl;
        if (person.has_email()) {
            cout << "  E-mail address: " << 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 << "  Mobile phone #: ";
                    break;
                case tutorial::Person::HOME:
                    cout << "  Home phone #: ";
                    break;
                case tutorial::Person::WORK:
                    cout << "  Work phone #: ";
                    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[MAX_SIZE];
        size_t len = socket.read_some(buffer(buf), ec);
        if(len >= MAX_SIZE) {
            std::cout << "data is too big, receive failed!" << std::endl;
            continue;
        }
        // 如果出错,打印出错信息
        if(ec) {
            std::cout << "receive error: " << boost::system::system_error(ec).what() << std::endl;
            continue;
        }

        tutorial::AddressBook address_book;
        if(address_book.ParseFromArray(buf, len) != 1) {    //序列化
            std::cout << "Failed to parse address book." << std::endl;
        }

        ListPeople(address_book);
    }
    
    // 可选项:删除所有protobuf分配的全局对象
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

  1. 在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(("localhost", 5000))

# 手动输入消息并打印
def PromptForAddress(person):
    person.id = int(input("Enter person ID number: "))
    person.name = input("Enter name: ")

    email = input("Enter email address (blank for none): ")
    if email != "":
        person.email = email

    while True:
        number = input("Enter a phone number (or leave blank to finish): ")
        if number == "":
            break

        phone_number = person.phones.add()
        phone_number.number = number

        type = input("Is this a mobile, home, or work phone? ")
        if type == "mobile":
            phone_number.type = addressbook_pb2.Person.PhoneType.MOBILE
        elif type == "home":
            phone_number.type = addressbook_pb2.Person.PhoneType.HOME
        elif type == "work":
            phone_number.type = addressbook_pb2.Person.PhoneType.WORK
        else:
            print ("Unknown phone type; leaving as default value.")


if __name__ == "__main__":
    address_book = addressbook_pb2.AddressBook()

    PromptForAddress(address_book.people.add())

    msg = address_book.SerializeToString()
    if len(msg) >= MAX_SIZE:
        print('data is too big, server will not receive data!')

    client.send(msg)

  1. 在CMakeLists.txt文件下添加如下内容:

# Minimum CMake required
cmake_minimum_required(VERSION 3.1.3)

if(protobuf_VERBOSE)
  message(STATUS "Protocol Buffers Configuring...")
endif()

# CMake policies
cmake_policy(SET CMP0022 NEW)
# On MacOS use @rpath/ for target'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 "${CMAKE_CXX_FLAGS} -std=gnu++11")
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})

  1. 在protobuf_socket文件夹下编译addressbook.proto文件

protoc -I=. --cpp_out=. addressbook.proto

protoc -I=. --python_out=. addressbook.proto

 &ems; “-I=.”表示编译的文件(addressbook.proto)位于当前文件夹。“-cpp_out=.”表示将生成的文件放在当前文件夹。最终生成的文件目录如图3所示。







图3

  1. 在protobuf_socket文件夹下编译客户端server.cpp文件

mkdir build
cd build
cmake ..
make

运行程序

  1. 在protobuf_socket文件夹下运行服务端程序

./build/server

  1. 在protobuf_socket文件夹下运行客户端程序,并根据提示输入信息,在服务端将看到最终的信息。

python3 client.py