前序
公司的protobuf相关文件实在windows下生成的,本文是个人想学习Linux下如何生成相关文件,把自己安装步骤和简单实用做一下记录。
我基于ubuntu 20.04.1
虚拟机,g++ 9.3.0
来讲解。
1. 请确保你的编译器支持c++11及其以上,尽量支持c++14。
2. 下文在涉及编译器版本编写特定代码处会加以说明。
protobuf3简介
简介引用这位博主的文章。
步骤
下载及安装
本次我先从github中下载cpp版本的源代码的protobuf-cpp-version.tar.gz/zip (我用的protobuf-cpp-3.15.6.zip
) 到windows中。
然后在虚拟机中,以root
登录。将刚刚下好的文件拷到/opt/
目录,然后将上述压缩包解压至此。
unzip protobuf-cpp-3.15.6.zip
进入解压后的文件目录
cd protobuf-3.15.6
./configure
执行
它默认是安装在 /usr/local
, 然后make
一下,编译时间略长,可以指定核心数量来减少编译时间。
./configure
make && make install
环境变量
vi /etc/profile ,追加以下内容。
#(动态库搜索路径) 程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/
#(静态库搜索路径) 程序编译期间查找动态链接库时指定查找共享库的路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/
#执行程序搜索路径
export PATH=$PATH:/usr/local/protobuf/bin/
#c程序头文件搜索路径
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
#c++程序头文件搜索路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/
#pkg-config 路径
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
vi /etc/ld.so.conf,追加/usr/local/protobuf/lib
将文件/etc/ld.so.conf
列出的路径下的库文件缓存到/etc/ld.so.cache
以供使用,因此当安装完一些库文件,或者修改/etc/ld.so.conf
增加了库的新搜索路径,需要运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache
中,如果没做,可能会找不到刚安装的库。
/sbin/ldconfig -v
验证protobuf环境配置成功
protoc --version
出现以下输出表示环境配置成功。
root@ubuntu:~# protoc --version
libprotoc 3.15.6
至此pb3安装完成!
C++演示
pb文件生成
我一般在/home/yz/share/
下工作,于是我在它里面创建一个protobuf_test
目录,然后进去创建一个msg.proto
的文件
syntax = "proto3";
package student_msg;
message info
{
int32 class_room = 1;
string student_id = 2;
}
message student
{
int32 index = 1;
info info = 2;
}
message student_list
{
repeated student students = 1;
}
执行下面命令,将msg.proto映射为相应头文件和源文件
protoc --cpp_out=. msg.proto
生成msg.pb.h
和 msg.pb.cc
C++开发,编译及运行
创建一个cpp文件,码代码
vi main.cpp
#include <iostream>
#include <memory>
#include <random>
#include <string>
#include <fcntl.h>
#include <msg.pb.h>
#ifdef __LINUX__
#include <unistd.h>
#endif
int main()
{
// 随机数种子
std::default_random_engine seed{};
std::uniform_int_distribution<unsigned> uniform(0, 9);
auto student_list{std::make_unique<student_msg::student_list>()};
for (size_t i{0}; i < 10; ++i)
{
// 填充每位同学的信息
auto student = student_list->add_students();
student->set_index(i+1);
auto student_info = student->mutable_info();
student_info->set_class_room(uniform(seed)); // 随机产生班级
student_info->set_student_id(std::string("No.") + std::to_string(100 + i));
}
// 写入当前目录下的文件中
int fd{::open("./store.txt", O_CREAT | O_RDWR, 0777)};
student_list->SerializePartialToFileDescriptor(fd);
::close(fd);
// 打开文件,看看之前的录入是否有效
fd = ::open("./store.txt", O_RDWR, 0777);
student_list.reset(new student_msg::student_list);
student_list->ParseFromFileDescriptor(fd);
for (size_t i{0}; i < student_list->students_size(); ++i)
{
std::cout << " student.index: " << student_list->students(i).index()
<< " student.classroom: " << student_list->students(i).info().class_room()
<< " student.id: " << student_list->students(i).info().student_id()
<< std::endl;
}
::close(fd);
return 0;
}
上文中第18行代码,需要C++14
支持。如果你的编译器版本不够,请换为普通的unique_ptr
的构造。
学会实用cmake,解决因为找不到系统头文件和库所带来的问题,这里推荐我自己学习过程中写的文章cmake
在当前目录创建CMakeLists.txt
,写入
cmake_minimum_required(VERSION 3.15)
project(PROTO)
aux_source_directory(${PROJECT_SOURCE_DIR} DIR_MAIN_SRCS)
# -I
include_directories(${PROJECT_SOURCE_DIR})
# parameterss
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAG ${CMAKE_CXX_FLAG} "-O2 -Wall -g ")
# macro
add_definitions(-D __LINUX__)
# -L
set(LIB_DIR /usr/local/lib)
link_directories(${LIB_DIR})
# -o
add_executable(proto ${DIR_MAIN_SRCS})
# -l
set(LIB_NAME protobuf pthread)
target_link_libraries(proto ${LIB_NAME})
在当前目录创建文件夹build
mkdir build
cd build
cmake ..
make
./proto
点斜杠执行proto后的输出为
student.index: 1 student.classroom: 0 student.id: No.100
student.index: 2 student.classroom: 1 student.id: No.101
student.index: 3 student.classroom: 7 student.id: No.102
student.index: 4 student.classroom: 4 student.id: No.103
student.index: 5 student.classroom: 5 student.id: No.104
student.index: 6 student.classroom: 2 student.id: No.105
student.index: 7 student.classroom: 0 student.id: No.106
student.index: 8 student.classroom: 6 student.id: No.107
student.index: 9 student.classroom: 6 student.id: No.108
student.index: 10 student.classroom: 9 student.id: No.109
对了,展示一下还没有在build
目录下cmake
前的我的目录吧,虽然没有把源文件、头文件分离,但是这里只是做测试,不用在意细节~
.
├── build
├── CMakeLists.txt
├── main.cpp
├── msg.pb.cc
├── msg.pb.h
└── msg.proto
更新 增加一个网络传输版本:
服务端代码:
#include <arpa/inet.h>
#include <msg.pb.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
const uint16_t PORT = 6666;
const uint16_t BUFFER_LEN = 1000;
const int LISTEN_PAD = 1;
const char* IPADDR = "127.0.0.1";
const std::string bye("get your massage, and good bye.");
auto main() -> int {
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in sock{};
sock.sin_family = AF_INET;
sock.sin_port = htons(PORT);
sock.sin_addr.s_addr = ::inet_addr(IPADDR);
if (-1 == ::bind(fd, reinterpret_cast<sockaddr*>(std::addressof(sock)),
sizeof(sockaddr_in))) {
::close(fd);
::exit(-1);
}
if (-1 == ::listen(fd, LISTEN_PAD)) {
::close(fd);
::exit(-1);
}
std::cout << "TURN TO LISTEN STATE WAITING FOR ONE PEER CONNECT..."
<< std::endl;
sockaddr_in peer_sock{};
uint32_t peer_addr_size = sizeof(sockaddr_in);
int cfd = ::accept(fd, reinterpret_cast<sockaddr*>(std::addressof(peer_sock)),
&peer_addr_size);
if (-1 == cfd) {
::close(fd);
::exit(-1);
}
std::cout << "CONNECT ESTABLISH TO PEER:" << cfd << std::endl
<< "IP:" << peer_sock.sin_port << " WAITING FOR IT SEND MESSAGE..."
<< std::endl;
auto buffer = new uint8_t[BUFFER_LEN]{0};
auto read_len = BUFFER_LEN;
{
read_len = ::read(cfd, buffer, BUFFER_LEN);
if (0 >= read_len) {
::close(cfd);
::close(fd);
return 0;
}
// 对端没关闭时,read阻塞
auto student_list{std::make_unique<student_msg::student_list>()};
if (student_list->ParseFromArray(buffer, read_len)) {
for (size_t i{0}; i < student_list->students_size(); ++i) {
std::cout << " student.index: " << student_list->students(i).index()
<< " student.classroom: "
<< student_list->students(i).info().class_room()
<< " student.id: "
<< student_list->students(i).info().student_id() << std::endl;
}
::write(cfd, bye.data(), bye.size());
sleep(1);
::close(cfd);
::close(fd);
return 0;
}
::close(cfd);
::close(fd);
return 0;
}
}
客户端代码:
#include <arpa/inet.h>
#include <msg.pb.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <memory>
#include <random>
#include <thread>
const uint16_t PORT = 6666;
const uint16_t BUFFER_LEN = 1000;
const char* IPADDR = "127.0.0.1";
using namespace std::literals;
auto main() -> int {
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = ::inet_addr(IPADDR);
if (-1 == ::connect(fd, reinterpret_cast<sockaddr*>(std::addressof(server)),
sizeof(sockaddr_in))) {
std::cout << "error:" << errno << std::endl;
::exit(-1);
}
auto student_list{std::make_unique<student_msg::student_list>()};
// 组织protobuf
{
std::default_random_engine seed{};
std::uniform_int_distribution<unsigned> uniform(0, 9);
for (size_t i{0}; i < 10; ++i) {
auto student = student_list->add_students();
student->set_index(i + 1);
auto student_info = student->mutable_info();
student_info->set_class_room(uniform(seed));
student_info->set_student_id(std::string("No.") +
std::to_string(100 + i));
}
}
std::string msgInput{};
student_list->SerializePartialToString(std::addressof(msgInput));
if (-1 != fd and -1 == ::write(fd, msgInput.data(), msgInput.size())) {
::close(fd);
::exit(-1);
}
// 收服务端回复
std::unique_ptr<char> bufferGet(new char[50]{0});
int readLen = -1;
readLen = ::read(fd, bufferGet.get(), BUFFER_LEN);
// 对端关闭时且本端fd没关闭,read读到数据为0,对端没关闭则阻塞都实际长度,读到-1失败
if (0 >= readLen) {
::close(fd);
::exit(-1);
}
std::cout << std::string(bufferGet.get(), readLen) << std::endl;
::close(fd);
return 0;
}
出于演示,期间很多可复用代码,并没封装,太懒了,直接写了一个脚本 里面使用g++
进行编译
#!/bin/bash
echo "compile server..."
g++ -g server.cc msg.pb.cc -I./ -L/usr/local/lib -lprotobuf -lpthread -o my_server
echo "compile client..."
g++ -g client.cc msg.pb.cc -I./ -L/usr/local/lib -lprotobuf -lpthread -o my_client
运行结果和之前大差不差