TCP 套接字:recv() 没有收到我的完整数据

Posted

技术标签:

【中文标题】TCP 套接字:recv() 没有收到我的完整数据【英文标题】:TCP Sockets: recv() not receiving my full data 【发布时间】:2020-12-29 22:01:45 【问题描述】:

我有一个关于客户端/服务器 TCP 应用程序的问题。

我需要它来做以下事情:

传输的文件应以不超过 1460 字节的包的形式发送。 客户端应询问要传输的文件名,然后连接到服务器,发送一个包含大小、要发送的包数量和文件名的结构。等待服务器发回与握手相同的结构,以检查是否一切正常。如果收到的相同,它将开始发送所有包裹。之后,再次发送 struct 通知所有内容都已发送并等待服务器响应。 服务器应该等待连接,然后创建一个子节点来管理它。它应该发回接收到的包含文件信息的结构,开始将接收到的包保存在与指示的名称相同的文件中,并在接收到包含信息结构的包后停止。然后检查收到的包裹数量,文件大小是否与结构上指示的相同。

我正在努力解决的主要问题是,在服务器中,recv() 没有获取某些包中发送的所有数据。例如,我尝试使用大小为 14.3kB 的文本文件(它可以分 10 个包发送,其中 9 个是 1460 字节,最后一个小于 1460 字节),但收到的第一个包是 528 字节,然后2 of 1460,那么它将完全停止接收。

我不知道该怎么做的另一件事是,当服务器接收到文件信息结构时,如何比较它现在转换为char数组。

我的代码如下:

客户

#include <string.h>
#include <stdbool.h>
#include <sys/types.h>    
#include <sys/socket.h>  
#include <netinet/in.h> 
#include <arpa/inet.h>
#include <unistd.h>
#include <math.h>

#define  PORT_NUM           33333  // Port number used
#define  IP_ADDR "127.0.0.1" // IP address of server1 (*** HARDWIRED ***)
#define SOCKET_PROTOCOL 0

struct infoarchivo
   unsigned long tamanio;
   unsigned long paquetes;
   char nombrearchivo[512]; info_tx,info_rx;

bool comparar(struct infoarchivo rx, struct infoarchivo tx);

int main(int argc, char *argv[])


 unsigned int         client_s;        // Descriptor del socket
 struct sockaddr_in   server_addr;     // Estructura con los datos del servidor
 struct sockaddr_in   client_addr;     // Estructura con los datos del cliente
 int                  addr_len;        // Tamaño de las estructuras
 char                 buf_tx[1460];    // Buffer de 1460 bytes para los datos a transmitir
 char                 ipserver[16];
 int                  bytesrecibidos,bytesaenviar, bytestx;               // Contadores
 int                  conectado; //variable auxiliar
 char                 nombre_archivo[512];
 long tamano_archivo,paquetes_archivo;
 char respuesta[3];
 bool flag,flag2;
 FILE * fp;
 int i;

 if (argc!=2)
   
   printf("uso: clienteUDP www.xxx.yyy.zzz\n");
   return -1; 
   
   strncpy(ipserver,argv[1],16);
 
 client_s = socket(AF_INET, SOCK_STREAM, SOCKET_PROTOCOL);
 if (client_s==-1)
   
   perror("socket");
   return 2;
   
 printf("Cree el descriptor del socket %d\n",client_s);
 
 server_addr.sin_family      = AF_INET;            // Familia TCP/IP
 server_addr.sin_port        = htons(PORT_NUM);    // Número de Port, htons() lo convierte al orden de la red
 
 if  (inet_aton(ipserver, &server_addr.sin_addr)==0) // cargo la estructura con la dirección IP del servidor
   
   printf ("La dirección  IP_ADDR no es una dirección IP válida. Programa terminado\n"); 
   return 3;
   
 addr_len = sizeof(server_addr);
  
 conectado=connect(client_s, (struct sockaddr *)&server_addr, sizeof(server_addr));
 if (conectado==-1)
   
   perror("connect");
   return 4;
   
    printf("El IP del servidor es: %s y el port del servidor  es %hu \n",inet_ntoa(server_addr.sin_addr),
           ntohs(server_addr.sin_port));
 do  
   printf("Ingrese ruta completa del archivo a transmitir:\n");
   scanf("%s", nombre_archivo); /*Consigo nombre del archivo a transmitir*/
   fp = fopen(nombre_archivo,"r");
   if(fp == NULL)
       perror("Error");
   return 0;
   
   fseek(fp, 0, SEEK_END);
   tamano_archivo = ftell(fp); /*tamano del archivo a transmitir*/
   rewind(fp);
   paquetes_archivo=ceil(tamano_archivo/1460)+1; /*cantidad de paquetes a enviar*/
   printf("%lu, %lu\n", tamano_archivo,paquetes_archivo);
   /*Envio informacion del archivo a transmitir*/
   info_tx.tamanio=tamano_archivo;
   info_tx.paquetes=paquetes_archivo;
   strcpy(info_tx.nombrearchivo,nombre_archivo);
   bytesaenviar=sizeof(info_tx);
   bytestx=send(client_s, &info_tx, bytesaenviar,0); 
   /*Espero confirmacion de lo recibido*/
   bytesrecibidos=recv(client_s, &info_rx, sizeof(info_rx), 0);
   flag = comparar(info_rx,info_tx); /*comparo las dos estructuras*/
   if(flag==true) /*si son iguales*/
   if(paquetes_archivo>1) /*envio archivo*/
       for(i=1;i<=paquetes_archivo-1;i++) /*si tengo que enviar mas de 1 paquete envio n-1 de 1459 bytes*/
           fread(buf_tx,1460,1,fp);
           bytestx=send(client_s, buf_tx, bytesaenviar,0);
           printf("Envie paquete %d, que dice: %s\n",i,buf_tx);
           bzero(buf_tx,sizeof(buf_tx));
       
   
   fread(buf_tx,tamano_archivo-1460*(paquetes_archivo-1),1,fp); /*y un ultimo paquete de menos de 1459 bytes*/
   bytestx=send(client_s, buf_tx, bytesaenviar,0);
   printf("Envie el ultimo paquete, que dice: %s\n",buf_tx);
   /*VOLVER A ENVIAR, RECIBIR Y COMPARAR ESTRUCTURAS*/
       bytesaenviar=sizeof(info_tx);
       bytestx=send(client_s, &info_tx, bytesaenviar,0); 
   printf("Envie info del archivo\n");
       /*Espero confirmacion de lo recibido*/
       bytesrecibidos=recv(client_s, &info_rx, sizeof(info_rx), 0);
   printf("Recibí info del archivo\n");
       flag2 = comparar(info_rx,info_tx);
   printf("Compare info del archivo\n");
   if(flag2==true)
       printf("Envio finalizado con exito.\n");    
   
   else
       printf("%s",info_rx.nombrearchivo);
   
   
   else
   printf("%s",info_rx.nombrearchivo);
   
   fclose(fp);
   printf("¿Desea enviar otro archivo? (Si/No)\n");
   scanf("%s",respuesta);

  while (strncmp(respuesta,"Si",2)==0); 
 
close(client_s);
printf("¡Hasta luego!\n");
return 0;
 // fin del programa    

bool comparar(struct infoarchivo rx,struct infoarchivo tx)
   if(rx.tamanio==tx.tamanio && rx.paquetes == tx.paquetes && strcmp(rx.nombrearchivo,tx.nombrearchivo)==0)
       return true;
   
   else
       return false;
   
 

服务器:

#include <string.h>
#include <signal.h> 
#include <sys/wait.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <stdbool.h>


#include <sys/types.h>    
#include <sys/socket.h>  
#include <netinet/in.h> 
#include <arpa/inet.h>
#include <unistd.h>

#define  PORT_NUM           33333  // Port number used
#define  IP_ADDR "127.0.0.1" // IP address of server1 (*** HARDWIRED ***)
#define NCOLA 2
#define SOCKET_PROTOCOL 0

int terminar=0;
void handler(int sig);

struct infoarchivo
    unsigned long tamanio;
    unsigned long paquetes;
    char nombrearchivo[512]; info_tx,info_rx;

bool comparar(char rx[],char buffer[], struct infoarchivo tx);
//===== Main program ========================================================
int main(int argc, char *argv[])


  unsigned int         server_s;        // Descriptor del socket
  unsigned int         connect_s;       // Connection socket descriptor
  struct sockaddr_in   server_addr;     // Estructura con los datos del servidor
  struct sockaddr_in   client_addr;     // Estructura con los datos del cliente
  struct in_addr       client_ip_addr;  // Client IP address
  int                  addr_len;        // Tamaño de las estructuras
  char                 buf_tx[1460];    // Buffer de 1460 bytes para los datos a transmitir
  char                 buf_rx[1460];    // Buffer de 1460 bytes para los datos a recibir
  int                  bytesrecibidos, bytesaenviar, bytestx;  // Contadores
  int                  i=0;             //contador de mensajes
  int              salida;
  unsigned long        contador=0,bytestotales=0;
  char *cmd[] = (char *)0;
  FILE * fp;
  pid_t pid_n;

  signal(SIGHUP,handler);

  // Crear el socket
  server_s = socket(AF_INET, SOCK_STREAM, SOCKET_PROTOCOL);
  if (server_s==-1)
    
    perror("socket");
    return 1;
    
  printf("Cree el descriptor del socket %d\n",server_s);

  server_addr.sin_family      = AF_INET;            // Familia TCP/IP
  server_addr.sin_port        = htons(PORT_NUM);    // Número de Port, htons() lo convierte al orden de la red
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // INADDR_ANY = cualquier direccion IP, htonl() lo convierte al orden de la red

  bind(server_s, (struct sockaddr *)&server_addr, sizeof(server_addr));
  
  printf("asocie el descriptor %u con el port %u acepta conexiones desde %u\n", server_s,PORT_NUM, INADDR_ANY) ;

  listen(server_s, NCOLA);
  addr_len = sizeof(client_addr);
  while(1)   
    connect_s = accept(server_s, (struct sockaddr *)&client_addr, &addr_len);
    /* >>> paso #6 <<<
      Si llegué aca hubo un pedido de conexión
      Verifico que no haya retornado un error (-1)*/
    if (connect_s==-1)
      
      perror("accept");
      return 2;
      
    /* Acá voy a derivar la atención de la conexión a un proceso Hijo. En el hijo voy a cerrar el socket original 
    (server_s) porque no lo utiliza más, solo conserva abierto el socket connect_s  */
    fp = fopen("servidor.log","a+");
    time_t rawtime;
    struct tm *info;
    time(&rawtime);
    info = localtime( &rawtime );
    fprintf(fp, "%s %hu %s",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),asctime(info));
    fclose(fp);

    if((pid_n=fork())==0)
    close (server_s); 
    printf("El hijo %d dice que el IP del cliente es: %s y el port del cliente es %hu \n",getpid(), inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    bytesrecibidos=recv(connect_s, &info_rx, sizeof(info_rx), 0); //Espero que me llegue la información a recibir.
    printf("Recibi la info del archivo\n");
    if(bytesrecibidos==-1)
        strcpy(info_tx.nombrearchivo,"Error al recibir archivo\n");
        info_tx.paquetes=0;
        info_tx.tamanio=0;
        bytesaenviar=sizeof(info_tx);
        bytestx=send(connect_s, &info_tx, bytesaenviar,0); 
        printf("Error recibiendo informacion de archivo\n");
        close(connect_s);
        return 0; 
    
    else
        fp=fopen(info_rx.nombrearchivo,"w");
        if(fp!=NULL) //Abri el archivo con exito
            //Vuelvo a enviar informacion
            strcpy(info_tx.nombrearchivo,info_rx.nombrearchivo);
            info_tx.paquetes=info_rx.paquetes;
            info_tx.tamanio=info_rx.tamanio;
            bytesaenviar=sizeof(info_tx);
            bytestx=send(connect_s, &info_tx, bytesaenviar,0);
            printf("Envie info del archivo\n");
        
        else //Eror al abrir el archivo
            strcpy(info_tx.nombrearchivo,"Error al abrir archivo\n");
            info_tx.paquetes=0;
            info_tx.tamanio=0;
            bytesaenviar=sizeof(info_tx);
            bytestx=send(connect_s, &info_tx, bytesaenviar,0); 
            perror("Error: ");
            close(connect_s);
            return 1; 
        

        sleep(1);
        bzero(buf_rx,sizeof(buf_rx));
        //Espero a recibir los paquetes
        do
            
            bytesrecibidos=recv(connect_s, buf_rx, sizeof(buf_rx),MSG_WAITALL);
            if(bytesrecibidos==-1)perror("Di -1 porque: ");
            printf("Recibi el paquete %ld, de tamaño %d, que dice: %s\n",contador+1,bytesrecibidos,buf_rx);
            fwrite(buf_rx,bytesrecibidos,1,fp);
            contador++;
            bytestotales=bytestotales+bytesrecibidos;
            bzero(buf_rx,sizeof(buf_rx));
        while(contador<info_rx.paquetes);
        printf("Sali del while\n");
        if(bytestotales==info_rx.tamanio&&contador==info_rx.paquetes)
            strcpy(info_tx.nombrearchivo,info_rx.nombrearchivo);
            info_tx.paquetes=info_rx.paquetes;
            info_tx.tamanio=info_rx.tamanio;
            bytesaenviar=sizeof(info_tx);
            bytestx=send(connect_s, &info_tx, bytesaenviar,0);
            printf("Envie la info del archivo\n");
        
        else
            fclose(fp);
            remove(info_rx.nombrearchivo);
            strcpy(info_tx.nombrearchivo,"No coincide cantidad de paquetes recibidos o bytes recibidos\n");
            printf("No coincide cantidad de paquetes recibidos o bytes recibidos\n");
            printf("%lu, %lu, %lu, %lu",info_rx.tamanio,info_rx.paquetes,contador,bytestotales);
            info_tx.paquetes=contador;
            info_tx.tamanio=bytestotales;
            bytesaenviar=sizeof(info_tx);
            bytestx=send(connect_s, &info_tx, bytesaenviar,0);
        
        printf("Termine de mandar todo\n");

    

    close(connect_s);
    return 0; 
    
    else  // el else es el proceso PADRE
      printf("Soy el padre %d, recibi un pedido de conexión, la derive a mi hijo %d\n", getpid(),pid_n); 
      close(connect_s);
     
    //PADRE vuelve al accept a esperar otra conexión
  wait(NULL);
  close(server_s);
  return 0;
 

void handler(int sig)

    if (sig==SIGHUP)
    
    terminar=1;
    printf("señal HUP recibida, cuando establezca la proxima conexión el servidor terminará\n");
    
 ``` 

【问题讨论】:

TCP 是一种流协议——它不能以任何方式保证您将收到与发送的相同大小的块。您的代码需要处理它。这就是为什么建立在 TCP 上的 protocls,例如 HTTP,用 \n 分隔部分消息并发送一个长度然后是可变长度数据。 所以我应该检查接收缓冲区的大小,直到获得所需的全部 1460 字节?如果是这样,如果使用 sizeof(buffer) 将返回 1460,因为它是预定义的数组,我该怎么做? 是的,你应该这样做。 您还必须考虑 send() 可能不会在一次调用中写入所有请求的字节。 您也可以使用MSG_WAITALL 标志。 【参考方案1】:

先说TCP socket。

当谈到 TCP 套接字时,它是一个数据流。 TCP 将数据视为非结构化、但有序的字节流。它与socket.io的种类不同。

有时,TCP 会从发送缓冲区中抓取数据块并将数据传递给网络层。可以抓取和放置在段中的最大数据量受最大段大小 (MSS) 的限制。 MSS 通常通过首先确定最大链路层帧的长度来设置。

所以这取决于设备。

例如,你有两条消息,每条消息都有 1000 字节的数据,你调用:

-------------客户端----

client.send(theFirstMessage) // 1000 bytes
client.send(theSecondMessage) // 1000 bytes

------------- 服务器端 -----

socket.onReceived(data => 
    // process(data)
)

使用上面的伪代码你应该注意:

onReceived 块上接收和调用的数据可能不是 TheFirstMessage 的 1000 字节。

    可能是前 400 个字节,然后在其他事件中您会收到 400 个字节,然后是更多的 400 个字节(第一个字节的 200 个字节和第二个字节的 200 个字节)。 可能是 1200 字节(第一个字节为 1000,第二个字节为 200)。

TCP 将数据视为非结构化的有序的字节流。 Socket.io 是一个包装器,当它使用 TCP 套接字时,它会为您收集和组合/拆分数据,以便您接收到与从另一端发送的数据完全相同的事件。 当你使用 TCP 时,你必须自己做,你必须定义应用程序协议来做。

发送/接收 TCP 请求有两种常用方法:

    分离器,你选择分离器。例如,我们选择 32 位 AABBCCDD 作为拆分器(与您选择 END_DATA 字符串相同),但请记住它是二进制数据。然后你必须确保请求中的数据不包含splitter。为此,您必须对请求进行编码。例如我们可以将请求编码为base64,然后使用base64表中不包含的字符作为分隔符。

    前缀长度,上面的方法有它的开销,因为我们必须对请求数据进行编码。前缀长度方法是更好的选择。 我们可以在前面加上请求的长度。

伪代码:

// use Int32, 4 bytes to indicate the length of message after it

-------------- client side ----------------
    client.send(theFirstMessage.length)    // Int32
    client.send(theFirstMessage) // 1000 bytes

    client.send(theSecondMessage.length) 
    client.send(theSecondMessage) // 1000 bytes


-------------- server side -----------------

    var buffer = Buffer()

    socket.onReceived(data => 
        buffer.append(data)
        
        let length = Int32(buffer[0...3])
        
        if (buffer.length >= length + 4) 
           let theRequest = buffer[4 ... 4 + length - 1]
           process(theRequest)

           buffer = buffer.dropFirst(4 + length)
        
       
    )

还有一点,在使用 TCP 套接字时,它只是字节流,所以字节序很重要https://en.wikipedia.org/wiki/Endianness

例如,一个安卓设备是小端,而服务器端(或其他安卓设备)是大端。然后是来自android设备的4个字节的Int32,在服务器端接收时,如果你不关心它会被错误解码。

所以,前缀长度应该按照特定的字节序进行编码。

【讨论】:

'在onReceived块上接收和调用的数据不能是theFirstMessage的1000字节':是的。您的其他两种可能性也可能发生,其他许多可能性也可能发生。 它看起来像一个英语语法问题。我建议你把“couldn't”改成“might not”

以上是关于TCP 套接字:recv() 没有收到我的完整数据的主要内容,如果未能解决你的问题,请参考以下文章

如果达到超时,gen_tcp:recv/3 是不是会关闭套接字?

为啥我不能激发 TCP 将 send() 拆分为多个 recv()

Python网络编程—(TCPUDP区别)

Linux 套接字:如何让 send() 等待 recv()

通过 TCP 填充数据

在erlang中中断gen_tcp:recv