【Linux】序列化与反序列化

news2025/1/18 3:25:14

目录

前言

什么是应用层?

再谈"协议"

 什么是序列化和反序列化

网络版计算器

整体流程实现

Sock.hpp的实现

TcpServer.hpp的实现

Protocol.hpp的实现

CalServer.cc的编写

CalClient.cc的编写

整体代码


 

前言

        本章是属于TCP/UDP四层模型中的第一层 应用层相关的内容。主要介绍了序列化与反序列化的应用,本章代码居多,主要是在代码中体现出序列化与反序列化,希望可以耐心阅读,坚持到底一定会有所收获。

什么是应用层?

  • 在网络编程中,应用层是网络协议栈中的最高层。它负责定义网络应用程序与网络通信的接口和规范,主要关注数据的格式、交互方式和应用层协议的定义。

        应用层协议是在应用程序之间进行通信时所使用的规则和约定。它定义了应用程序如何打包、发送、接收和解析数据。应用层协议可以基于不同的传输协议(如TCP、UDP)来提供不同的服务,例如文件传输、电子邮件、网页浏览、即时通信等。

        应用层协议通常在应用程序中实现,为应用程序提供了一组函数、API(例如我们前面使用的socket相关接口)或库,使得应用程序能够通过网络与其他应用程序进行通信。开发者可以使用这些功能来处理数据的编码、解码、发送和接收等操作,同时也可以根据需要定义自己的应用层协议。

        简单来说就是我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.


再谈"协议"

        协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢.

        例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.

        这里有两种方案:

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格;

约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 "序列化" 和 "反序列化"

 什么是序列化和反序列化

        序列化是将内存中的结构化数据(结构体)转换为字节序列的过程。通过序列化,可以将数据结构转换为一系列字节,使其可以被保存到磁盘或通过网络传输。序列化的结果是一个字节流或二进制数据,可以在需要时进行持久化存储或传输给其他系统。

        反序列化是将字节流或二进制数据转换回原始结构化数据(结构体)的过程。通过反序列化,可以将序列化后的数据重新还原为内存中的数据结构,以便在程序中进行处理或使用。

        序列化和反序列化通常用于在分布式系统中进行数据传输、存储和进程间通信。例如,在网络通信中,发送方将数据结构序列化为字节流,通过网络发送给远程主机,然后在远程主机上进行反序列化,以还原数据并进行处理。



网络版计算器

整体流程实现

        这个简易版的网络程序是客户端发送类似与“1+2”,“1-2”,“1/2”..,然后服务器返回对应的结果和状态码,代表结果的正确与否。

        我们编写的整体流程是:先对socket接口做封装成Sock类,提供Socket接口,Bind接口,Listen接口,Accept接口以及Connect接口.

        然后我们再对服务器做相关的封装,封装成TcpServer类,构造函数用于初始化服务器,该类对外提供BindService(用于绑定服务)接口,Excute(用来执行对应的回调函数)接口,Start(用于服务器的启动并创建线程运行对应的服务)等接口.

        然后是协议的定制,通信双方(服务端和客户端必须同时遵守这一套规定),共有两个类,分别为Request类和Response类,每个类中分别有两个接口 Serialization(序列化)与Deserialization(反序列化),用于格式的转化。

        最后再分别编写服务端和客户端即可,


