【问题标题】:How to send and receive an array of integers from client to server with C sockets?如何使用 C 套接字从客户端向服务器发送和接收整数数组?
【发布时间】:2015-09-07 09:10:29
【问题描述】:

问题:使用 TCP 和套接字 C API 从客户端向服务器发送和接收整数数组(后来是浮点数)。必须在 Winsock 和 UNIX 中运行。

将来可以处理客户端/服务器的不同字节序,但现在在两台具有相同字节序的机器(Windows客户端,Linux服务器)之间进行测试。

我实现了客户端和服务器,似乎一切正常,但问题是send()(客户端)和recv()(服务器)调用如何处理我的实现方式。或者,换句话说,如果方法有缺陷。

方法是:

  1. 在客户端根据预定义的算法生成一个 uint8_t 的向量(从 0 到 254 的 N 个值序列)。此序列在服务器中复制以进行比较 与传入的数据(通过比较 2 个向量)。

  2. 在客户端,发送数组大小

  3. 在客户端,使用数组上的循环发送数组,为每个元素调用send()

  4. 在服务器上,recv() 数组大小。

  5. 在服务器上,recv() 对数组大小使用循环的数组,为每个元素调用 recv()

要检查我的方法,

  1. 我将服务器接收到的字节保存到上一个recv()循环中的文件中

  2. 循环之后,读取这个文件,按照步骤1)生成另一个相同大小的向量,比较这2个向量。 它们匹配,使用测试发送和接收了 255,000,000 个数组元素。

问题: 然后可以假设服务器recv()循环保证匹配客户端send()循环?

或者,换句话说,数组索引以相同的顺序到达?

我正在关注优秀的“C 语言中的 TCP/IP 套接字”(Donahoo、Calvert)以及回显客户端/服务器的示例

http://cs.baylor.edu/~donahoo/practical/CSockets/

引用:

“在连接的一端调用 send() 发送的字节可能不会全部由另一端的 recv() 调用返回。”

在此示例中,recv() 部分的处理方式有所不同,循环直到接收到的字节总数与发送的字节数(已知大小)匹配,根据:

while (totalBytesRcvd < echoStringLen)
{
  bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0))
  totalBytesRcvd += bytesRcvd;   /* Keep tally of total bytes */
}

完整示例:

http://cs.baylor.edu/~donahoo/practical/CSockets/code/TCPEchoClient.c

但这种情况适用于 一个 send() 的多字节调用,可能不会一次全部接收到。

在我的例子中,有 N 个发送调用(每个 1 个字节)和 N 个接收调用(每个 1 个字节),它们恰好以相同的顺序进行。

问题: TCP/IP 协议是否保证多个发送调用(具有顺序时间戳) 能保证按顺序收到吗?还是时间不是问题?

一些研究:

When sending an array of int over TCP, why are only the first amount correct?

引用:

“没有什么可以保证 TCP 将如何将您发送到流的数据打包 - 它只保证它会在应用程序级别以正确的顺序结束。”

更多链接

How do I send an array of integers over TCP in C?

谢谢

EDIT : 使用 main() 函数和用法以及变量名编辑代码以清楚起见

使用示例:向IP地址的服务器发送N个序列1次

./client -i IP-address -n N -d

代码:Client.cpp

#if defined (_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <iostream>
#include <stdint.h>
#include <vector>

const unsigned short server_port = 5000;  // server port
void client_echo_text(const char *server_ip);
void client_send_data(const char *server_ip, const uint32_t arr_size_mult);

///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////

// usage: 
// send N sequences 1 time to server at <IP adress>
// ./client -i <IP adress> -n N -d 
// same with infinite loop
// ./client -i <IP adress> -n N -l 

