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

相关推荐
了一li1 小时前
Qt中的QProcess与Boost.Interprocess:实现多进程编程
服务器·数据库·qt
日记跟新中1 小时前
Ubuntu20.04 修改root密码
linux·运维·服务器
唐小旭1 小时前
服务器建立-错误:pyenv环境建立后python版本不对
运维·服务器·python
明 庭1 小时前
Ubuntu下通过Docker部署NGINX服务器
服务器·ubuntu·docker
BUG 4041 小时前
Linux——Shell
linux·运维·服务器
007php0071 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
yang_shengy1 小时前
【JavaEE】网络(6)
服务器·网络·http·https
大霞上仙2 小时前
Linux 多命令执行
linux·运维·服务器
冷心笑看丽美人2 小时前
探索 Samba 服务器:搭建跨平台文件共享的桥梁
运维·服务器