Sock.hpp的实现

        这一个文件主要是封装socket的相关接口,到时候我们使用相关socket接口时,只需要调用Sock类中的相关接口即可.

        第一个是Socket函数,封装了socket,并得到一个套接字,将该套接字返回,代码如下:

    int Socket()
    {
        // 1.创建套接字
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create sock success,  listensock: %d", listensock);
        return listensock;
    }

        第二个是Bind接口,同样地我们首先需要创建一个sockaddr_in结构体,然后填入相关的数据,这个前几章已经说过好多次了,便不再详细说明了,然后调用bind绑定。

    int Bind(int sock, uint16_t port, string ip = "0.0.0.0")
    {
        // 2.bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;//使用的协议簇为IPv4
        local.sin_port = htons(port);//填入端口号
        local.sin_addr.s_addr = INADDR_ANY;//填入ip地址

        if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error", errno, strerror(errno));
            exit(3);
        }
    }

        第三个是Listen接口,用于对客户端请求的监听,内部只需要调用listen接口并判断即可.

    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error", errno, strerror(errno));
            exit(3);
        }
        logMessage(NORMAL, "init server success");
    }

        第四个是Accept接口,和上一章所说的一样,先建立一个sockaddr_in结构体,用于接收保存客户端的相关信息(ip和端口等等),同时会得到一个新的套接字,返回这个套接字即可。

    int Accept(int listensock, string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof src;
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error", errno, strerror(errno));
            return -1;
        }
        if (port)
            *port = ntohs(src.sin_port);
        if (ip)
            *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }

        最后一个是Connect接口,和之前的用法一样,先创建一个sockaddr_in结构体,然后填入客户端本身的相关信息,最后调用connect函数接口.

    bool Conncect(int sock,string server_ip, uint16_t server_port)
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr=inet_addr(server_ip.c_str());
        
        if(connect(sock,(struct sockaddr*)&server,sizeof server) == 0) return true;
        else {perror("connect"); return false; }
    }

TcpServer.hpp的实现

        这个类中首先要有这3个成员:listensock_用于监听的套接字,Sock sock,上面的Sock类的一个对象,用于接口的调用,vector<func_t> func_,用于存储相关的服务. func_t是一个函数指针.

定义如下:

   using func_t = function<void(int)>;

        首先是构造函数,需要对服务器进行初始化,包括创建套接字,绑定与监听,如下:

        TcpServer(const uint16_t &port, const string &ip = "0.0.0.0")
        {
            listensock_ = sock_.Socket();
            sock_.Bind(listensock_, port, ip);
            sock_.Listen(listensock_);
        }

        然后是BindService,用于将绑定某一个服务,将其加入到func_即可.

        void BindService(func_t func)
        {
            func_.push_back(func);
        }

        接下来是Excute函数,用于执行func_中的方法:

        void Excute(int sock)
        {
            for(auto & f : func_)
            {
                f(sock);
            }
        }

        然后是Start函数,用于服务器的启动,调用Accept等待客户端的连接,当有客户端连接后,创建一个新的线程,并调用对应的回调函数,于此同时,我们新建一个ThreadData类,用于存储该线程的相关信息,包括该线程的套接字和调用该线程的this指针.如下:

        void Start()
        {
            for (;;)
            {
                string clientip;
                uint16_t clientport;
                int sock = sock_.Accept(listensock_, &clientip, &clientport);
                if (sock == -1)
                    continue;
                logMessage(NORMAL, "Create new link success, sock: %d", sock);

                pthread_t tid;
                ThreadData* td= new ThreadData(sock,this);
                pthread_create(&tid, nullptr, ThreadRoutine, td);
            }
        }

ThreadData类:

    class ThreadData
    {
    public:
        ThreadData(int sock,TcpServer* server):sock_(sock),server_(server)
        {}
        ~ThreadData(){}
    public:
         int sock_;
         TcpServer* server_;
    };

回调函数ThreadRoutine

        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData* td = static_cast<ThreadData*>(args);
            td->server_->Excute(td->sock_);
            close(td->sock_);

            return nullptr;
        }

Protocol.hpp的实现

        这个类主要是对发送方和应答方进行一个协议的定制,方便发送和解析数据。

所以一共两个类,Request类和Response类.

  • 先来看Request类,即请求方。该类主要是对请求数据的序列化和反序列化.
  • 共有3个类成员:x_,y_,op_,分别代表第一个操作、第二个操作数、和符号.

        第一个接口Serialization序列化,它每次需要把结构化数据转化成字符串形式,我们规定形式为

