序列化结构(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自动释放

相关推荐
ac.char3 分钟前
在 Ubuntu 上安装 Yarn 环境
linux·运维·服务器·ubuntu
敲上瘾3 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
不会写代码的ys9 分钟前
【类与对象】--对象之舞,类之华章,共绘C++之美
c++
兵哥工控11 分钟前
MFC工控项目实例三十二模拟量校正值添加修改删除
c++·mfc
长弓聊编程21 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.29 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
暮色_年华43 分钟前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子1 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
soulteary1 小时前
突破内存限制:Mac Mini M2 服务器化实践指南
运维·服务器·redis·macos·arm·pika
我们的五年1 小时前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习