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

相关推荐
ZachOn1y2 分钟前
计算机网络:计算机网络概述 —— 描述计算机网络的参数
网络·tcp/ip·计算机网络·考研必备
凌云行者29 分钟前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
丶Darling.43 分钟前
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树
开发语言·数据结构·c++·笔记·学习·算法
光通信学徒1 小时前
ubuntu图形界面右上角网络图标找回解决办法
linux·服务器·ubuntu·信息与通信·模块测试
wusam1 小时前
螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习03(网络及IP规划)
运维·服务器·网络·docker·容器
你会发光哎u1 小时前
Webpack模式-Resolve-本地服务器
服务器·前端·webpack
小飞猪Jay1 小时前
面试速通宝典——10
linux·服务器·c++·面试
程序猿阿伟2 小时前
《C++高效图形用户界面(GUI)开发:探索与实践》
开发语言·c++
阿客不是客2 小时前
深入计算机语言之C++:C到C++的过度
c++
LN-ZMOI2 小时前
c++学习笔记1
c++·笔记·学习