“x_ op_ y_”,所以代码如下:

        string Serialization()
        {
            // 1.自主实现 "x_ op_ y_\r\n"
            string str;
            str = to_string(x_);
            str += SPACE;
            str += op_;
            str += SPACE;
            str += to_string(y_);
            return str;
        }

        第二个接口是请求方 Deserialization反序列化的实现,需要将字符串数据转化成结构化的数据,首先我们要解析字符串,分别提取出x,y和op,然后赋值给这个类成员(x_,y_,op_),便成功的反序列化了.

        bool Deserialization(const string &str)
        {
            //提取x的第一个字符
            size_t left = str.find(SPACE);
            if (left == string::npos)
                return false;
            //提取y的第一个字符
            size_t right = str.rfind(SPACE);
            if (right == string ::npos)
                return false;
            
            //得到整数x
            x_ = atoi(str.substr(0, left).c_str());
            //得到整数y
            y_ = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
                return false;
            else
                op_ = str[left + SPACE_LEN];
            return true;
        }
  •  Response类

  • 该类主要是对处理结果的序列化和反序列化,类成员有result_和code_,分别代表结果和结果状态码.

它的序列化形式我们规定为“result_ code_”,所以代码如下:

        // code_ result
        string Serialization()
        {
            string s;
            s = to_string(code_);
            s += SPACE;
            s += to_string(result_);
            return s;
        }

反序列化逻辑和Request类一样。分别提取,然后赋值给类成员:

        bool Deserialization(const string& s)
        {
            size_t pos = s.find(SPACE);
            if(pos == string::npos)
            {
                return false;
            }
            code_ = atoi(s.substr(0,pos).c_str());
            result_ = atoi(s.substr(pos+SPACE_LEN).c_str());

            return true;
        }

于此同时,还封装了两个函数,Recv和Send.

实现分别如下:

    string Recv(int sock)
    {
        //tcp是面向字节流的
        char inbuffer[1024];
        ssize_t s = recv(sock, inbuffer, sizeof inbuffer, 0);
        if (s > 0)
        {
            inbuffer[s] = '\0';
            return inbuffer;
        }
        else if(s == 0)
        {
            cout << "client quit" << endl;
        }
        else
        {
            cout << "recv error" << endl;
        }
        return "";
    }
    void Send(int sock, const string str)
    {
        send(sock, str.c_str(), str.size(), 0);
    }

到这里我们基本工作就完成了一大半,接下来是服务端和客户端的编写

CalServer.cc的编写

        这是服务端的代码,首先我们都是要忽略SIGPIPE信号的,这是为了避免进程在写入已关闭的套接字时触发SIGPIPE信号而终止。

        例如,当一个客户端与服务器建立连接后,但在向客户端发送响应前,客户端已经关闭了连接。如果服务器端没有忽略SIGPIPE信号,那么在尝试向已关闭的连接写入数据时,服务器进程将会被终止,这显然是不希望看到的行为。

    signal(SIGPIPE, SIG_IGN);

        然后我们需要绑定一个服务,我们要进行计算,所以假设服务 方法是calculator,该函数首先要从调用Recv读取到请求,如果读取结果不为空的话,我们将读取到的数据进行反序列化成结构化的数据(Request类),然后再将这个结构化的数据传送到calculatorHelp()函数进行计算,再次得到一个结构化的数据(Response类),最后再对这个数据进行序列化成字符串形式,再Send回请求的客户端中.

static Response calculatorHelp(Request &req)
{
    Response resp(0, 0);
    switch (req.op_)
    {
    case '+':
        resp.result_ = req.x_ + req.y_;
        break;
    case '-':
        resp.result_ = req.x_ - req.y_;
        break;
    case '*':
        resp.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (req.y_ == 0)
            resp.code_ = 1;
        else
            resp.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (req.y_ == 0)
            resp.code_ = 2;
        resp.result_ = req.x_ % req.y_;
        break;
    default:
        resp.code_ = 3;
        break;
    }
    return resp;
}
void calculator(int sock)
{
    while (true)
    {
        string str = Recv(sock); // 在这里我们读到了一个请求
        if (!str.empty())
        {
            Request req;
            req.Deserialization(str); // 反序列化
            Response resp = calculatorHelp(req);
            string respString = resp.Serialization(); // 对计算结果进行序列化
            Send(sock, respString);
        }
        else
        {
            break;
        }
    }
}
int main()
{
    //....
    server->BindService(calculator);
    //....
}