int main(int argc, char *argv[])
{
  char server_ip[255]; // server IP address (dotted quad)
  strcpy(server_ip, "127.0.0.1");
  uint32_t arr_size_mult = 10;

  //no arguments
  if (argc == 1)
  {
    client_send_data(server_ip, arr_size_mult);
  }

  for (int i = 1; i < argc && argv[i][0] == '-'; i++)
  {
    switch (argv[i][1])
    {
    case 'i':
      strcpy(server_ip, argv[i + 1]);
      i++;
      break;
    case 'e':
      client_echo_text(server_ip);
      exit(0);
      break;
    case 'n':
      arr_size_mult = atoi(argv[i + 1]);
      i++;
      break;
    case 'd':
      client_send_data(server_ip, arr_size_mult);
      exit(0);
      break;
    case 'l':
      while (true)
      {
        client_send_data(server_ip, arr_size_mult);
      }
      break;
    }
  }

  return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
//client_send_data
///////////////////////////////////////////////////////////////////////////////////////

void client_send_data(const char *server_ip, const uint32_t arr_size_mult)
{
  int sock;                          // socket descriptor
  struct sockaddr_in server_addr;    // server address

  //data
  const uint32_t arr_size = arr_size_mult * 255; // array size

  //construct array
  std::vector<uint8_t> val8(arr_size);
  uint8_t v8 = 0;
  for (size_t i = 0; i < arr_size; ++i)
  {
    val8[i] = v8;
    v8++;
    if (v8 == 255)
    {
      v8 = 0;
    }
  }

#if defined (_MSC_VER)
  WSADATA ws_data;
  if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
  {
    exit(1);
  }
#endif

  // create a stream socket using TCP
  if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  {
    exit(1);
  }

  // construct the server address structure
  memset(&server_addr, 0, sizeof(server_addr));          // zero out structure
  server_addr.sin_family = AF_INET;                      // internet address family
  server_addr.sin_addr.s_addr = inet_addr(server_ip);    // server IP address
  server_addr.sin_port = htons(server_port);             // server port

  // establish the connection to the server
  if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
  {
    std::cout << "connect error: " << strerror(errno) << std::endl;
    exit(1);
  }

  //send array size
  if (send(sock, (char *)&arr_size, sizeof(uint32_t), 0) != sizeof(uint32_t))
  {
    exit(1);
  }

  std::cout << "client sent array size: " << (int)arr_size << std::endl;

  //send array
  for (size_t i = 0; i < arr_size; ++i)
  {
    v8 = val8[i];
    if (send(sock, (char *)&v8, sizeof(uint8_t), 0) != sizeof(uint8_t))
    {
      exit(1);
    }
  }

  std::cout << "client sent array: " << std::endl;

#if defined (_MSC_VER)
  closesocket(sock);
  WSACleanup();
#else
  close(sock);
#endif

}

代码:Server.cpp

if defined(_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <iostream>
#include <stdint.h>
#include <assert.h>
#include <vector>

const unsigned short server_port = 5000;  // server port
void server_echo_text();
void server_recv_data(bool verbose);
void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size);

///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
  bool verbose = false;

  //no arguments
  if (argc == 1)
  {
    server_recv_data(verbose);
  }

  for (int i = 1; i < argc && argv[i][0] == '-'; i++)
  {
    switch (argv[i][1])
    {
    case 'v':
      std::cout << "verbose mode: " << std::endl;
      verbose = true;
      break;
    case 'e':
      std::cout << "running echo server: " << std::endl;
      server_echo_text();
      exit(0);
      break;
    case 'd':
      std::cout << "running data server: " << std::endl;
      server_recv_data(verbose);
      exit(0);
      break;
    }
  }

  return 0;

}

///////////////////////////////////////////////////////////////////////////////////////
//server_recv_data
///////////////////////////////////////////////////////////////////////////////////////

