TCP - это потоковый протокол. Он не имеет понятия сообщений, поэтому, когда сервер отправляет "Sending file."
, между строкой и началом отправляемого файла нет разделения. Все просто входит в поток один байт за следующим, и когда сетевой стек решает, что пора, обычно потому, что пакет был заполнен или прошло слишком много времени с момента последнего добавления данных , пакет отправлен, возможно содержащие несколько сообщений.
So
int bytesReceived = recv(sock, buf, 4096, 0);
очень вероятно читает полные 4096 байт, Attempting to take a screenshot.\0Sending file.\0
плюс первые четыре тысячи или около того байтов растрового изображения. Код клиента использует строку и отбрасывает оставшуюся часть буфера.
Вам необходимо установить протокол связи, который находится между сокетом и записью файла. Существует множество способов справиться с этим. Обычные приемы чтения строк:
- Запишите длину строки перед записью строки, чтобы обработчик протокола знал, сколько байтов нужно прочитать раньше времени
Отправитель
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;
}
- Вставьте канареечное значение, чтобы обработчик протокола знал, когда прекратить чтение. Нулевой терминатор хорошо работает здесь. Протокол считывает до тех пор, пока не найдет ноль и сохранит оставшуюся часть прочитанного для последующего использования. Здесь нет примера кода, потому что это можно сделать разными способами.
- Не использовать строки и отправлять сообщения с целочисленным кодом. Например:
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
может дать сбой, и это не проверяется. Всегда проверяйте коды возврата. Люди не помещают их туда, если они не важны.