一切完成后,我们在Start启动服务器即可.

    server->Start();

CalClient.cc的编写

        客户端编写就比较简单了,先创建套接字,然后获取到用户输入的ip和port,然后进行Connect,连接完成后,提示用户输入数据,输入完成后,将数据序列化,然后发送给服务器,然后接收服务器返回的数据,再将其反序列化即可。

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();

    cout << sockfd << endl;
    if (!sock.Conncect(sockfd, serverip, serverport))
    {
        cerr << "connect error" << endl;
        exit(2);
    }

    while (true)
    {
        Request req;

        cout << "Please Enter x# ";
        cin >> req.x_;
        cout << "Please Enter y#" ;
        cin >> req.y_;
        cout << "Please Enter operator# ";;
        cin >> req.op_; ;

        string s = req.Serialization();

        Send(sockfd, s);

        string r = Recv(sockfd);
        Response resp;
        resp.Deserialization(r);
        cout << "code: " << resp.code_ << endl;
        cout << "result: " << resp.result_ << endl;

        sleep(1);
    }
    return 0;
}

代码效果图:

 


 

整体代码

如果你不想仔细看,直接拷贝下面的代码也可以运行,然后可以自己研究:

  • Sock.hpp
#pragma once
#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <memory>
#include <pthread.h>
#include <signal.h>
#include <cstring>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

using namespace std;
class Sock
{
public:
    const static int gbacklog = 20;

    Sock(){}
    int Socket()
    {
        // 1.创建套接字
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create sock success,  listensock: %d", listensock);
        return listensock;
    }
    int Bind(int sock, uint16_t port, string ip = "0.0.0.0")
    {
        // 2.bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;//使用的协议簇为IPv4
        local.sin_port = htons(port);//填入端口号
        //local.sin_addr.s_addr = ip.empty() ? INADDR_ANY : inet_addr(ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error", errno, strerror(errno));
            exit(3);
        }
    }
    void Listen(int sock)
    {
        // 3.因为TCP是面向连接的,意味着当我们正式通信的时候,需要先建立连接
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error", errno, strerror(errno));
            exit(3);
        }
        logMessage(NORMAL, "init server success");
    }
    // const string& 输入型参数
    // string* 输出型参数
    // string& 输入输出型参数
    int Accept(int listensock, string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof src;
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error", errno, strerror(errno));
            return -1;
        }
        if (port)
            *port = ntohs(src.sin_port);
        if (ip)
            *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }

    bool Conncect(int sock,string server_ip, uint16_t server_port)
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr=inet_addr(server_ip.c_str());
        // cout << server.sin_port << " " << server.sin_addr.s_addr << endl;
        if(connect(sock,(struct sockaddr*)&server,sizeof server) == 0) return true;
        else {perror("connect"); return false; }
    }
    ~Sock()
    {
    }

};
  • TcpServer.hpp
​
#pragma once
#include "Sock.hpp"
#include <vector>
#include <functional>
#include <pthread.h>
namespace ns_TcpServer
{
    class TcpServer;
    using func_t = function<void(int)>;
    class ThreadData
    {
    public:
        ThreadData(int sock,TcpServer* server):sock_(sock),server_(server)
        {}
        ~ThreadData(){}
    public:
         int sock_;
         TcpServer* server_;
    };
    class TcpServer
    {
    private:
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData* td = static_cast<ThreadData*>(args);
            td->server_->Excute(td->sock_);
            close(td->sock_);

            return nullptr;
        }

    public:
        TcpServer(const uint16_t &port, const string &ip = "0.0.0.0")
        {
            listensock_ = sock_.Socket();
            sock_.Bind(listensock_, port, ip);
            sock_.Listen(listensock_);
        }
        void BindService(func_t func)
        {
            func_.push_back(func);
        }
        void Excute(int sock)
        {
            for(auto & f : func_)
            {
                f(sock);
            }
        }
        void Start()
        {
            for (;;)
            {
                string clientip;
                uint16_t clientport;
                int sock = sock_.Accept(listensock_, &clientip, &clientport);
                if (sock == -1)
                    continue;
                logMessage(NORMAL, "Create new link success, sock: %d", sock);

                pthread_t tid;
                ThreadData* td= new ThreadData(sock,this);
                pthread_create(&tid, nullptr, ThreadRoutine, td);
            }
        }
        ~TcpServer()
        {
            if (listensock_ >= 0)
                close(listensock_);
        }