void server_recv_data(bool verbose)
{
  const int MAXPENDING = 5;             // maximum outstanding connection requests
  int server_socket;                    // socket descriptor for server
  int client_socket;                    // socket descriptor for client
  sockaddr_in server_addr;              // local address
  sockaddr_in client_addr;              // client address
  int  recv_size;                       // size in bytes returned by recv() 
#if defined (_MSC_VER)
  int len_addr;                         // length of client address data structure
#else
  socklen_t len_addr;
#endif

  //data
  uint32_t arr_size = 0;
  size_t slab_size = 1;
  FILE *file;

#if defined (_MSC_VER)
  WSADATA ws_data;
  if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
  {
    exit(1);
  }
#endif

  // create socket for incoming connections
  if ((server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  {
    exit(1);
  }

  // construct local address structure
  memset(&server_addr, 0, sizeof(server_addr));     // zero out structure
  server_addr.sin_family = AF_INET;                 // internet address family
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // any incoming interface
  server_addr.sin_port = htons(server_port);        // local port

  // bind to the local address
  if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) < 0)
  {
    //bind error: Permission denied
    //You're probably trying to bind a port under 1024. These ports usually require root privileges to be bound.
    std::cout << "bind error: " << strerror(errno) << std::endl;
    exit(1);
  }

  // mark the socket so it will listen for incoming connections
  if (listen(server_socket, MAXPENDING) < 0)
  {
    exit(1);
  }

  for (;;) // run forever
  {
    // set length of client address structure (in-out parameter)
    len_addr = sizeof(client_addr);

    // wait for a client to connect
    if ((client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len_addr)) < 0)
    {
      exit(1);
    }

    // convert IP addresses from a dots-and-number string to a struct in_addr and back
    char *str_ip = inet_ntoa(client_addr.sin_addr);
    std::cout << "handling client " << str_ip << std::endl;

    // receive array size
    if ((recv_size = recv(client_socket, (char *)&arr_size, sizeof(uint32_t), 0)) != sizeof(uint32_t))
    {
      exit(1);
    }

    std::cout << "server received array size: " << (int)arr_size << std::endl;

    //save file
    file = fopen("file.bin", "wb");
    fwrite(&arr_size, sizeof(uint32_t), 1, file);

    //receive array
    for (size_t i = 0; i < arr_size; ++i)
    {
      uint8_t v8;
      if ((recv_size = recv(client_socket, (char *)&v8, sizeof(uint8_t), 0)) != sizeof(uint8_t))
      {
        exit(1);
      }

      //write 1 element
      fwrite(&v8, sizeof(uint8_t), slab_size, file);
    }

    fclose(file);

    std::cout << "server received array: " << std::endl;
    check_file(arr_size, verbose, slab_size);

    // close client socket
#if defined (_MSC_VER)
    closesocket(client_socket);
#else
    close(client_socket);
#endif
  }


}

///////////////////////////////////////////////////////////////////////////////////////
//check_file
///////////////////////////////////////////////////////////////////////////////////////

void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size)
{
  //read file
  std::vector<uint8_t> val8(arr_size);
  std::vector<uint8_t> val8_c(arr_size);
  uint32_t arr_size_r;
  uint8_t v8;
  FILE *file;

  file = fopen("file.bin", "rb");
  fread(&arr_size_r, sizeof(uint32_t), 1, file);
  assert(arr_size_r == arr_size);

  for (size_t i = 0; i < arr_size; ++i)
  {
    fread(&v8, sizeof(uint8_t), slab_size, file);
    val8[i] = v8;
    if (verbose) std::cout << (int)val8[i] << " ";

  }
  if (verbose) std::cout << std::endl;

  fclose(file);

  //check data, define array the same as in client, compare arrays
  v8 = 0;
  for (size_t i = 0; i < arr_size; ++i)
  {
    val8_c[i] = v8;
    v8++;
    if (v8 == 255)
    {
      v8 = 0;
    }
  }

  //compare arrays
  for (size_t i = 0; i < arr_size; ++i)
  {
    if (val8_c[i] != val8[i])
    {
      std::cout << "arrays differ at: " << i << " " << (int)val8_c[i] << " " << (int)val8[i] << std::endl;
      assert(0);
    }
  }

  std::cout << "arrays match: " << (int)arr_size << " " << (int)arr_size_r << std::endl;
  std::cout << std::endl;

}

