【问题标题】:C++file transfer with TCP protocol使用 TCP 协议的 C++ 文件传输
【发布时间】:2018-11-12 16:13:48
【问题描述】:

我目前正在编写一个服务器和客户端应用程序,该应用程序尝试传输屏幕截图,但无法正常工作。我是这样实现的。

SOCKET sock;
char buf[4096];

DWORD WINAPI  thread_function()
{
    bool file_transfer = false;
    bool loop = true;
   while (1)
   {
       ZeroMemory(buf, 4096);
       int bytesReceived = recv(sock, buf, 4096, 0);
       if (bytesReceived > 0)
       {
           std::string received(buf, 0, bytesReceived);
           if (received == "Sending file.")
           {
               file_transfer = true;
           }

           if (file_transfer == false)
           {
           std::cout << "\nSERVER> " << std::string(buf, 0, bytesReceived) << std::endl;  
           std::cout << "> ";
           }
           else if (file_transfer == true)
           {
               loop = true;
               TCHAR *szfname = "screenshot.bmp";
               FILE* f = fopen(szfname, "wb");
               if (NULL == f)
               {
                   std::cerr << "Error opening file" << std::endl;
                   return 1;
               }
               while ((bytesReceived = recv(sock, buf, 4096, 0)) > 0 && loop == true)
               {
                   received = buf;
                   if (received == "File transfer completed !")
                   {
                       loop = false;
                       std::cout << "File transfer completed !" << std::endl;
                       std::cout << "> ";
                   }
                   else
                   {
                   fwrite(buf, 1, bytesReceived, f);
                   }
               }
               file_transfer = false;
           }
       }
   }
}

我用这个调用函数

CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread_function, 0, 0, 0);

问题是我相信这不是一种非常干净的方式,而且它也不能完美地工作。收到文件后,我没有正确接收服务器发送的内容。

这是我认为很好的服务器代码。

            send(clientSocket, TEXT("Attempting to take a screenshot."), sizeof(TEXT("Attempting to take a screenshot...")), 0);
            HWND win = GetDesktopWindow();
            HDC dc = GetDC(win);
            if (HDCToFile("screenshot.bmp", dc, { 0, 0, 1920, 1080 }) == true)
            {
                send(clientSocket, TEXT("Sending file."), sizeof(TEXT("Sending file.")), 0);
                FILE *fp = fopen("screenshot.bmp", "rb");
                if (fp == NULL)
                {
                    std::cerr << "Error : Cannot open file." << std::endl;
                    return 1;
                }
                while (1)
                {
                   char buff[4096] = { 0 };
                    int nread = fread(buff, 1, 4096, fp);
                    if (nread > 0)
                    {
                        send(clientSocket, buff, sizeof(buff), 0);
                    }
                    if (nread < 4096)
                    {
                        if (feof(fp))
                        {
                            std::cout << "File transfer completed !" << std::endl;
                            send(clientSocket, TEXT("File transfer completed !"), sizeof(TEXT("File transfer completed !")), 0);
                        }
                        if (ferror(fp))
                            std::cerr << "Error reading." << std::endl;
                        break;
                    }
                }
            }
            else
            {
                send(clientSocket, TEXT("Screen capture failed...."), sizeof(TEXT("Screen capture failed....")), 0);
            }

感谢您的时间和帮助。

【问题讨论】:

  • 1.线程会添加自己的问题,所以从删除它开始。 2.阅读this 3.一旦你的东西在没有线程的情况下工作,你可以考虑添加它。
  • 您的接收方检查if (received == "Sending file.") 是错误的。假设 TCP,字符串 received 可以是一个完整的 4Kb 缓冲区,因为在调用 recv 时不会保留 send 调用之间的边界。如果有一个,则字符串比较不会在 nul 终止符处停止。您应该考虑将传输层(从 TCP 流中接收的大量数据)与解析层(从有效负载中区分状态或控制消息)分开。
  • 这确实是TCP协议。 “您应该考虑将传输层(从 TCP 流中接收的大量数据)与解析层(从有效负载中区分状态或控制消息)分开。”我明白你的意思,但老实说,我不知道如何实施。对于链接我“如何调试小程序”的人,如果我知道为什么这不起作用,我不会问。自从我刚开始使用套接字以来,我对套接字了解不多。我的问题不是很具体,因为我根本不知道问题出在哪里。

标签: c++ sockets client-server file-transfer


【解决方案1】:

TCP 是一种流协议。它没有消息的概念,因此当服务器发送"Sending file." 时,字符串和正在发送的文件的开头之间没有分隔。一切都只是一个字节后一个字节进入流,当网络堆栈决定是时候了,通常是因为一个数据包已被填充或it's been too long since data was last added,一个数据包被发送,可能包含多条消息。

所以

int bytesReceived = recv(sock, buf, 4096, 0);

很可能读取完整的 4096 个字节,Attempting to take a screenshot.\0Sending file.\0 加上位图的前四千字节左右。客户端代码使用字符串并丢弃缓冲区的其余部分。

您需要建立一个位于套接字和文件写入之间的通信协议。有很多不同的方法来处理这个问题。读取字符串的常见技巧是

  1. 在写入字符串之前写入字符串的长度,以便协议处理程序提前知道要读取多少字节

发件人

uint16_t len = str.length(); // size is exactly 16 bits
len = htons(len); // endian is known
int sent = send(sock, (char*)&len, sizeof(len), 0);
// test sent for success (did not fail, sent all the bytes)
sent = send(sock, str.c_str(), len, 0);
// test sent for success (did not fail, sent all the bytes) 
// may need to loop here if the string is super long.

接收者

uint16_t len;
int recd = recv(sock, (char*)&len, sizeof(len), MSG_WAITALL);
// test recd for success (did not fail, read all the bytes)
// MSG_WAITALL will read exactly the right number of bytes or die trying. 
len = ntohs(len); // ensure correct endian
std::string msg(len, ' '); // allocate a big enough string
char * msgp = &msg[0]; // or msg.data() if C++17 or better. 
                       // Never seen &msg[0] fail, but this is not guaranteed by C++
while (len) // sometimes you want an extra exit condition here to bail out early
{
    recd = recv(sock, msgp, len, 0);
    // test recd for success 
    len -= recd;
    msgp += recd;
 } 
  1. 插入一个金丝雀值,以便协议处理程序知道何时停止读取。空终止符在这里工作得很好。该协议会向上读取,直到找到 null 并保留读取的其余内容以供以后使用。这里没有代码示例,因为这可以通过多种不同的方式完成。
  2. 不使用字符串而是发送整数代码消息。例如:


enum messageID
{
    TAKING_SCREENSHOT,
    SENDING_FILE,
    EATING_COOOOOOKIE_OM_NOM_NOM
};

好的!正确移动琴弦。假设我那里没有错误。这个想法是对的,但实际代码来自记忆,可能包含脑残。

你想要的是一堆函数,一个用于你发送的每种类型的数据。这些功能中的每一个都可以并且应该单独测试,以便当您将它们集成到程序中时,程序看起来像

sendString(sock, "Attempting to take a screenshot.");
if (getBitmap("screenshot.bmp"))
{
    sendString(sock, "Sending file.");
    sendBitmap(sock, "screenshot.bmp");
}

receiveString(sock);
std::string command = receiveString(sock);
if (command == "Sending file.")
{
    receiveBitmap(sock, "screenshot.bmp");
}
else if (command == "Eating coooooookie! Om! Nom! Nom!")
{
    OmNomNom(sock);
}

这几乎是万无一失的。

注意事项:

服务器中存在一个错误:int nread = fread(buff, 1, 4096, fp); 获取读取的字节数,但send(clientSocket, buff, sizeof(buff), 0); 总是尝试发送一个完整的缓冲区,而不管读取了多少字节,所以垃圾会被发送到客户端。 send 也可能失败,并且没有被检查。始终检查返回代码。人们不会把它们放在那里,除非它们很重要。

【讨论】:

  • 感谢您的详细回答,这对我很有帮助。我将尝试实现所有这些,稍后我会提供反馈。编辑:我只记得一个细节,接收数据需要多线程还是没用?
  • 取决于你在做什么。如果您在等待数据到达时还有其他需要处理的事情,那么您可以使用线程、轮询或重叠 IO。如果您只有一个连接并且可以坐下来等待消息,那么线程是无用的。
猜你喜欢
  • 2021-02-08
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-05
  • 1970-01-01
  • 2020-03-04
相关资源
最近更新 更多