简体   繁体   English

使用套接字发送和接收文件

[英]Send and receive file with socket

This part of code is used by a client when receiving a file: 客户端在接收文件时使用以下部分代码:

void do_retr_cmd(int f_sockd){
  int fd;
  ssize_t nread = 0;
  uint32_t fsize, fsize_tmp, total_bytes_read, size_to_receive;
  char *filename = NULL, *conferma = NULL, *filebuffer = NULL;
  char buf[256], dirp[256], t_buf[256];

  memset(dirp, 0, sizeof(dirp));
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  printf("Write the name of file to download: ");
  fgets(dirp, BUFFGETS, stdin)
  filename = NULL;
  filename = strtok(dirp, "\n");
  sprintf(buf, "RETR %s", dirp);
  if(send(f_sockd, buf, strlen(buf), 0) < 0){
    perror("Errore durante l'invio del nome del file");
    onexit(f_sockd, 0, 0, 1);
  }
  fsize = 0;
  recv(f_sockd, t_buf, sizeof(t_buf), 0)
  fsize = atoi(t_buf);
  fd = open(filename, O_CREAT | O_WRONLY, 0644);
  fsize_tmp = fsize;
  filebuffer = (char *)malloc(fsize);
  total_bytes_read = 0;
  nread = 0;
  for(size_to_receive = fsize; size_to_receive > 0;){
    nread = read(f_sockd, filebuffer, size_to_receive);
    if(nread < 0){
      perror("read error on retr");
      onexit(f_sockd, 0, 0, 1);
    }
    if(write(fd, filebuffer, nread) != nread){
      perror("write error on retr");
      onexit(f_sockd, 0, 0, 1);
    }
    size_to_receive -= nread;
  }
  close(fd);
  fflush(stdout);
  fflush(stdin);
  memset(buf, 0, sizeof(buf));
  recv(f_sockd, buf, 21, 0)
  printf("%s", buf);
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  memset(dirp, 0, sizeof(dirp));
  free(filebuffer);
}

And this part of code is used by a server when sending a file: 服务器发送文件时使用这部分代码:

void do_server_retr_cmd(f_sockd, m_sockd){
  int fd, rc;
  uint32_t fsize, size_to_send;
  char *filename = NULL, *other = NULL;
  char buf[512], t_buf[256];
  off_t offset;
  struct stat fileStat;

  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  recv(f_sockd, buf, sizeof(buf), 0)
  other = NULL;
  filename = NULL;
  other = strtok(buf, " ");
  filename = strtok(NULL, "\n");

  if(strcmp(other, "RETR") == 0){
    printf("Ricevuta richiesta RETR\n");
  } else /* do something */

  fd = open(filename, O_RDONLY);

  memset(&fileStat, 0, sizeof(fileStat));
  fileStat.st_size = 0;
  fstat(fd, &fileStat)
  fsize = fileStat.st_size;
  snprintf(t_buf, 255, "%" PRIu32, fsize);
  send(f_sockd, t_buf, sizeof(t_buf), 0)
  offset = 0;
  for (size_to_send = fsize; size_to_send > 0; ){
    rc = sendfile(f_sockd, fd, &offset, size_to_send);
    if (rc <= 0){
      perror("sendfile");
      onexit(f_sockd, m_sockd, fd, 3);
    }
    size_to_send -= rc;
  }
  close(fd);
  fflush(stdout);
  fflush(stdin);
  memset(buf, 0, sizeof(buf));
  strcpy(buf, "226 File transfered\n");
  send(f_sockd, buf, strlen(buf), 0)
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
}

--> Errors checking have been omitted <-- ->错误检查已被省略<-
I have a big problem with these 2 pieces of code. 我对这两个代码有很大的疑问。 When i start the main program i have to write: 当我启动主程序时,我必须写:
1. retr and then i press enter 1. retr ,然后按Enter
2. Write the filename to download: i write the filename and then i press enter 2. Write the filename to download: 我写入文件名,然后按Enter
The problem is that sometimes the file is downloaded correctly and sometimes it is not downloaded but a part of it is displayed on stdout (on the terminal). 问题在于,有时文件已正确下载,有时下载,但文件的一部分显示在stdout(在终端上)上。
I don't understand why i got this strange behavior. 我不明白为什么我会有这种奇怪的行为。
PS: i know my code is ugly but ia C-newbie! PS:我知道我的代码很丑,但是我是C新手!

I'm developing on Ubuntu amd64 and using GCC-4.6.3 (C language). 我正在Ubuntu 64位上开发并使用GCC-4.6.3(C语言)。

TCP connections gives you a reliable bi-directional stream of bytes , but the boundaries of your "application messages" are not preserved, meaning one send() can be received in multiple recv() s on the other side, and the other way around, several send() s can be collapsed into one recv() (and you can receive part of the last chunk you sent). TCP连接为您提供了可靠的双向字节流 ,但是未保留“应用程序消息”的边界,这意味着可以在另一侧的多个recv()接收一个send() ,而在另一侧,可以将多个send()折叠为一个recv() (并且您可以接收所发送的最后一个块的一部分)。 The good thing is you do receive bytes you sent, in the order you sent them. 好消息是,您确实按发送顺序接收了发送的字节。

The line recv(f_sockd, buf, sizeof(buf), 0); recv(f_sockd, buf, sizeof(buf), 0); in the server code assumes that you read the file name here, while in fact you can get up to 256 bytes of whatever your client sent. 服务器代码中的假定您在此处读取文件名,而实际上您最多可以获取256字节的客户端发送的内容。

You need to impose some sort of an application-level protocol on top of bare TCP. 您需要在裸TCP之上强加某种应用程序级协议 A very simplistic one would be to send a textual header in front of your file content in the form: 一种非常简单的方法是以以下形式在文件内容之前发送文本标题:

file-size file-name\n

So your server can look up the first newline, split the line on the first space and have the number of bytes to expect, and the file name to save those bytes to. 因此,您的服务器可以查找第一个换行符,在第一个空格处分割行,并具有期望的字节数,以及将这些字节保存到的文件名。 Do not ignore the rest of the receive buffer after that newline, save it to the file. 在换行符之后,不要忽略其余的接收缓冲区,将其保存到文件中。 This also gives you a possibility of re-using that connection for multiple file transfers. 这也使您可以重新使用该连接进行多次文件传输。

Hope this helps. 希望这可以帮助。

recv(f_sockd, buf, 21, 0)
printf("%s", buf);

This printf prints a ton of random junk because there's no actual protocol implemented to control what it receives and what it prints. 由于没有实现控制接收和打印内容的实际协议,因此该printf打印大量随机垃圾。 For example, how does the printf know how many bytes to print? 例如, printf如何知道要打印多少字节?

The previous version of my answer wasn't quite right, but here's why you are seeing the strange behaviour. 我的答案的先前版本不太正确,但是这就是为什么您看到奇怪行为的原因。 You send the file size as 您将文件大小发送为

snprintf(t_buf, 255, "%" PRIu32, fsize);

You then receive with 然后您会收到

recv(f_sockd, t_buf, sizeof(t_buf), 0)

but that's not guaranteed to actually read sizeof(t_buf) bytes. 但这不能保证实际读取sizeof(t_buf)字节。 Then atoi will sometimes return an incorrect size, and the rest of the file is treated as the status message, which is printed at the end (up to the first null char). 然后atoi有时会返回不正确的大小,文件的其余部分将被视为状态消息,并在末尾显示(直到第一个空字符)。

Because recv may not return all of the data you ask for at once, you have to check its return value and potentially repeat the call to recv : 由于recv可能不会一次返回您要求的所有数据,因此您必须检查其返回值并可能重复调用recv

size_t to_recv = sizeof(t_buf);
size_t rcvd = 0;
while (to_recv > 0) {
    ssize_t r = recv(f_sockd, t_buf + rcvd, sizeof(t_buf) - rcvd, 0);
    if (r < 0) {
        //error
    }
    else {
        to_recv -= r;
        rcvd += r;
    }
}

Obviously you either have to know how much data to expect, or come up with a better protocol as suggested in the other answers (for example, look for a terminator to determine when you have read the size). 显然,您要么必须知道要期待多少数据,要么要按照其他答案中的建议提出更好的协议(例如,寻找终止符来确定何时读取大小)。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM