Sending Message Over Tcp/IP using Sockets

2019-09-07 01:59发布

问题:

I am trying to send data between a client/Server, the data looks like

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

I am trying to Write and Read this message between a client and server constantly updating the elements in this struct. I have heard Writev may do the trick. i want to send a message to the server and then the server pulls out the elements and uses those elements as conditionals to execute the proper method?

回答1:

Assuming you want to do the serialization yourself and not use Google Protocol Buffers or some library to handle it for you, I'd suggest writing a pair of functions like this:

// 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;
}

With these functions, you are now able to convert a Message into a simple char-array and back at will. So now all you have to do is send the char-array over the TCP connection, receive it at the far end, and then Deserialize the array back into a Message struct there.

One wrinkle with this is that your char arrays will be variable-length (due to the presence of a string which can be different lengths), so your receiver will need some easy way to know how many bytes to receive before calling DeserializeMessage() on the array.

An easy way to handle that is to always send a 4-byte integer first, before sending the char-array. The 4-byte integer should always be the size of the upcoming array, in bytes. (Be sure to convert the integer to big-endian first, via htonl(), before sending it, and convert it back to native-endian on the receiver via htonl() before using it).



回答2:

Okay, I'll take a stab at this. I'm going to assume that you have a "message" object on the sending side and what you want to do is somehow send it across to another machine and reconstruct the data there so you can do some computation on it. The part that you may not be clear on is how to encode the data for communications and then decode it on the receiving side to recover the information. The simplistic approach of just writing the bytes contained in a "message" object (i.e. write(fd, msg, sizeof(*msg), where "msg" is a pointer to an object of type "message") won't work because you will end up sending the value of a virtual address in the memory of one machine to different machine and there's not much you can do with that on the receiving end. So the problem is to design a way to pass an two integers and a character string bundled up in a way that you can fish them back out on the other end. There are, of course, many ways to do this. Does this describe what you are trying to do?



回答3:

You can send structs over socket, but you have to serialize them before sending the struct using boost serialization.

Here is a sample code :

    #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";
    }

you can compile the program by

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

you must have libboost_serialization.a statically compiled in your machine.

Keeping the sockets 'blocking' will be good and you have to devise for reading these structs from recv buffer.