在 C++ 中通过 tcp 套接字发送结构

Posted

技术标签:

【中文标题】在 C++ 中通过 tcp 套接字发送结构【英文标题】:Sending struct over tcp socket in C++ 【发布时间】:2018-06-26 10:46:44 【问题描述】:

我正在尝试通过 tcp 套接字发送结构。我是套接字编程的新手,我确实尝试过这里建议的选项,但这些并没有达到我的目的。有人可以帮忙吗?

我已经编写了 Server.cpp 和 Client.cpp 并且两者都可以正确编译。但是,当我执行服务器以收听客户端时,我不确定服务器是否能够从客户端接收结构。另外,一旦收到此结构,我该如何阅读?

服务器.cpp

#include<iostream>
#include <cstring>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string>
#include<stdio.h>
using namespace std;
int main()

        struct UE
        
        string Net;
        int imsi;
         ;
        UE UE2;
        //cout<<UE1.imsi<<"\n"<<UE1.Net<<"\n";
        int sock, cli, receive;
        sockaddr_in server, client;
        unsigned int len;

        if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        
                perror("Socket:");
                exit(-1);
        
        server.sin_family = AF_INET;
        server.sin_port = htons(10000);
        //cout<<htons(10000);
        server.sin_addr.s_addr = INADDR_ANY;
        //cout<<INADDR_ANY;
        memset(&(server.sin_zero), '\0', 8);
        len = sizeof(struct sockaddr_in);
        if((bind(sock, (struct sockaddr *)&server, len)) == -1)
        
                perror("Bind:");
                exit(-1);

        
        if((listen(sock, 5)) == -1)
        
                perror("Listen:");
                exit(-1);
        
        while(1)
        
                cli = accept(sock,(struct sockaddr *)&client, &len);
                if(cli == -1)
                
                        perror("Accept");
                        exit(-1);
                
                receive = recv(sock, (void*)&UE2, sizeof(UE2), NULL);
                cout<<UE2.imsi;
                //cout<<UE2.imsi<<"\n"<<UE2.Net;
                //int sent = send(cli, (const void*)&mesg, sizeof mesg, 0);
                //cout<<"Sent"<<sent<<" bytes to client :<<inet_ntoa(client.sin_addr)";
                close(cli);
        

客户端.cpp

#include <arpa/inet.h>
#include<iostream>
#include <cstring>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string>
#include<stdio.h>
using namespace std;
int main(int argc, char *argv[])

        struct UE
        
        string Net;
        int imsi;
         ;
        UE UE1;
        UE1.Net = "4G";
        UE1.imsi = htons(8649);
        int sock, receive;
        struct sockaddr_in server;
        char mesg[200];
        if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        
                perror("Socket:");
                exit(-1);
        
        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        //cout<<server.sin.port;
        server.sin_addr.s_addr = inet_addr(argv[1]);
        //cout<<server.sin_addr.s_addr;
        memset(&(server.sin_zero), '\0', 8);
        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == -1)
        
                perror("Connect:");
                exit(-1);
        
        int count = 0;
        while(count = 0)
        
        send(sock, &UE1, sizeof(UE1), 0);
        //receive = recv(sock, (void*)&mesg, sizeof mesg, NULL);
        count++;
        
        cout<<"Sent "<<UE1.imsi<<" and "<<UE1.Net<<" to Server \n";
        close(sock);

【问题讨论】:

struct UE 是非 POD 类型(由于包含 std::string),因此它不能通过普通的 send/recv 调用发送,以您的方式尝试。 您能否详细说明如何做到这一点? :S 你真的应该避免using namespace std - 这是一个坏习惯,当你没想到它时can silently change the meaning of your program。习惯于使用命名空间前缀(std 故意很短),或将只是您需要的名称 导入到最小合理范围 【参考方案1】:

您的代码中至少有 2 个问题:

    您不能以这种方式发送像 std::string 这样的对象以及任何包含它的对象(正式非 POD 数据),您需要编组您的数据。周围有很多库(例如 google proto 缓冲区),或者您可以编写自己的库。这个话题太宽泛,无法在此处的答案中涵盖。

    1234563 /li>

【讨论】:

此外,即使发送 POD 数据也是有问题的,因为 POD 数据的字节布局可能会因编译程序的 CPU 架构而异。当结构的字节布局和字节序(由接收程序的编译器指定)与发送方运行的程序不完全匹配时,接收方将误解数据。【参考方案2】:

您永远不应该将整个结构写入文件或套接字。 始终分别编写每个字段,并以相同的方式读取它们。 这样做时,您会付出一些内存开销,但出于性能原因,这通常是一个不错的设计,因为您不想将每个值都写入套接字。

在发送二进制数据时,您应始终注意以下事项:

不同的endianness 不同的padding bye-sizes of intrinsic types 中的差异

你需要一些类似下面的函数:

virtual MESSAGE_BUFFER * GetMessageAsBinaryPtr()

  MESSAGE_BUFFER * binaryMsg = new MESSAGE_BUFFER;
  UINT8 * ptrBuffer = &(*binaryMsg)[0];
  ptrBuffer = this->serializeUInt16(ptrBuffer, this->m_majorVersion);
  ptrBuffer = this->serializeUInt16(ptrBuffer, this->m_minorVersion);
  ptrBuffer = this->serializeUInt32(ptrBuffer,         (UINT32)this->m_messageType);
  ptrBuffer = this->serializeUInt64(ptrBuffer, this->m_packetID);
  ptrBuffer = this->serializeDouble(ptrBuffer, this->m_timestamp);
  return binaryMsg;


virtual void CreateFromBinary(MESSAGE_BUFFER buffer)

  UINT8 * ptrBuffer = &buffer[0];
  ptrBuffer = this->deserializeUInt16FromBuffer(ptrBuffer, &this->m_majorVersion);
  ptrBuffer = this->deserializeUInt16FromBuffer(ptrBuffer, &this->m_minorVersion);

  UINT32 messageType = 0;
  ptrBuffer = this->deserializeUInt32FromBuffer(ptrBuffer, &messageType);
  this->SetMessageType((MessageTypes)messageType);

  ptrBuffer = this->deserializeUInt64FromBuffer(ptrBuffer, &this->m_packetID);
  ptrBuffer = this->deserializeDoubleFromBuffer(ptrBuffer, &this->m_timestamp);


inline UINT8 * serializeUInt16(UINT8 * buffer, UINT16 value)

  buffer[1] = value;
  buffer[0] = value >> 8;
  return buffer + 2;


inline UINT8 * deserializeUInt16FromBuffer(UINT8 * buffer, UINT16 *     pOutput)

  *pOutput = (*pOutput << 8) + buffer[0];
  *pOutput = (*pOutput << 8) + buffer[1];
  return buffer + 2;

当你有这样的功能时,你可以将你的结构序列化和反序列化到一个缓冲区,然后通过你的套接字发送这个缓冲区。

注意几点:

首先将要发送的结构序列化,逐个字段放入缓冲区中 MESSAGE_BUFFER 是 UINT8[1024] 类型 序列化例程返回一个指向缓冲区中下一个空闲字节的指针,我们用它来计算它序列化到多少字节 在我的例程中没有针对缓冲区溢出的保护

【讨论】:

取自这个 SO 答案***.com/a/1577174/8033062【参考方案3】:

您的代码中几乎没有错误。 在 server.cpp sockaddr_in --> struct sockaddr_in

一旦server 使用accept() 调用接受连接请求,它会返回新文件描述符,使用new fd 你应该使用read 和@ 987654326@ 操作不与旧的。

替换下面的语句

receive = recv(sock, (void*)&UE2, sizeof(UE2), NULL); /** you are receiving with old fd called sock **/

receive = recv(cli, (void*)&UE2, sizeof(UE2), NULL);

client.cpp

using namespace std;
int main(int argc, char *argv[])

        struct UE
        
                string Net;
                int imsi;
         ;
        UE UE1;
        UE1.Net = "4G";
        UE1.imsi = htons(8649);
        int sock, receive;
        struct sockaddr_in server;
        char mesg[200];
        sock = socket(PF_INET, SOCK_STREAM, 0);
        perror("Socket:");

        server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[2]));
        server.sin_addr.s_addr = inet_addr(argv[1]);
        memset(&(server.sin_zero), '\0', 8);

        connect(sock, (struct sockaddr*)&server, sizeof(server));
        perror("Connect:");

        int count = 0;
        send(sock, &UE1, sizeof(UE1), 0);
        perror("send");
        cout<<"Sent "<<UE1.imsi<<" and "<<UE1.Net<<" to Server \n";
        close(sock);

server.cpp

using namespace std;
int main()

        struct UE
        
                string Net;
                int imsi;
         ;
        UE UE2;

        int sock, cli, receive;
        struct sockaddr_in server, client;
        unsigned int len;
        sock = socket(AF_INET, SOCK_STREAM, 0);
        perror("Socket:");

        server.sin_family = AF_INET;
        server.sin_port = htons(10001);
        server.sin_addr.s_addr = INADDR_ANY;
        memset(&(server.sin_zero), '\0', 8);

        len = sizeof(server);
        bind(sock, (struct sockaddr *)&server, len);
        perror("Bind:");

        listen(sock, 1);
        perror("Listen:");

        cli = accept(sock,(struct sockaddr *)&client, &len);
        perror("accept");
        receive = recv(cli, ( void*)&UE2, sizeof(UE2), 0);
        perror("recv");

        cout << "rec = "<<receive<<endl;
        cout<<UE2.imsi<<"\n";
        close(sock);
        perror("close");

【讨论】:

您的代码也不起作用。 Std::string 不能在结构内传输

以上是关于在 C++ 中通过 tcp 套接字发送结构的主要内容,如果未能解决你的问题,请参考以下文章

在 Erlang 中通过 tcp 套接字发送元组

在 C++ 中通过套接字发送图像

在 c++ 中通过套接字(发送函数)发送图片,但不接收完整(Windows)!

《asyncio 系列》8. 在 asyncio 中通过流(StreamReaderStreamWriter)来实现 TCP 请求的发送与接收

在 C++ 中通过 UDP 发送缓冲图像

在 Qt 中通过 TCP 传输大文件