【问题讨论】:

  • std::cout 闻起来不像C。 :)
  • 如果您稍后考虑使用浮点数(或者可能是更复杂的数据类型组合),您可以考虑使用库将数据结构化为流。 Google Protobufs、Cap'n Proto、MAVlink 或 Apache Thrift 为您执行此操作(后两者也原生支持 c)。它们提供了在发送数据之前将数据转换为字节流并在接收数据后正确读取的有效方法。
  • TCP 保证数据将按照它们在流中发送出去的顺序传递。不过,为 sendrecv 使用一字节缓冲区效率非常低。
  • @Andreas Gschossmann。感谢您的建议。这是一个个人项目,主要目标是学习,所以我会尽可能从头开始做所有事情,否则就没有乐趣了:-)。你是对的,当来到花车时,这将是一个更大的挑战:-)
  • 您假设 recv 将读取“消息”。 TCP 不支持消息。该程序将随机失败。

标签: c++ arrays sockets tcp


【解决方案1】:

TCP 是一个streaming 协议,它保证发送的stream 在接收端的精确复制。所以是的,排序总是匹配的。如果消息乱序,协议栈将重新排序消息。因此,如果您可靠地捕捉到流的开头和结尾,那么介于两者之间的一切都会井然有序并处于良好状态。

我不确定您是否想发送一个数字而不是将它们预先编组到一个大缓冲区中。您将获得几个数量级的性能提升。

