Data transfer over sockets[TCP] how to pack multiple integer in c/c++ and transfer the data with send() recv()?

女生的网名这么多〃 提交于 2021-01-20 08:39:43

问题


I'm making a small client/server based game, on linux in c/c++ and I need to send the player turn to the server.

Here is my problem.

I want to send two integers to the server and sometimes it works perfectly, but sometimes the server receives both integer in the first recv() and its stuck.

I know that the best way is to package the messages. The problem is I don't know how the syntax should look like.

In theory--> the player input would be like an int column = 4 and a second int row = 1 and I package the message as 4|1 or something like this. Then I send from client to server and encode it on the server. An example would be great or maybe some advice how stuff like this is handled probably. I'm still very new to socket programming.

Here is how my function looks like:

Client:

#define BUFFER 512

void send_turn_to_server(int sock, int row, int column)
{
   // sends  row to server from player turn

   char char_row[BUFFER];
   sprintf(char_row, "%d", row);
   char *message_from_client = char_row;
   int len, bytes_sent_row;
   len = strlen(message_from_client);

   if (sendall(sock, message_from_client, &len) == -1)
   {
      perror("sendall");
      printf("We only sent %d bytes because of the error!\n", len);
   }


   char char_column[BUFFER];
   int bytes_sent_column;
   //sends column from player turn
   //sprintf converts the int to char
   sprintf(char_column, "%d", column);
   char *column_from_client = char_column;
   len = strlen(column_from_client);

   if (sendall(sock, column_from_client, &len) == -1)
   {
      perror("sendall");
      printf("We only sent %d bytes because of the error!\n", len);
   }
   cout << "send_turn_to_server_complete" << endl;
}

Here I use a function from Beej's Guide to Network Programming, so I can be sure the whole buffer is sent.

Client:

int sendall(int s, char *buf, int *len)
{
   int total = 0;        // how many bytes we've sent
   int bytesleft = *len; // how many we have left to send
   int n;

   while (total < *len)
   {
      n = send(s, buf + total, bytesleft, 0);
      if (n == -1)
      {
         break;
      }
      total += n;
      bytesleft -= n;
   }

   *len = total; // return number actually sent here

   return n == -1 ? -1 : 0; // return -1 on failure, 0 on success
}

Server:

int receive_player_turn(int sock, int &int_row, int &int_column)
{
   int byte_count;
   char buf[BUFFER];
   byte_count = recv(sock, buf, sizeof buf, 0);
   cout << "The row from player: " << buf << endl;
   //The C library function int atoi(const char *str) converts the string argument str to an integer (type int).
   int_row = atoi(buf);
   //cleans the buffer
   bzero(buf, sizeof(buf));

   byte_count = recv(sock, buf, sizeof buf, 0);
   cout << buf << endl;
   cout << "The column from player: " << buf << endl;

   //converts the char string to an int
   int_column = atoi(buf);

   cout << endl

        << "receive player turn worked" << endl
        << "players turn was in the row " << int_row << " and in the column " << int_column + 1 << endl;

   return int_row, int_column;
}

output correct from server:

Player connected: SchleichsSalaticus
The row from player: 7
4
The column from player: 4

receive player turn worked
players turn was in the row 7 and in the column 5  
7 4

output wrong from server:

Player connected: SchleichsSalaticus
The row from player: 74

回答1:


The issue is that TCP is a continuous stream, with no concept of the start or end of a ”message” because it is not message-based.

Most times, people use a very simple ”framing protocol” whereby you always send a 4-byte header on every transfer which tells the recipient how many bytes to read, then you send that many bytes as your message.

Use htonl() to send the 4-byte header in network byte order then you will be interoperable. There is a very similar example here.




回答2:


One possible solution could be defining a format for the message that client send to server. For example you could define a protocol as follow:

[4 bytes length of your message][2 bytes for first player][2 bytes for second one] and in server side you should at first in rcv function get 4 bytes and extract the length of the arrived message and based on the receiving length(L) call again the rcv function with size L after that you should parse received messaged and extract the turn of each players.




回答3:


If all your messages are expected to be of same length, then you do not need a message header. Something like that given below should work fine. In general you should be prepared to receive less or more than your expected message, as well as for one message to be split across many receives.

Also, I would recommend one function that receives bytes making no assumption about what they mean, and another that interprets them. Then the first one can be applied more broadly.

Treat the following only as pseudo code. not tested.

   // use a buffer length double of MESSAGE_LENGTH.
   static int offset = 0; // not thread safe.
   // loop to receive a message.
   while(offset < MESSAGE_LENGTH) {
       byte_count = recv(sock, &buf[offset],  (sizeof(buf)-offset), 0);
       if(byte_count > 0) {
          offset += byte_count;
       }
       else {
          // add error handling here. close socket.
          break out of loop
       }      
   }
   //  process buf here, but do not clear it.
   //  received message always starts at buf[0].
   
   if(no receive error above) {
       process_received_message(buf); // 
   }

   // move part of next message (if any) to start of buffer.
   if(offset > MESSAGE_LENGTH) {
        // copy the start of next message to start of buffer.
        // and remember the new offset to avoid overwriting them.
        char* pSrc = &buf[MESSAGE_LENGTH];
        char* pSrcEnd = &buf[offset];
        char* pDest = buf;
        while(pSrc < pSrcEnd){
           *pDest++ = *pSrc++;
        } //or memcpy.    
        offset -= MESSAGE_LENGTH;
   } 
   else {
        offset = 0;    
   }



回答4:


On many hardware architectures, integers and other types have alignment requirements. The compiler normally takes care of this, but when in a buffer, unaligned accesses can be an issue. Furthermore, the server and the client might not use the same byte order.

Here is a set of inline helper functions you can use to pack and unpack integer types to/from a buffer:

/* SPDX-License-Identifier: CC0-1.0 */

#ifndef   PACKING_H
#define   PACKING_H
#include <stdint.h>

/* Packing and unpacking unsigned and signed integers in
   little-endian byte order.
   Works on all architectures and OSes when compiled
   using a standards-conforming C implementation, C99 or later.
*/

static inline void pack_u8(unsigned char *dst, uint8_t val)
{
    dst[0] =  val & 255;
}

static inline void pack_u16(unsigned char *dst, uint16_t val)
{
    dst[0] =  val       & 255;
    dst[1] = (val >> 8) & 255;
}

static inline void pack_u24(unsigned char *dst, uint32_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
}

static inline void pack_u32(unsigned char *dst, uint32_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
    dst[3] = (val >> 24) & 255;
}

static inline void pack_u40(unsigned char *dst, uint64_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
    dst[3] = (val >> 24) & 255;
    dst[4] = (val >> 32) & 255;
}

static inline void pack_u48(unsigned char *dst, uint64_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
    dst[3] = (val >> 24) & 255;
    dst[4] = (val >> 32) & 255;
    dst[5] = (val >> 40) & 255;
}

static inline void pack_u56(unsigned char *dst, uint64_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
    dst[3] = (val >> 24) & 255;
    dst[4] = (val >> 32) & 255;
    dst[5] = (val >> 40) & 255;
    dst[6] = (val >> 48) & 255;
}

static inline void pack_u64(unsigned char *dst, uint64_t val)
{
    dst[0] =  val        & 255;
    dst[1] = (val >> 8)  & 255;
    dst[2] = (val >> 16) & 255;
    dst[3] = (val >> 24) & 255;
    dst[4] = (val >> 32) & 255;
    dst[5] = (val >> 40) & 255;
    dst[6] = (val >> 48) & 255;
    dst[7] = (val >> 56) & 255;
}

static inline void pack_i8(unsigned char *dst, int8_t val)
{
    pack_u8((uint8_t)val);
}

static inline void pack_i16(unsigned char *dst, int16_t val)
{
    pack_u16((uint16_t)val);
}

static inline void pack_i24(unsigned char *dst, int32_t val)
{
    pack_u24((uint32_t)val);
}

static inline void pack_i32(unsigned char *dst, int32_t val)
{
    pack_u32((uint32_t)val);
}

static inline void pack_i40(unsigned char *dst, int64_t val)
{
    pack_u40((uint64_t)val);
}

static inline void pack_i48(unsigned char *dst, int64_t val)
{
    pack_u48((uint64_t)val);
}

static inline void pack_i56(unsigned char *dst, int64_t val)
{
    pack_u56((uint64_t)val);
}

static inline void pack_i64(unsigned char *dst, int64_t val)
{
    pack_u64((uint64_t)val);
}

static inline uint8_t unpack_u8(const unsigned char *src)
{
    return (uint_fast8_t)(src[0] & 255);
}

static inline uint16_t unpack_u16(const unsigned char *src)
{
    return  (uint_fast16_t)(src[0] & 255)
         | ((uint_fast16_t)(src[1] & 255) << 8);
}

