【问题标题】:Sending Message Over Tcp/IP using Sockets使用套接字通过 Tcp/IP 发送消息
【发布时间】:2023-08-08 16:16:01
【问题描述】:

我正在尝试在客户端/服务器之间发送数据,数据看起来像

typedef Struct Message 
  { int id;
    int message_length;
     char* message_str;
    }message;

我试图在客户端和服务器之间通过WriteRead 不断更新此结构中的元素。我听说Writev 可以解决问题。我想发送一个 向服务器发送消息,然后服务器提取元素并使用这些元素作为条件来执行正确的方法?

【问题讨论】:

  • 这里的具体问题是什么?
  • 我正在尝试向服务器发送一条包含两个整数和一个字符串的消息,然后让服务器仔细解析此消息?

标签: c sockets tcp struct


【解决方案1】:

假设您想自己进行序列化,而不是使用 Google Protocol Buffers 或其他库来为您处理,我建议您编写一对这样的函数:

// Serializes (msg) into a flat array of bytes, and returns the number of bytes written
// Note that (outBuf) must be big enough to hold any Message you might have, or there will
// be a buffer overrun!  Modifying this function to check for that problem and
// error out instead is left as an exercise for the reader.
int SerializeMessage(const struct Message & msg, char * outBuf)
{
   char * outPtr = outBuf;

   int32_t sendID = htonl(msg.id);   // htonl will make sure it gets sent in big-endian form
   memcpy(outPtr, &sendID, sizeof(sendID));
   outPtr += sizeof(sendID);

   int32_t sendLen = htonl(msg.message_length);
   memcpy(outPtr, &sendLen, sizeof(sendLen));
   outPtr += sizeof(sendLen);

   memcpy(outPtr, msg.message_str, msg.message_length);  // I'm assuming message_length=strlen(message_str)+1 here
   outPtr += msg.message_length;

   return (outPtr-outBuf);
}

// Deserializes a flat array of bytes back into a Message object.  Returns 0 on success, or -1 on failure.
int DeserializeMessage(const char * inBuf, int numBytes, struct Message & msg)
{
   const char * inPtr = inBuf;

   if (numBytes < sizeof(int32_t)) return -1;  // buffer was too short!
   int32_t recvID = ntohl(*((int32_t *)inPtr));
   inPtr += sizeof(int32_t);
   numBytes -= sizeof(int32_t);
   msg.id = recvID;

   if (numBytes < sizeof(int32_t)) return -1;   // buffer was too short!
   int32_t recvLen = ntohl(*((int32_t *)inPtr));
   inPtr += sizeof(int32_t);
   numBytes -= sizeof(int32_t);
   msg.message_length = recvLen;       if (msg.message_length > 1024) return -1;  /* Sanity check, just in case something got munged we don't want to allocate a giant array */

   msg.message_str = new char[msg.message_length];
   memcpy(msg.message_str, inPtr, numBytes);
   return 0;
}

使用这些函数,您现在可以将 Message 转换为简单的 char-array 并随意转换回来。所以现在你要做的就是通过 TCP 连接发送 char 数组,在远端接收它,然后将数组反序列化回那里的 Message 结构。

其中一个问题是您的 char 数组将是可变长度的(由于存在可能不同长度的字符串),因此您的接收者需要一些简单的方法来知道在调用 DeserializeMessage 之前要接收多少字节() 在数组上。

一个简单的处理方法是总是先发送一个 4 字节整数,然后再发送 char 数组。 4 字节整数应该始终是即将到来的数组的大小,以字节为单位。 (确保在发送之前先通过 htonl() 将整数转换为大端序,然后在使用前通过 htonl() 将其转换回接收器上的 native-endian)。

【讨论】:

  • 优秀的答案,欣赏序列化的分离。发送方和接收方之间的 4 字节转换有助于阐明发送的内容以及接收方式。谢谢
【解决方案2】:

好的,我会尝试一下。我将假设您在发送端有一个“消息”对象,您想要做的是以某种方式将其发送到另一台机器并在那里重建数据,以便您可以对其进行一些计算。您可能不清楚的部分是如何对通信数据进行编码,然后在接收端对其进行解码以恢复信息。仅写入“消息”对象中包含的字节的简单方法(即 write(fd, msg, sizeof(*msg),其中“msg”是指向“消息”类型对象的指针)将不起作用,因为你最终会将一台机器内存中的虚拟地址的值发送到不同的机器,在接收端你无能为力.所以问题是设计一种方法来传递两个整数和一个字符将绳子捆绑在一起,以便您可以在另一端将它们捞出。当然,有很多方法可以做到这一点。这是否描述了您正在尝试做的事情?

【讨论】:

  • 这正是我想要做的。我现在明白了这个问题,你不能只通过套接字发送一个结构。我是否应该创建两个 int 和一个 Char* 并将它们全部捆绑到一个缓冲区中,用 ' ' 字符分隔每个值?
  • 关于如何将两个整数和一个字符串捆绑到消息中的任何建议。
【解决方案3】:

您可以通过套接字发送结构,但您必须在使用 boost 序列化发送结构之前对其进行序列化。

这是一个示例代码:

    #include<iostream>
    #include<unistd.h>
    #include<cstring>
    #include <sstream> 

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>

    using namespace std;

    typedef struct {
        public:
            int id;
            int message_length;
            string message_str;
        private:
        friend class boost::serialization::access; 
        template <typename Archive> 
        void serialize(Archive &ar, const unsigned int vern) 
        { 
            ar & id; 
            ar & message_length;
            ar & message_str;
        }
    } Message;

    int main()
    {
        Message newMsg;
        newMsg.id = 7;
        newMsg.message_length = 14;
        newMsg.message_str="Hi ya Whats up";

        std::stringstream strData;
        boost::archive::text_oarchive oa(strData);
        oa << newMsg;
        char *serObj = (char*) strData.str().c_str();

        cout << "Serialized Data ::: " << serObj << "Len ::: " << strlen(serObj) << "\n";
        /* Send serObj thru Sockets */


        /* recv serObj from socket & deserialize it */
        std::stringstream rcvdObj(serObj);
        Message deserObj;
        boost::archive::text_iarchive ia(rcvdObj);
        ia >> deserObj;
        cout<<"id ::: "<<deserObj.id<<"\n";
        cout<<"len ::: "<<deserObj.message_length<<"\n";
        cout<<"str ::: "<<deserObj.message_str<<"\n";
    }

你可以编译程序

g++ -o 串行 boost.cpp /usr/local/lib/libboost_serialization.a

你必须在你的机器上静态编译 libboost_serialization.a。

保持套接字'阻塞'会很好,你必须设计从 recv 缓冲区读取这些结构。

【讨论】:

    最近更新 更多