    private:
        int listensock_;
        Sock sock_;
        vector<func_t> func_;
    };
}

​
  • Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

using namespace std;

namespace ns_protocol
{
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
    class Request
    {
    public:
        string Serialization()
        {
            // 1.自主实现 "x_ op_ y_\r\n"
            string str;
            str = to_string(x_);
            str += SPACE;
            str += op_;
            str += SPACE;
            str += to_string(y_);
            return str;
        }
        bool Deserialization(const string &str)
        {
            size_t left = str.find(SPACE);
            if (left == string::npos)
                return false;
            size_t right = str.rfind(SPACE);
            if (right == string ::npos)
                return false;

            x_ = atoi(str.substr(0, left).c_str());
            y_ = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
                return false;
            else
                op_ = str[left + SPACE_LEN];
            return true;
        }

    public:
        Request(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }
        Request()
        {
        }
        ~Request() {}

    public:
        int x_;
        int y_;
        char op_;
    };
    class Response
    {
    public:
        // code_ result
        string Serialization()
        {
            string s;
            s = to_string(code_);
            s += SPACE;
            s += to_string(result_);
            return s;
        }

        bool Deserialization(const string& s)
        {
            size_t pos = s.find(SPACE);
            if(pos == string::npos)
            {
                return false;
            }
            code_ = atoi(s.substr(0,pos).c_str());
            result_ = atoi(s.substr(pos+SPACE_LEN).c_str());

            return true;
        }

    public:
        Response(int res, int code) : result_(result_), code_(code)
        {
        }
        Response()
        {
        }
        ~Response() {}

    public:
        int result_; // 计算结果
        int code_;   // 计算结果状态码
    };
    string Recv(int sock)
    {
        //tcp是面向字节流的

        char inbuffer[1024];
        ssize_t s = recv(sock, inbuffer, sizeof inbuffer, 0);
        if (s > 0)
        {
            inbuffer[s] = '\0';
            return inbuffer;
        }
        else if(s == 0)
        {
            cout << "client quit" << endl;
        }
        else
        {
            cout << "recv error" << endl;
        }
        return "";
    }
    void Send(int sock, const string str)
    {
        send(sock, str.c_str(), str.size(), 0);
    }
}
  • CalServer.cc
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>
#include <signal.h>

using namespace ns_TcpServer;
using namespace ns_protocol;
using namespace std;

static void usage(string proc)
{
    cout << "\n Usage: " << proc << "port\n"
         << endl;
}
static Response calculatorHelp(Request &req)
{
    Response resp(0, 0);
    switch (req.op_)
    {
    case '+':
        resp.result_ = req.x_ + req.y_;
        break;
    case '-':
        resp.result_ = req.x_ - req.y_;
        break;
    case '*':
        resp.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (req.y_ == 0)
            resp.code_ = 1;
        else
            resp.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (req.y_ == 0)
            resp.code_ = 2;
        resp.result_ = req.x_ % req.y_;
        break;
    default:
        resp.code_ = 3;
        break;
    }
    return resp;
}
void calculator(int sock)
{
    while (true)
    {
        string str = Recv(sock); // 在这里我们读到了一个请求
        if (!str.empty())
        {
            Request req;
            req.Deserialization(str); // 反序列化
            Response resp = calculatorHelp(req);
            string respString = resp.Serialization(); // 对计算结果进行序列化
            Send(sock, respString);
        }
        else
        {
            break;
        }
    }
}

//./calServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    //一般经验:server在编写的时候,要有较为严谨的判断逻辑
    //一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法访问问题
    signal(SIGPIPE, SIG_IGN);
    unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();
}
  • CalClient.cc
