使用Protobuf进行socket通信

参考大神文档

本文实现在Linux系统上客户端与服务端通过protobuf进行socket通信的简单实例。

Protobuf

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库:

  1. 先去Boost官网下载最新的Boost版本, 我下载的是boost_1_78_0版本, 解压.

  2. 进入解压后目录: cd boost_1_78_0, 执行下面的命令:

./bootstrap.sh --prefix=path/to/installation/prefix

prefix的值是你希望安装boost的路径, 不开启此参数的话默认安装在 /usr/local 下. 我安装在 /home/boost/boost_1_78_0目录下:

 ./bootstrap.sh --prefix=/home/boost/boost_1_78_0
  1. 接着执行:

注意: home目录不要用 ~ 表示, 编译脚本不识别 ~, 会在当前目前新建一个名为 ‘~’ 的目录。

./b2 install

这条命令把boost的头文件文件夹 include/ 安装在prefix定义的目录中, 并且会编译所有的boost模块, 并将编译好的库文件夹 lib/ 也放在prefix定义的目录中。

socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让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 [default = MOBILE];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 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;
}

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)

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})

编译文件

在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

根据提示输入信息,可以在服务端看到最后的输出信息。
在这里插入图片描述

最后运行结果!
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值