static inline uint32_t unpack_u24(const unsigned char *src)
{
    return  (uint_fast32_t)(src[0] & 255)
         | ((uint_fast32_t)(src[1] & 255) << 8)
         | ((uint_fast32_t)(src[2] & 255) << 16);
}

static inline uint32_t unpack_u32(const unsigned char *src)
{
    return  (uint_fast32_t)(src[0] & 255)
         | ((uint_fast32_t)(src[1] & 255) << 8)
         | ((uint_fast32_t)(src[2] & 255) << 16)
         | ((uint_fast32_t)(src[3] & 255) << 24);
}

static inline uint64_t unpack_u40(const unsigned char *src)
{
    return  (uint_fast64_t)(src[0] & 255)
         | ((uint_fast64_t)(src[1] & 255) << 8)
         | ((uint_fast64_t)(src[2] & 255) << 16)
         | ((uint_fast64_t)(src[3] & 255) << 24)
         | ((uint_fast64_t)(src[4] & 255) << 32);
}

static inline uint64_t unpack_u48(const unsigned char *src)
{
    return  (uint_fast64_t)(src[0] & 255)
         | ((uint_fast64_t)(src[1] & 255) << 8)
         | ((uint_fast64_t)(src[2] & 255) << 16)
         | ((uint_fast64_t)(src[3] & 255) << 24)
         | ((uint_fast64_t)(src[4] & 255) << 32)
         | ((uint_fast64_t)(src[5] & 255) << 40);
}

static inline uint64_t unpack_u56(const unsigned char *src)
{
    return  (uint_fast64_t)(src[0] & 255)
         | ((uint_fast64_t)(src[1] & 255) << 8)
         | ((uint_fast64_t)(src[2] & 255) << 16)
         | ((uint_fast64_t)(src[3] & 255) << 24)
         | ((uint_fast64_t)(src[4] & 255) << 32)
         | ((uint_fast64_t)(src[5] & 255) << 40)
         | ((uint_fast64_t)(src[6] & 255) << 48);
}

static inline uint64_t unpack_u64(const unsigned char *src)
{
    return  (uint_fast64_t)(src[0] & 255)
         | ((uint_fast64_t)(src[1] & 255) << 8)
         | ((uint_fast64_t)(src[2] & 255) << 16)
         | ((uint_fast64_t)(src[3] & 255) << 24)
         | ((uint_fast64_t)(src[4] & 255) << 32)
         | ((uint_fast64_t)(src[5] & 255) << 40)
         | ((uint_fast64_t)(src[6] & 255) << 48)
         | ((uint_fast64_t)(src[7] & 255) << 56);
}

static inline int8_t unpack_i8(const unsigned char *src)
{
    return (int8_t)(src[0] & 255);
}

static inline int16_t unpack_i16(const unsigned char *src)
{
    return (int16_t)unpack_u16(src);
}

static inline int32_t unpack_i24(const unsigned char *src)
{
    uint_fast32_t u = unpack_u24(src);
    /* Sign extend to 32 bits */
    if (u & 0x800000)
        u |= 0xFF000000;
    return (int32_t)u;
}

static inline int32_t unpack_i32(const unsigned char *src)
{
    return (int32_t)unpack_u32(src);
}

static inline int64_t unpack_i40(const unsigned char *src)
{
    uint_fast64_t u = unpack_u40(src);
    /* Sign extend to 64 bits */
    if (u & UINT64_C(0x0000008000000000))
        u |= UINT64_C(0xFFFFFF0000000000);
    return (int64_t)u;
}

static inline int64_t unpack_i48(const unsigned char *src)
{
    uint_fast64_t u = unpack_i48(src);
    /* Sign extend to 64 bits */
    if (u & UINT64_C(0x0000800000000000))
        u |= UINT64_C(0xFFFF000000000000);
    return (int64_t)u;
}

static inline int64_t unpack_i56(const unsigned char *src)
{
    uint_fast64_t u = unpack_u56(src);
    /* Sign extend to 64 bits */
    if (u & UINT64_C(0x0080000000000000))
        u |= UINT64_C(0xFF00000000000000);
    return (int64_t)u;
}

static inline int64_t unpack_i64(const unsigned char *src)
{
    return (int64_t)unpack_u64(src);
}

#endif /* PACKING_H */

When packed, these values are in two's complement little-endian byte order.