#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"
using namespace std;
using namespace ns_protocol;

static void usage(string proc)
{
    cout << "\n Usage: " << proc << " serverIp serverPort\n"
         << endl;
}
//./clinet server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();

    cout << sockfd << endl;
    if (!sock.Conncect(sockfd, serverip, serverport))
    {
        cerr << "connect error" << endl;
        exit(2);
    }

    while (true)
    {
        Request req;
        cout << "Please Enter x# ";
        cin >> req.x_;
        cout << "Please Enter y#" ;
        cin >> req.y_;
        cout << "Please Enter operator# ";;
        cin >> req.op_; 
        string s = req.Serialization();

        Send(sockfd, s);

        string r = Recv(sockfd);
        Response resp;
        resp.Deserialization(r);
        cout << "code: " << resp.code_ << endl;
        cout << "result: " << resp.result_ << endl;

        sleep(1);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/944061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

opencv的使用(Ubuntu linux环境,AS jni,AS java)

最近要完成一个功能&#xff0c;就是把四个视频合成左右上下分布的一个视频。尝试很多方法&#xff0c;最终使用opencv来实现该功能。&#xff08;通过opencv实现的视频好像没有声音。&#xff09;研究的步骤&#xff0c;首先在Ubuntu环境测试&#xff0c;该功能是否实现。然后…

13.搬砖

目录 题目 Description Input Output 思路&#xff08;归并排序&#xff09; 具体步骤如下 C整体代码&#xff08;含详细注释&#xff09; 归并排序总结 核心步骤 代码模板 题目 Description 小张在暑假时间来到工地搬砖挣钱。包工头交给他一项艰巨的任务&#xff0…

Mavan进阶之父子模块(继承)

文章目录 Mavan 父子模块&#xff08;继承&#xff09;1. 父项目2. 子项目3. 父子项目的使用 Mavan 父子模块&#xff08;继承&#xff09; 「继承」是 Maven 中很强大的一种功能&#xff0c;继承可以使得子 pom 可以获得 parent 中的各项配置&#xff0c;可以对子 pom 进行统…

深度学习之反卷积

具体推理可以参考https://blog.csdn.net/zhsmkxy/article/details/107073350

uniapp微信小程序使用stomp.js实现STOMP传输协议的实时聊天

简介&#xff1a; 原生微信小程序中使用 本来使用websocket&#xff0c;后端同事使用了stomp协议&#xff0c;导致前端也需要对应修改。 如何使用 1.yarn add stompjs 2.版本 “stompjs”: “^2.3.3” 3.在static/js中新建stomp.js和websocket.js&#xff0c;然后在需要使用…

一文讲透超宽带(UWB)前世今生

►►►UWB大火与巨头入局 传闻已久的蔚来手机可能即将要发布了。据工信部官网显示&#xff1a;申请单位为蔚来移动科技有限公司、型号为N2301的手机已正式完成入网。相关认证信息显示&#xff0c;N2301支持UWB&#xff0c;可以被用作蔚来汽车的数字钥匙。 图 1 蔚来手机概念图 …

第十四课 定语从句

文章目录 前言 所有定语从句的连接词是没有意思的一、定语从句的定义和结构二、关系代词引导的定语从句1、whowho谓语&#xff08;宾语&#xff09;状语who系动词表语状语who助动词及物动词的过去分词 2、whomwhom主语及物动词&#xff08;状语&#xff09;whom主语谓语to及物动…

2023京东咖啡机行业数据分析(京东数据分析平台)

如今咖啡的渗透率越来越高&#xff0c;养成咖啡饮用习惯的消费者越来越多&#xff0c;尤其是一二线城市。同时&#xff0c;随着人们收入水平的提高&#xff0c;精致生活理念使人们对咖啡的态度从提神需求逐渐转变为社交需求&#xff0c;国内咖啡机市场的发展空间也逐步增大。 …

Oracle 本地客户端连接远程 Oracle 服务端并使用 c# 连接测试

这里写自定义目录标题 前言Oracle 客户端安装先决条件下载 Oracle 客户端Oracle 客户端环境变量配置 PL/SQLPL/SQL 下载PL/SQL 配置 配置远程连接tnsnames.ora 文件配置 使用 PL/SQL 连接远程数据库使用 C# 远程访问 Oracle 数据库结语 前言 最近有一个需要使用本地的 Oracle …

java内存分区

按照垃圾收集&#xff0c;将 Java 堆划分为**新生代 &#xff08;Young Generation&#xff09;和老年代&#xff08;Old Generation&#xff09;**两个区域&#xff0c; 新生代存放存活时间短的对象&#xff0c;而每次回收后存活的少量对象&#xff0c;将会逐步晋升到老年代中…

idea 创建mybatis xml文件时找不到

1、File >Settings 如图 &#xff1a; 2、添加模板&#xff1a;如下图 3、添加xml模板 如下图&#xff1a; 模板内容&#xff1a; <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//E…

微信小程序使用stomp.js实现STOMP传输协议的实时聊天

简介&#xff1a; uniapp开发的小程序中使用 本来使用websocket&#xff0c;后端同事使用了stomp协议&#xff0c;导致前端也需要对应修改。 如何使用 在static/js中新建stomp.js和websocket.js&#xff0c;然后在需要使用的页面引入监听代码发送代码即可 代码如下&#x…

燃气管网监测系统,提升城市燃气安全防控能力

燃气是我们日常生活中不可或缺的能源&#xff0c;但其具有易燃易爆特性&#xff0c;燃气安全使用、泄漏监测尤为重要。当前全国燃气安全事故仍呈现多发频发态势&#xff0c;从公共安全的视角来看&#xff0c;燃气已成为城市安全的重大隐忧&#xff01;因此&#xff0c;建立一个…

Linux 终端命令行 产品介绍

Linux命令手册内置570多个Linux 命令&#xff0c;内容包含 Linux 命令手册。 【软件功能】&#xff1a; 文件传输 bye、ftp、ftpcount、ftpshut、ftpwho、ncftp、tftp、uucico、uucp、uupick、uuto、scp备份压缩 ar、bunzip2、bzip2、bzip2recover、compress、cpio、dump、gun…

计算机毕设 基于深度学习的植物识别算法 - cnn opencv python

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 MobileNetV2网络4 损失函数softmax 交叉熵4.1 softmax函数4.2 交叉熵损失函数 5 优化器SGD6 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&a…

跨境电商面临的法律与合规问题

跨境电商在全球范围内取得了飞速的发展&#xff0c;但这一领域也伴随着复杂的法律与合规问题&#xff0c;涉及国际法律、知识产权、税务、隐私等多个方面。在跨境电商中&#xff0c;合法合规的经营不仅有助于企业长期发展&#xff0c;还能增强消费者信任&#xff0c;提升市场竞…

使用docker、docker-compose部署微服务

使用docker、docker-compose部署微服务 一、使用docker部署1、准备2、上传jar包3、编写dockerfile文件3、构建镜像和容器 二、使用docker-compose部署1、准备服务的jar包和dockerfile文件2、编写docker-compose.yml文件3、docker-compose常用命令&#xff08;1&#xff09;、前…

地下水质分析积分球

我国的河流水资源相当丰富&#xff0c;河川径流总量历年来位居世界第三&#xff0c;年均达到了27000亿m。但经济快速发展的同时对河流水资源产生了一定的负面影响&#xff0c;河流水质污染和富营养化的现象偶有发生&#xff0c;在对我国七大水系216条河流503个主要断面进行监测…

冠达管理:什么是k线怎样看k线图?

K线图是一种股票商场常用的价格图表&#xff0c;它显现了一段时刻内股票开盘价、收盘价、最高价和最低价等信息。K线起源于日本&#xff0c;在上世纪90年代被引进到全球股市中。跟着股市的开展&#xff0c;K线图已经成为股票商场数据剖析中常用的工具&#xff0c;因此了解K线图…

一文速学-让神经网络不再神秘,一天速学神经网络基础-前向传播(三)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;…