【讨论】:

    【解决方案2】:

    正如@usr 指出的那样,循环的构造很糟糕。需要的是“全部发送”和“全部接收”功能。

    这些是基于史蒂文斯的书“UNIX网络编程:套接字介绍”

    http://www.informit.com/articles/article.aspx?p=169505&seqNum=9

    从客户端发送所有函数和发送函数:

    void send_all(int sock, const void *vbuf, size_t size_buf)
    {
      const char *buf = (char*)vbuf;    // can't do pointer arithmetic on void* 
      int send_size;                  // size in bytes sent or -1 on error 
      size_t size_left;               // size left to send 
      const int flags = 0;
    
      size_left = size_buf;
    
      while (size_left > 0)
      {
        if ((send_size = send(sock, buf, size_left, flags)) == -1)
        {
          std::cout << "send error: " << strerror(errno) << std::endl;
          exit(1);
        }
    
        if (send_size == 0)
        {
          std::cout << "all bytes sent " << std::endl;
          break;
        }
    
        size_left -= send_size;
        buf += send_size;
      }
    
      return;
    }
    
     ///////////////////////////////////////////////////////////////////////////////////////
    //client_send_data
    ///////////////////////////////////////////////////////////////////////////////////////
    
    void client_send_data(const char *server_ip, const uint32_t arr_size_mult)
    {
      int sock;                          // socket descriptor
      struct sockaddr_in server_addr;    // server address
    
      //data
      const uint32_t arr_size = arr_size_mult * 255; // array size
    
      //construct array
      std::vector<uint8_t> val8(arr_size);
      uint8_t v8 = 0;
      for (size_t i = 0; i < arr_size; ++i)
      {
        val8[i] = v8;
        v8++;
        if (v8 == 255)
        {
          v8 = 0;
        }
      }
    
    #if defined (_MSC_VER)
          WSADATA ws_data;
          if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
          {
            exit(1);
          }
    #endif
    
      // create a stream socket using TCP
      if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
      {
        exit(1);
      }
    
      // construct the server address structure
      memset(&server_addr, 0, sizeof(server_addr));          // zero out structure
      server_addr.sin_family = AF_INET;                      // internet address family
      server_addr.sin_addr.s_addr = inet_addr(server_ip);    // server IP address
      server_addr.sin_port = htons(server_port);             // server port
    
      // establish the connection to the server
      if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
      {
        std::cout << "connect error: " << strerror(errno) << std::endl;
        exit(1);
      }
    
      //send array size
      send_all(sock, (void *)&arr_size, sizeof(uint32_t));
    
      std::cout << "client sent array size: " << (int)arr_size << std::endl;
    
      //send array
      //std::vector.data() returns the address of the initial element in the container (C++11)
      send_all(sock, (void *)val8.data(), sizeof(uint8_t) * val8.size());
    
      std::cout << "client sent array: " << std::endl;
    
    #if defined (_MSC_VER)
          closesocket(sock);
          WSACleanup();
    #else
          close(sock);
    #endif
    
    }
    

    接收所有功能

    void recv_all(int sock, void *vbuf, size_t size_buf, FILE *file)
    {
      char *buf = (char*)vbuf;  // can't do pointer arithmetic on void* 
      int recv_size;            // size in bytes received or -1 on error 
      size_t size_left;         // size left to send 
      const int flags = 0;
    
      size_left = size_buf;
    
      while (size_left > 0)
      {
        if ((recv_size = recv(sock, buf, size_left, flags)) == -1)
        {
          std::cout << "recv error: " << strerror(errno) << std::endl;
          exit(1);
        }
    
        if (recv_size == 0)
        {
          std::cout << "all bytes received " << std::endl;
          break;
        }
    
        //save to local file
        fwrite(buf, recv_size, 1, file);
    
        size_left -= recv_size;
        buf += recv_size;
      }
    
      return;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////
    //server_recv_data
    ///////////////////////////////////////////////////////////////////////////////////////
    
    void server_recv_data(bool verbose)
    {
      const int MAXPENDING = 5;             // maximum outstanding connection requests
      int server_socket;                    // socket descriptor for server
      int client_socket;                    // socket descriptor for client
      sockaddr_in server_addr;              // local address
      sockaddr_in client_addr;              // client address
    #if defined (_MSC_VER)
          int len_addr;                         // length of client address data structure
    #else
          socklen_t len_addr;
    #endif
    
      //data
      uint32_t arr_size = 0;
      const size_t slab_size = 1;
      FILE *file;
    
    #if defined (_MSC_VER)
          WSADATA ws_data;
          if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
          {
            exit(1);
          }
    #endif
    
      // create socket for incoming connections
      if ((server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
      {
        exit(1);
      }
    
      // construct local address structure
      memset(&server_addr, 0, sizeof(server_addr));     // zero out structure
      server_addr.sin_family = AF_INET;                 // internet address family
      server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // any incoming interface
      server_addr.sin_port = htons(server_port);        // local port
    
      // bind to the local address
      if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) < 0)
      {
        //bind error: Permission denied
        //You're probably trying to bind a port under 1024. These ports usually require root privileges to be bound.
        std::cout << "bind error: " << strerror(errno) << std::endl;
        exit(1);
      }
    
      // mark the socket so it will listen for incoming connections
      if (listen(server_socket, MAXPENDING) < 0)
      {
        exit(1);
      }
    
      for (;;) // run forever
      {
        // set length of client address structure (in-out parameter)
        len_addr = sizeof(client_addr);
    
        // wait for a client to connect
        if ((client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len_addr)) < 0)
        {
          exit(1);
        }
    
        // convert IP addresses from a dots-and-number string to a struct in_addr and back
        char *str_ip = inet_ntoa(client_addr.sin_addr);
        std::cout << "handling client " << str_ip << std::endl;
    
        ///////////////////////////////////////////////////////////////////////////////////////
        //receive data and save to local file as received
        ///////////////////////////////////////////////////////////////////////////////////////
    
        //save local file
        file = fopen(check_file_name.c_str(), "wb");
    
        //receive/save array size
        recv_all(client_socket, &arr_size, sizeof(uint32_t), file);
    
        std::cout << "server received array size: " << (int)arr_size << std::endl;
    
        //receive/save array
        uint8_t *buf = new uint8_t[arr_size];
    
        recv_all(client_socket, buf, sizeof(uint8_t) * arr_size, file);
    
        delete[] buf;
    
        fclose(file);
    
        std::cout << "server received array: " << std::endl;
    
        //check
        check_file(arr_size, verbose, slab_size);
    
        // close client socket
    #if defined (_MSC_VER)
            closesocket(client_socket);
    #else
            close(client_socket);
    #endif
      }
    
    
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-29
      • 1970-01-01
      • 2013-12-26
      • 2019-08-25
      • 2020-07-10
      相关资源
      最近更新 更多