pack_uN() and unpack_uN() work with unsigned integers from 0 to 2N-1, inclusive.

pack_iN() and unpack_iN() work with signed integers from -2N-1 to 2N-1-1, inclusive.

Let's consider a simple binary protocol, where each message starts with two bytes: first one the total length of this message, and the second one identifying the type of the message.

This has the nice feature that if something odd happens, it is always possible to resynchronize by sending at least 256 zeroes. Each zero is an invalid length for the message, so they should just be skipped by the receiver. You probably won't need this, but it may come in handy someday.

To receive a message of this form, we can use the following function:

/* Receive a single message.
   'fd' is the socket descriptor, and
   'msg' is a buffer of at least 255 chars.
   Returns -1 with errno set if an error occurs,
   or the message type (0 to 255, inclusive) if success.
*/
int recv_message(const int fd, unsigned char *msg)
{
    ssize_t  n;

    msg[0] = 0;
    msg[1] = 0;

    /* Loop to skip zero bytes. */
    do {

        do {
            n = read(fd, msg, 1);
        } while (n == -1 && errno == EINTR);
        if (n == -1) {
            /* Error; errno already set. */
            return -1;
        } else
        if (n == 0) {
            /* Other end closed the socket. */
            errno = EPIPE;
            return -1;
        } else
        if (n != 1) {
            errno = EIO;
            return -1;
        }

    } while (msg[0] == 0);

    /* Read the rest of the message. */        
    {
        unsigned char *const end = msg + msg[0];
        unsigned char       *ptr = msg + 1;

        while (ptr < end) {
            n = read(fd, ptr, (size_t)(end - ptr));
            if (n > 0) {
                ptr += n;
            } else
            if (n == 0) {
                /* Other end closed socket */
                errno = EPIPE;
                return -1;
            } else
            if (n != -1) {
                errno = EIO;
                return -1;
            } else
            if (errno != EINTR) {
                /* Error; errno already set */
                return -1;
            }
        }
    }

    /* Success, return message type. */
    return msg[1];
}

In your own code, you can use the above like this:

    unsigned char buffer[256];

    switch(receive_message(fd, buffer)) {
    case -1:
        if (errno == EPIPE) {
            /* The other end closed the connection */
        } else {
            /* Other error; see strerror(errno). */
        }
        break or return or abort;

    case 0: /* Exit/cancel game */
        break or return or abort;

    case 4: /* Coordinate message */
        int x = unpack_i16(buffer + 2);
        int y = unpack_i16(buffer + 4);
        
        /* x,y is the coordinate pair; do something */

        break;

    default:
        /* Ignore all other message types */
    }

where I randomly chose 0 as the abort-game message type, and 4 as the coordinate message type.

Instead of scattering such statements here and there in your client, put it in a function. You could also consider using a finite-state machine to represent the game state.

To send messages, you can use a helper function like

/* Send one or more messages; does not verify contents.
   Returns 0 if success, -1 with errno set if an error occurs.
*/
int send_message(const int fd, const void *msg, const size_t len)
{
    const unsigned char *const end = (const unsigned char *)msg + len;
    const unsigned char       *ptr = (const unsigned char *)msg;
    ssize_t                    n;

    while (ptr < end) {
        n = write(fd, ptr, (size_t)(end - ptr));
        if (n > 0) {
            ptr += n;

        } else
        if (n != -1) {
            /* C library bug, should not occur */
            errno = EIO;
            return -1;
        } else
        if (errno != EINTR) {
            /* Other error */
            return -1;
        }
    }

    return 0;
}

so that sending an abort game (type 0) message would be

int send_abort_message(const int fd)
{
    unsigned char buffer[2] = { 1, 0 };
    return send_message(fd, buffer, 2);
}

and sending a coordinate (type 4) message would be e.g.

int send_coordinates(const int fd, const int x, const int y)
{
    unsigned char buffer[2 + 2 + 2];
    buffer[0] = 6;  /* Length in bytes/chars */
    buffer[1] = 4;  /* Type */
    pack_i16(buffer + 2, x);
    pack_i16(buffer + 4, y);
    return send_message(fd, buffer, 6);
}

If the game is not turn-based, you won't want to block in the sends or receives, like the above functions do.

Nonblocking I/O is the way to go. Essentially, you'll have something like

static int            server_fd = -1;

static size_t         send_size = 0;
static unsigned char *send_data = NULL;
static size_t         send_next = 0;    /* First unsent byte */
static size_t         send_ends = 0;    /* End of buffered data */

