序列化结构(protobuf)实现一个TCP服务器(C++)

Protocol Buffers(protobuf)是一种由Google开发的用于序列化结构化数据的方法,通常用于在不同应用程序之间进行数据交换或存储数据。它是一种语言无关、平台无关、可扩展的机制,可以用于各种编程语言和环境中。

1、首先建立proto文件,syntax如果不标明proto3,则会默认使用proto2版本,在后面的使用过程中需要加上包名,以防止命名空间冲突,消息体中的序号表明在序列化数据中该变量出现的顺序。如果要规定该变量只有n中可能,可以使用枚举类型,例如表示人的性别男女。编辑 protoc -I=. --cpp_out=. message.proto
protobuf3文档中文译版

cpp 复制代码
syntax = "proto3";
package csj;
message MyMessage {
int32 id = 1;
string content = 2;
}

2、编写服务端,编辑 g++ -o server server.cpp message.pb.cc -lprotobuf

cpp 复制代码
#include "message.pb.h" // 你的protobuf生成的头文件
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
using namespace std;
 
// 接收protobuf消息
bool ReceiveProtobufMessage(int socket_fd, csj::MyMessage* message) {
    std::string serialized_message;
    const int BUFFER_SIZE = 1024; // 设置一个缓冲区大小
    char buffer[BUFFER_SIZE];
    
    ssize_t received = recv(socket_fd, buffer, BUFFER_SIZE, 0);
    if (received < 0) {
        std::cerr << "Failed to receive message." << std::endl;
        return false;
    }
    
    serialized_message.assign(buffer, received);

    if (!message->ParseFromString(serialized_message)) {
        std::cerr << "Failed to parse received message." << std::endl;
        return false;
    }
 
    return true;
}
 
int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        cerr<<"Error opening socket"<<endl;
        return 1;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(12346);

    if (bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        cerr<<"Error on binding"<<endl;
        return 1;
    }

    if (listen(server_fd, 5) < 0) {
        cerr<<"Error on listen"<<endl;
        return 1;
    }

    // std::cout << "Server listening on port " << PORT << std::endl;

    client_len = sizeof(client_addr);
    client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &client_len);
    if (client_fd < 0) {
        cerr<<"Error on accept"<<endl;
        return 1;
    }
    // 假设socket_fd是已经建立的socket连接的文件描述符
    while(true)
    {
        csj::MyMessage message;
        if (!ReceiveProtobufMessage(client_fd, &message)) {
            std::cerr << "Failed to receive protobuf message." << std::endl;
            return 1;
        }
    
        std::cout << "Received message: " << message.id() << ", " << message.content() << std::endl;
    }
    
    close(client_fd);
    close(server_fd);
    
    return 0;
}

3、编写客户端,编辑 g++ -o client client.cpp message.pb.cc -lprotobuf

cpp 复制代码
#include "message.pb.h" // 你的protobuf生成的头文件
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
using namespace std;
 
// 发送protobuf消息
bool SendProtobufMessage(int socket_fd, const csj::MyMessage& message) {
    std::string serialized_message;
    if (!message.SerializeToString(&serialized_message)) {
        std::cerr << "Failed to serialize message." << std::endl;
        return false;
    }
 
    ssize_t sent = send(socket_fd, serialized_message.data(), serialized_message.size(), 0);
    if (sent < 0) {
        std::cerr << "Failed to send message." << std::endl;
        return false;
    }
 
    return true;
}
 
int main() {
    int clientSocket;
    struct sockaddr_in serverAddress;

    // 创建套接字
    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == -1) {
        cerr << "Failed to create socket" << endl;
        return -1;
    }

    // 设置服务器地址和端口
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
    serverAddress.sin_port = htons(12346);

    // 连接服务器
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        cerr << "Failed to connect to server" << endl;
        return -1;
    }
    // 假设socket_fd是已经建立的socket连接的文件描述符
    while(true)
    {
        csj::MyMessage message;
        cout<<"please enter id:";
        int id;
        cin>>id;
        cout<<"please enter content:";
        string content;
        cin>>content;
        message.set_id(id);
        message.set_content(content);
        if (!SendProtobufMessage(clientSocket, message)) 
        {
        std::cerr << "Failed to send protobuf message." << std::endl;
        return 1;
        }
    }
    close(clientSocket);
    return 0;
}

当服务端两次使用同一个端口号,会出现Error on binding,这是同一时间在同一端口上启动两个TCP服务器,第二个服务器会收到端口已被占用的错误,并且无法绑定到该端口上。这是因为操作系统会阻止多个应用程序同时绑定到相同的端口上,以确保网络通信的正确性和稳定性。虽然服务端结束了,但这个接口不会立马就"让"出来,而是过一段时间由OS自动释放

相关推荐
HaoHao_01017 分钟前
AWS Serverless Application Repository
服务器·数据库·云计算·aws·云服务器
Golinie40 分钟前
【C++高并发服务器WebServer】-2:exec函数簇、进程控制
linux·c++·webserver·高并发服务器
励志去大厂的菜鸟1 小时前
系统相关类——java.lang.Math (三)(案例详细拆解小白友好)
java·服务器·开发语言·深度学习·学习方法
老王聊主机1 小时前
2025年华为云一键快速部署幻兽帕鲁联机服务器教程
运维·服务器·华为云
课堂随想1 小时前
`std::make_shared` 无法直接用于单例模式,因为它需要访问构造函数,而构造函数通常是私有的
c++·单例模式
Moniicoo1 小时前
Linux中关于glibc包编译升级导致服务器死机或者linux命令无法使用的情况
linux·运维·服务器
Zfox_1 小时前
应用层协议 HTTP 讲解&实战:从0实现HTTP 服务器
linux·服务器·网络·c++·网络协议·http
OliverH-yishuihan2 小时前
C++ list 容器用法
c++·windows·list
wangchen_02 小时前
Linux终端之旅: 权限管理三剑客与特殊权限
linux·运维·服务器
Forest_HAHA2 小时前
14,c++——继承
开发语言·c++