C ++ передача файлов по протоколу TCP - PullRequest
0 голосов
/ 12 ноября 2018

В настоящее время я пишу серверное и клиентское приложение, которое пытается передать снимок экрана, но оно не работает должным образом.Я реализовал это так.

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 Ответ

0 голосов
/ 12 ноября 2018

TCP - это потоковый протокол. Он не имеет понятия сообщений, поэтому, когда сервер отправляет "Sending file.", между строкой и началом отправляемого файла нет разделения. Все просто входит в поток один байт за следующим, и когда сетевой стек решает, что пора, обычно потому, что пакет был заполнен или прошло слишком много времени с момента последнего добавления данных , пакет отправлен, возможно содержащие несколько сообщений.

So

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. Вставьте канареечное значение, чтобы обработчик протокола знал, когда прекратить чтение. Нулевой терминатор хорошо работает здесь. Протокол считывает до тех пор, пока не найдет ноль и сохранит оставшуюся часть прочитанного для последующего использования. Здесь нет примера кода, потому что это можно сделать разными способами.
  2. Не использовать строки и отправлять сообщения с целочисленным кодом. Например:


enum messageID
{
    TAKING_SCREENSHOT,
    SENDING_FILE,
    EATING_COOOOOOKIE_OM_NOM_NOM
};

OK! Это правильно перемещает струны. Предполагая, что у меня нет ошибки там. Идея верна, но реальный код взят из памяти и может содержать мозговые следы.

То, что вы хотите иметь, - это набор функций, по одной для каждого типа данных, которые вы отправляете. Каждую из этих функций можно и нужно тестировать отдельно, чтобы при интеграции их в программу программа выглядела примерно как

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 может дать сбой, и это не проверяется. Всегда проверяйте коды возврата. Люди не помещают их туда, если они не важны.

...