static size_t         recv_size = 0;
static unsigned char *recv_data = NULL;
static size_t         recv_next = 0;    /* Start of next message */
static size_t         recv_ends = 0;    /* End of buffered data */

and you set the server_fd nonblocking using e.g. fcntl(server_fd, F_SETFL, O_NONBLOCK);.

A communicator function will try to send and receive as much data as possible. It will return 1 if it sent anything, 2 if it received anything, 3 if both, 0 if neither, and -1 if an error occurred:

int communicate(void) {
    int      retval = 0;
    ssize_t  n;

    while (send_next < send_ends) {
        n = write(server_fd, send_data + send_next, send_ends - send_next);
        if (n > 0) {
            send_next += n;
            retval |= 1;
        } else
        if (n != -1) {
            /* errno already set */
            return -1;
        } else
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            /* Cannot send more without blocking */
            break;
        } else
        if (errno != EINTR) {
            /* Error, errno set */
            return -1;
        }
    }

    /* If send buffer became empty, reset it. */
    if (send_next >= send_ends) {
        send_next = 0;
        send_ends = 0;
    }

    /* If receive buffer is empty, reset it. */
    if (recv_next >= recv_ends) {
        recv_next = 0;
        recv_ends = 0;
    }

    /* Receive loop. */
    while (1) {

        /* Receive buffer full? */
        if (recv_ends + 256 > recv_ends) {
            /* First try to repack. */
            if (recv_next > 0) {
                memmove(recv_data, recv_data + recv_next, recv_ends - recv_next);
                recv_ends -= recv_next;
                recv_next = 0;
            }
            if (recv_ends + 256 > recv_ends) {
                /* Allocate 16k more (256 messages!) */
                size_t         new_size = recv_size + 16384;
                unsigned char *new_data;
                
                new_data = realloc(recv_data, new_size);
                if (!new_data) {
                    errno = ENOMEM;
                    return -1;
                }
                
                recv_data = new_data;
                recv_size = new_size;
            }
        }

        /* Try to receive incoming data. */
        n = read(server_fd, recv_data + recv_ends, recv_size - recv_ends);
        if (n > 0) {
            recv_ends += n;
            retval |= 2;
        } else
        if (n == 0) {
            /* Other end closed the connection. */
            errno = EPIPE;
            return -1;
        } else
        if (n != -1) {
            errno = EIO;
            return -1;
        } else
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            break;
        } else
        if (errno != EINTR) {
            return -1;
        }
    }

    return retval;
}

When there is nothing to do, and you want to wait for a short while (some milliseconds), but interrupt the wait whenever more I/O can be done, use

/* Wait for max 'ms' milliseconds for communication to occur.
   Returns 1 if data received, 2 if sent, 3 if both, 0 if neither
   (having waited for 'ms' milliseconds), or -1 if an error occurs.
*/
int communicate_wait(int ms)
{
    struct pollfd  fds[1];
    int            retval;

    /* Zero timeout is "forever", and we don't want that. */
    if (ms < 1)
        ms = 1;

    /* We try communicating right now. */
    retval = communicate();
    if (retval)
        return retval;

    /* Poll until I/O possible. */
    fds[0].fd = server_fd;
    if (send_ends > send_next)
        fds[0].events = POLLIN | POLLOUT;
    else
        fds[0].events = POLLIN;
    fds[0].revents = 0;
    poll(fds, 1, ms);

    /* We retry I/O now. */
    return communicate();
}

To process messages received thus far, you use a loop:

    while (recv_next < recv_ends && recv_next + recv_data[recv_next] <= recv_ends) {
        if (recv_data[recv_next] == 0) {
            recv_next++;
            continue;
        }

        /* recv_data[recv_next+0] is the length of the message,
           recv_data[recv_next+1] is the type of the message. */

        switch (recv_data[recv_next + 1]) {

        case 4: /* Coordinate message */
            if (recv_data[recv_next] >= 6) {
                int  x = unpack_i16(recv_data + recv_next + 2);
                int  y = unpack_i16(recv_data + recv_next + 4);

                /* Do something with x and y ... */

            }
            break;

        /* Handle other message types ... */

        }

        recv_next += recv_data[recv_next];
    }

Then you recalculate game state, update the display, communicate some more, and repeat.



来源:https://stackoverflow.com/questions/62874574/data-transfer-over-socketstcp-how-to-pack-multiple-integer-in-c-c-and-transf

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!