1. 前言

本章节将实现C++版本的个人注册页面的gRPC服务器
更多基础知识可以参考上一节: GRPC 快速入门

2. 定义服务

要定义服务,需要在login.proto文件中指定一个名为service的内容,另外我们还需要定义一个LoginInfo的消息体用来传递信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
syntax = "proto3"; //指定版本信息,不指定会报错

package tutorial; //package声明符,用来防止不同的消息类型有命名冲突

// 注册消息体
message LoginInfo {
string usrname = 1;
string password = 2;
}

// login response
message LoginResponse {
// 状态码,0表示成功
int32 status_code = 1;
// 返回信息,包含成功或错误描述
string message = 2;
}

// 定义服务
service LoginService {
rpc login(LoginInfo) returns (LoginResponse);
}

将.proto 翻译成C++文件

1
2
protoc -I . --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` login.proto
protoc -I . --cpp_out=. login.proto

运行此命令将在您的当前目录中生成以下文件:

  • login.pb.h: 声明生成的客户端的头文件
  • login.pb.cc: 包含客户端的实现
  • login.grpc.pb.h: 声明生成的服务器端的头文件
  • login.grpc.pb.cc: 包含服务器端的实现

如果要生成python 文件

1
2
3
 # 安装依赖
pip3 install grpcio-tools
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. login.proto

3. 服务器代码

下面我们来编写服务器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <grpcpp/grpcpp.h>
#include "login.pb.h"
#include "login.grpc.pb.h"

class LoginServiceImpl final : public tutorial::LoginService::Service
{
grpc::Status login(::grpc::ServerContext *context, const ::tutorial::LoginInfo *request, ::tutorial::LoginResponse *response)
override
{
std::cout << " usrname: " << request->usrname() << " password: " << request->password() << std::endl;
std::string msg("Login successfully!");
response->set_status_code(0);
response->set_message(msg);
return grpc::Status::OK;
}
};

int main(int argv, char **argc)
{
const std::string server_address = "localhost:50051";
LoginServiceImpl service = LoginServiceImpl();

grpc::ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);

std::unique_ptr<grpc::Server> server(builder.BuildAndStart());

std::cout << "Server listening on " << server_address << std::endl;

server->Wait();

return 0;
}

4. 客户端代码

下面我们来编写客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <grpcpp/grpcpp.h>
#include "login.pb.h"
#include "login.grpc.pb.h"
#include <unordered_map>

std::unordered_map<std::string, std::string> user_maps = {
{"张三", "123456"},
{"李四", "666666"},
};

int main(int argc, char **argv)
{
const std::string server_address = "localhost:50051";
grpc::ChannelArguments channel_args;
channel_args.SetMaxReceiveMessageSize(1024 * 1024 * 1024); // 1GB
channel_args.SetMaxSendMessageSize(1024 * 1024 * 1024);
auto channel = grpc::CreateCustomChannel(
server_address,
grpc::InsecureChannelCredentials(),
channel_args);
std::unique_ptr<tutorial::LoginService::Stub> stub = tutorial::LoginService::NewStub(channel);

for (auto user : user_maps)
{
tutorial::LoginInfo info;
tutorial::LoginResponse res;
info.set_usrname(user.first);
info.set_password(user.second);
grpc::ClientContext context;
grpc::Status status = stub->login(&context, info, &res);
if (!status.ok())
{
std::cerr << "检查网络是否发生错误: " << status.error_message() << std::endl;
continue;
}

if (!res.status_code() == 0)
{
std::cerr << user.first << "注册失败: " << res.message() << std::endl;
continue;
}

std::cerr << user.first << "注册成功: " << res.message() << std::endl;
continue;
}
return 0;
}

5. 编译

测试过Makefile,编译依赖库太多,不建议使用。建议使用cmake比较好处理, CMakeLists.txt 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
cmake_minimum_required(VERSION 3.8)

project(Tutorial C CXX)

set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
find_package(Threads REQUIRED)
set(_REFLECTION gRPC::grpc++_reflection)
find_package(Protobuf CONFIG REQUIRED)
set(_GRPC_GRPCPP gRPC::grpc++)
find_package(gRPC CONFIG REQUIRED)

set(PROTO_SRCS login.grpc.pb.cc login.pb.cc)

set(CMAKE_CXX_STANDARD 17)

add_executable(login_client ${PROTO_SRCS} client.cpp)
add_executable(login_server ${PROTO_SRCS} server.cpp)

target_link_libraries(login_client
absl::flags
absl::flags_parse
${_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF}
)

target_link_libraries(login_server
absl::flags
absl::flags_parse
${_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF}
)
1
2
mkdir build && cd build
cmake ..

编译服务器和客户端

1
make login_server login_client

先运行服务器,再运行客户端,服务器端看到正常打印时,恭喜你完成了第一个C++通讯程序

6. 故障解决

故障一:
在编译时可能会遇到protocol版本冲突,出现如下报错(error: #error PROTOBUF_VERSION was previously defined):
在这里插入图片描述
说明grpc中使用的protocol和本地版本不一致,需要保证两个版本一致,两个解决方法

  1. 方法一:
    或者卸载掉本地版本,卸载指令
    先查看位置
1
2
which protoc
# protoc: /usr/local/bin/protoc

删除可执行和库

1
2
3
rm -rf /usr/local/bin/protoc #可执行
sudo rm -rf /usr/local/include/google/protobuf #头文件
sudo rm -rf /usr/local/lib/libproto* # 库文件

配置grpc的bin到path中

1
export PATH=$PATH:<your grpc install path>/bin

然后查看protoc是否是使用的grpc中的protoc

1
which protoc 
  1. 方法二
    先检查你的grpc使用的protoc版本,然后安装相应版本
1
2
<your grpc install path>/bin/protoc --version
# libprotoc 25.1

故障二:
在这里插入图片描述
使用C++17可以解决