简体   繁体   English

如何使用C套接字从客户端发送和接收整数数组?

[英]How to send and receive an array of integers from client to server with C sockets?

The problem: send and receive an array of integers (later floats) from client to server using TCP and the sockets C API. 问题:使用TCP和套接字C API从客户端发送和接收整数数组(以后的浮点数)。 Must run both in Winsock and UNIX. 必须在Winsock和UNIX中运行。

In the future different endianess for client/server can be handled, but now the test was made between 2 machines with same endianess (Windows client, Linux server). 将来可以处理客户端/服务器的不同endianess,但现在测试是在两台具有相同endianess的计算机之间进行的(Windows客户端,Linux服务器)。

I implemented the client and server, all seems to work, but the question is a doubt on how the send() (client) and recv() (server) calls handle the way my implementation is made. 我实现了客户端和服务器,似乎都工作,但问题是如何send() (客户端)和recv() (服务器)调用处理我的实现方式。 Or, in other words, if the approach has a flaw. 或者,换句话说,如果方法有缺陷。

The approach was: 方法是:

  1. On the client generate a vector of uint8_t according to a predefined algorithm (N sequences of values from 0 to 254). 在客户端上根据预定义算法生成uint8_t的向量(N个值从0到254的序列)。 This sequence is reproduced in the server to compare with the incoming data (by comparing 2 vectors). 在服务器中再现该序列以与输入数据进行比较(通过比较2个向量)。

  2. On the client, send the array size 在客户端上,发送数组大小

  3. On the client, send the array using a loop on the array, call send() for each element. 在客户端上,使用数组上的循环send()数组,为每个元素调用send()

  4. On the server, recv() the array size. 在服务器上, recv()数组大小。

  5. On the server, recv() the array using a loop on the array size, call recv() for each element. 在服务器上, recv()使用在阵列大小的循环阵列,调用recv()为每个元素。

To check my approach, 检查我的方法,

  1. I save the bytes received on the server to a file inside the previous recv() loop 我将服务器上收到的字节保存到上一个recv()循环内的文件中

  2. After the loop , read this file, generate another vector with the same size according to step 1), compare the 2 vectors. 在循环之后,读取该文件,根据步骤1)生成具有相同大小的另一个向量,比较2个向量。 They match, using tests up 255,000,000 array elements sent and received. 它们匹配,使用测试发送和接收的255,000,000个数组元素。

Question : One can then assume that the server recv() loop is guaranteed to match the client send() loop? 问题 :可以假设服务器recv()循环保证与客户端send()循环匹配?

Or, in other words, that the array indices arrive in the same order? 或者,换句话说,数组索引以相同的顺序到达?

I am following the excellent "TCP/IP Sockets in C" (Donahoo, Calvert) and on the example of echo client / server 我正在关注优秀的“C中的TCP / IP套接字”(Donahoo,Calvert)以及echo客户端/服务器的示例

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

Quote: 引用:

"The bytes sent by a call to send() on one end of a connection may not all be returned by a single call to recv() on the other end." “在一个连接的一端调用send()发送的字节可能不会全部由另一端的recv()调用返回。”

The recv() part is handled differently in this example, a loop is made until the total number of bytes received matches the (known size) of bytes sent, according to: 在这个例子中, recv()部分的处理方式不同,根据以下内容进行循环,直到收到的字节总数与发送的字节数(已知大小)相匹配为止:

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

Complete example: 完整的例子:

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

But this case is for one send() with multiple bytes call that might be not received all at once. 但是这种情况适用于一个 send()具有多个字节调用,可能无法一次全部接收。

In my case there are N send calls (1 byte each) and N receive calls (1 byte each), that happen to be made in the same order. 在我的情况下,有N个发送调用(每个1个字节)和N个接收调用(每个1个字节),恰好按相同的顺序进行。

Question: Does the TCP/IP protocol guarantee that the multiple send calls (that have sequential time stamps) are guaranteed to be received in order? 问题: TCP / IP协议是否保证按顺序接收多个发送呼叫(具有连续时间戳)? Or time not an issue here? 或者时间不是问题吗?

Some research: 有些研究:

When sending an array of int over TCP, why are only the first amount correct? 通过TCP发送int数组时,为什么只有第一个数量正确?

Quote: 引用:

"There is nothing to guarantee how TCP will packet up the data you send to a stream - it only guarantees that it will end up in the correct order at the application level." “没有什么可以保证TCP如何将你发送到数据流的数据分组 - 它只能保证它在应用程序级别以正确的顺序结束。”

Some more links 更多链接

How do I send an array of integers over TCP in C? 如何在C中通过TCP发送整数数组?

Thanks 谢谢

EDIT : code edited with main() functions and usage, and variable names for clarity 编辑:使用main()函数和用法编辑的代码,以及变量名称,以便清楚

Usage example: send N sequences 1 time to server at IP-address 用法示例:将N个序列1次发送到IP地址的服务器

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

Code: Client.cpp 代码: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

}

Code: Server.cpp 代码: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;

}

TCP is a streaming protocol, it guarantees the exact replication of the sent stream at receiver. TCP是一种协议,它保证了接收方发送的精确复制。 So yes, ordering will match, always. 所以是的,订购总是匹配。 The protocol stack will reorder messages if they come out of order. 如果消息出现故障,协议栈将重新排序消息。 So if you reliably catch the beginning of the stream and the end of the stream then everything in between will come in order and in the good shape. 因此,如果您可靠地捕获流的开头和流的结尾,那么介于两者之间的所有内容都将按顺序排列并且形状良好。

I am not sure though you'd ever want to send a single number and not pre-marshal them into a large buffer. 我不确定你是否曾想要发送一个号码而不是将它们预先编组到一个大缓冲区中。 You will get several orders of magnitude improvement in performance. 您将获得几个数量级的性能提升。

As @usr pointed out, the loops are badly constructed. 正如@usr指出的那样,循环结构很糟糕。 What is needed are "send all" and "receive all" functions. 所需要的是“全部发送”和“全部接收”功能。

These ones are based on the book by Stevens "UNIX Network Programming: Sockets Introduction" 这些是基于史蒂文斯的书“UNIX网络编程:套接字介绍”

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

Send all function and send function from client: 从客户端发送所有功​​能和发送功能:

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

}

Receive all function 接收所有功能

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
  }


}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C#或C / C ++如何从客户端接收数据包,过滤并将其重定向到服务器,从服务器接收并发送至客户端 - C# or C/C++ How to receive packets from client, filter & redirect them to server, receive from server & send to client 如何通过套接字从服务器向客户端发送文本文件的内容? - How to send contents of a text file from server to client via sockets? 套接字:如何在客户端接收/解析数据时将数据发送到客户端而无需“等待” - Sockets: How to send data to the client without 'waiting' on them as they receive/parse it 如何在 QT 中通过 C++ 中的套接字发送和接收? - How to send and receive over sockets in C++ in QT? C++ 将图像客户端发送到服务器(套接字),图像损坏 - C++ Send image client to server (sockets), image corrupt 将float数组从C ++服务器发送到C#客户端 - Send float array from C++ server to C# client 如何在C#上使用客户端从服务器接收数据? - How to receive data from server with client on c#? 使用 sockets 将字符串从一台计算机上的 C++ 客户端发送到另一台计算机上的 Python 服务器。 获取`发送:错误的文件描述符` - Using sockets to send a string from a C++ client on one computer to a Python server on another. Getting `send: Bad file descriptor` 部分发送/接收TCP套接字c ++ - Partial send/receive TCP Sockets c++ C++ 服务器 recv() 命令并不总是从客户端接收 send() - C++ Server recv() command does not always receive send() from client
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM