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

相关推荐
@hdd1 分钟前
C++ | STL之list详解:双向链表的灵活操作与高效实践
c++·list
凤年徐19 分钟前
【C/C++】深入理解指针(二)
c语言·开发语言·c++·经验分享·笔记·指针
嘿rasa22 分钟前
2025最新系统 Linux 教程(三)
linux·运维·服务器
、我是男生。25 分钟前
巴法云平台-TCP设备云-微信小程序实时接收显示数据-原理
网络协议·tcp/ip·微信小程序
网硕互联的小客服25 分钟前
服务器风扇故障导致过热问题的解决方案
运维·服务器·chrome
facaixxx202433 分钟前
应用镜像是什么?轻量应用服务器的镜像大全
linux·运维·服务器
weixin_445054721 小时前
力扣刷题-热题100题-第35题(c++、python)
c++·python·leetcode
XXYBMOOO1 小时前
基于 Qt 的 BMP 图像数据存取至 SQLite 数据库的实现
数据库·c++·qt
noravinsc1 小时前
Linux 下 Module 工具的介绍与使用
linux·运维·服务器
Sunlight_7772 小时前
第五章 SQLite数据库:1、SQLite 基础语法及使用案例
java·linux·服务器·jvm·数据库·tcp/ip·sqlite