在网络上接收文件时会丢失一些数据包。

huangapple go评论78阅读模式
英文:

Receiving a file over a network loses some packets when receiving

问题

I have made a server and client program in c++. I used winsock2.h for this project. I made the program be able to send and receive files over the network. The server program and client program are two separate projects because making the program in the same project is not possible to check them.

When i run both client and server in a single device and try sending a text from the client to the server and vice versa, it succeeds, even sending files from server to client and vice versa also works.

When i run server in a device and client on another, sending text succeeds (probably because its just a single packet). Sending files however tends to have some faults. i have the packet size set to 1024 bytes, and whenever sending a single packet, it works. but when i try sending more than couple packets, it fails to receive couple packets at the last. i tried with files of multiple sizes and it failed multiple times at the last couple packets. i thought the problem was with my stack-based code and so i tried changing it to heap, and it still didnt work . it's been a while since i couldnt figure out the problem. I tried checking out places that might have the similar problems, but i wasnt able to find a solution to it.

These are my codes:

Receive function in Server program

bool Server::ReceiveFile()
{
	if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }


	if (!is_ConnectedWithAClient)return false;

	//Receive File name to use 
	if (!ReceiveText()) {
		PRINT_ERR("Failed to receive file name!!");
		return false;
	}
	std::cout << "Received file name: " << text << std::endl;
	std::string filename = text;

	std::string filepath = File_Path + filename;

	// Receive the file size
	if (!ReceiveText())
	{
		PRINT_ERR("Failed to receive file size!!");
	}
	PRINT_LOG("Received File size: " + text + " bytes.");

	int fileSize = std::stoi(text);

	if (!CreateFilepath(filepath))return false;

	std::ofstream file(filepath, std::ios::binary);
	if (!file) {
		PRINT_ERR("Failed to create file: " + filename);
		return false;
	}
	PRINT_LOG("File created successfully at path: " + filepath);

	//char buffer[BUFFER_SIZE];
	char* buffer = new char[BUFFER_SIZE];
	int totalBytesReceived = 0;
	int emptycounter = 0;
	while (totalBytesReceived < fileSize)
	{
		bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
		if (bytesRecv == SOCKET_ERROR) {
			PRINT_ERR("Failed to receive file data");
			return false;
		}

		totalBytesReceived += bytesRecv;

		if (bytesRecv < 1)	emptycounter++;
		else file.write(buffer, bytesRecv);

		if (emptycounter > 5)
		{
			if (totalBytesReceived < 1)PRINT_LOG("The file is empty!!");
			break;
		}

		PRINT_LOG("Bytes received for file: " + filename + " : " + std::to_string(bytesRecv));
		// Send acknowledgement message
		if (!SendText("ACK")) {
			PRINT_ERR("Failed to send acknowledgement message");
			delete[]buffer;
			return false;
		}

		// Wait for acknowledgement response from client
		if (!ReceiveText()) {
			PRINT_ERR("Failed to receive acknowledgement response");
			delete[]buffer;
			return false;
		}

		// Check if acknowledgement response is valid
		if (text != "ACK") {
			PRINT_ERR("Invalid acknowledgement response");
			delete[]buffer;
			return false;
		}
		Sleep(Sleep_timer);
		PRINT_LOG("Total Bytes received : " + filename + " : " + std::to_string(totalBytesReceived));
	}
	file.close();
	return true;
}

Send function in client program

bool Client::SendFile(std::string filepath)
{
	if (!is_ConnectedWiththeServer)return false;

	// Open the file
	std::ifstream file(filepath, std::ios::binary);
	if (!file) {
		PRINT_ERR("Failed to open file: " + filepath);
		return false;
	}

	// Extract the file name from the file path
	size_t fileNameStart = filepath.find_last_of("\\/") + 1;
	std::string fileName = filepath.substr(fileNameStart);

	// Get file size
	file.seekg(0, std::ios::end);
	int fileSize = static_cast<int>(file.tellg());
	file.seekg(0, std::ios::beg);

	// Send file name 
	PRINT_LOG("Sending file: " + fileName);
	if (!SendText(fileName))
	{
		file.close();
		return false;
	}
	PRINT_LOG("Sent file name: " + fileName);

	// Send file size 
	PRINT_LOG("Sending file size: " + std::to_string(fileSize) + " bytes");

	std::string f_size = std::to_string(fileSize);
	if (!SendText(f_size))
	{
		file.close();
		return false;
	}
	PRINT_LOG("Sent file size: " + std::to_string(fileSize) + " bytes");


	// Send file contents
	//char buffer[BUFFER_SIZE];
	char* buffer = new char[BUFFER_SIZE];
	int totalBytesSent = 0;


	while (file)
	{
		file.read(buffer, BUFFER_SIZE - 1);
		int bytesRead = static_cast<int>(file.gcount());
		bytesSent = send(clientSocket, buffer, bytesRead, 0);
		if (bytesSent == SOCKET_ERROR) {
			PRINT_ERR("Failed to send text");
			file.close();
			return false;
		}
		totalBytesSent += bytesSent;
		PRINT_LOG("Sent " + std::to_string(bytesSent) + " bytes from file");

		// Wait for acknowledgement message from server
		if (!ReceiveText()) {
			PRINT_ERR("Failed to receive acknowledgement message");
			delete[]buffer;
			return false;
		}

		// Send acknowledgement response to server
		if (!SendText("ACK")) {
			PRINT_ERR("Failed to send acknowledgement response");
			delete[]buffer;
			return false;
		}
		Sleep(Sleep_timer);
	}

	file.close();
	PRINT_LOG("Sent file data: " + std::to_string(totalBytesSent) + " bytes");

	return true;
}

EDIT These are the send and receive text, if thats necessary:

bool Server::ReceiveText()
{
	if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }


	text = "";
	if (!CheckSocInit()) return false;
	char buffer[BUFFER_SIZE];
	bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
	if (bytesRecv < 1) {
		PRINT_ERR("Failed to receive text");
		return false;
	}
	buffer[bytesRecv] = '\0';
	text += buffer;

	return true;
}
bool Server::SendText(std::string text)
{
	if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }


	if (!is_Connected

<details>
<summary>英文:</summary>

I have made a server and client program in c++. I used winsock2.h for this project. I made the program be able to send and receive files over the network. The server program and client program are two separate projects because making the program in the same project is not possible to check them. 

When i run both client and server in a single device and try sending a text from the client to the server and vice versa, it succeeds, even sending files from server to client and vice versa also works.

When i run server in a device and client on another, sending text succeeds (probably because its just a single packet). Sending files however tends to have some faults. i have the packet size set to 1024 bytes, and whenever sending a single packet, it works. but when i try sending more than couple packets, it fails to receive couple packets at the last. i tried with files of multiple sizes and it failed multiple times at the last couple packets. i thought the problem was with my stack-based code and so i tried changing it to heap, and it .. still didnt work . it&#39;s been a while since i couldnt figure out the problem. I tried checking out places that might have the similar problems, but i wasnt able to find a solution to it.

These are my codes :

**Receive function in Server program**

bool Server::ReceiveFile()
{
if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

if (!is_ConnectedWithAClient)return false;
//Receive File name to use 
if (!ReceiveText()) {
PRINT_ERR(&quot;Failed to receive file name!!&quot;);
return false;
}
std::cout &lt;&lt; &quot;Received file name: &quot; &lt;&lt; text &lt;&lt; std::endl;
std::string filename = text;
std::string filepath = File_Path + filename;
// Receive the file size
if (!ReceiveText())
{
PRINT_ERR(&quot;Failed to receive file size!!&quot;);
}
PRINT_LOG(&quot;Received File size: &quot; + text + &quot; bytes.&quot;);
int fileSize = std::stoi(text);
if (!CreateFilepath(filepath))return false;
std::ofstream file(filepath, std::ios::binary);
if (!file) {
PRINT_ERR(&quot;Failed to create file: &quot; + filename);
return false;
}
PRINT_LOG(&quot;File created successfully at path: &quot; + filepath);
//char buffer[BUFFER_SIZE];
char* buffer = new char[BUFFER_SIZE];
int totalBytesReceived = 0;
int emptycounter = 0;
while (totalBytesReceived &lt; fileSize)
{
bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
if (bytesRecv == SOCKET_ERROR) {
PRINT_ERR(&quot;Failed to receive file data&quot;);
return false;
}
totalBytesReceived += bytesRecv;
if (bytesRecv &lt; 1)	emptycounter++;
else file.write(buffer, bytesRecv);
if (emptycounter &gt; 5)
{
if (totalBytesReceived &lt; 1)PRINT_LOG(&quot;The file is empty!!&quot;);
break;
}
PRINT_LOG(&quot;Bytes received for file: &quot; + filename + &quot; : &quot; + std::to_string(bytesRecv));
// Send acknowledgement message
if (!SendText(&quot;ACK&quot;)) {
PRINT_ERR(&quot;Failed to send acknowledgement message&quot;);
delete[]buffer;
return false;
}
// Wait for acknowledgement response from client
if (!ReceiveText()) {
PRINT_ERR(&quot;Failed to receive acknowledgement response&quot;);
delete[]buffer;
return false;
}
// Check if acknowledgement response is valid
if (text != &quot;ACK&quot;) {
PRINT_ERR(&quot;Invalid acknowledgement response&quot;);
delete[]buffer;
return false;
}
Sleep(Sleep_timer);
PRINT_LOG(&quot;Total Bytes received : &quot; + filename + &quot; : &quot; + std::to_string(totalBytesReceived));
}
file.close();
return true;

}


**Send function in client program**

bool Client::SendFile(std::string filepath)
{
if (!is_ConnectedWiththeServer)return false;

// Open the file
std::ifstream file(filepath, std::ios::binary);
if (!file) {
PRINT_ERR(&quot;Failed to open file: &quot; + filepath);
return false;
}
// Extract the file name from the file path
size_t fileNameStart = filepath.find_last_of(&quot;\\/&quot;) + 1;
std::string fileName = filepath.substr(fileNameStart);
// Get file size
file.seekg(0, std::ios::end);
int fileSize = static_cast&lt;int&gt;(file.tellg());
file.seekg(0, std::ios::beg);
// Send file name 
PRINT_LOG(&quot;Sending file: &quot; + fileName);
if (!SendText(fileName))
{
file.close();
return false;
}
PRINT_LOG(&quot;Sent file name: &quot; + fileName);
// Send file size 
PRINT_LOG(&quot;Sending file size: &quot; + std::to_string(fileSize) + &quot; bytes&quot;);
std::string f_size = std::to_string(fileSize);
if (!SendText(f_size))
{
file.close();
return false;
}
PRINT_LOG(&quot;Sent file size: &quot; + std::to_string(fileSize) + &quot; bytes&quot;);
// Send file contents
//char buffer[BUFFER_SIZE];
char* buffer=new char[BUFFER_SIZE];
int totalBytesSent = 0;
while (file)
{
file.read(buffer, BUFFER_SIZE-1);
int bytesRead = static_cast&lt;int&gt;(file.gcount());
bytesSent = send(clientSocket, buffer, bytesRead, 0);
if (bytesSent == SOCKET_ERROR) {
PRINT_ERR(&quot;Failed to send text&quot;);
file.close();
return false;
}
totalBytesSent += bytesSent;
PRINT_LOG(&quot;Sent &quot; + std::to_string(bytesSent) + &quot; bytes from file&quot;);
// Wait for acknowledgement message from server
if (!ReceiveText()) {
PRINT_ERR(&quot;Failed to receive acknowledgement message&quot;);
delete[]buffer;
return false;
}
// Send acknowledgement response to server
if (!SendText(&quot;ACK&quot;)) {
PRINT_ERR(&quot;Failed to send acknowledgement response&quot;);
delete[]buffer;
return false;
}
Sleep(Sleep_timer);
}
file.close();
PRINT_LOG(&quot;Sent file data: &quot; + std::to_string(totalBytesSent) + &quot; bytes&quot;);
return true;

}

**EDIT** These are the send and receive text, if thats necessary:

bool Server::ReceiveText()
{
if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

text = &quot;&quot;;
if (!CheckSocInit()) return false;
char buffer[BUFFER_SIZE];
bytesRecv = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
if (bytesRecv &lt; 1) {
PRINT_ERR(&quot;Failed to receive text&quot;);
return false;
}
buffer[bytesRecv] = &#39;\0&#39;;
text += buffer;
return true;

}


bool Server::SendText(std::string text)
{
if (!is_still_connected(clientSocket)) { PRINT("Client has disconnected"); return false; }

if (!is_ConnectedWithAClient)return false;
bytesSent = send(clientSocket, text.c_str(), int(text.size()) + 1, 0);
if (bytesSent == SOCKET_ERROR) {
PRINT_ERR(&quot;Failed to send text&quot;);
return false;
}
return true;

}


They are almost same in both client and server. there are only some flag checks as the difference in both
BTW there are some macros i&#39;ve been using for simplifying my understanding of the code. Here are the macros i&#39;ve used in those code:

#define BUFFER_SIZE 10240

#define PRINT_ERR(x) std::cerr<<"ERROR: "<<x<<std::endl;
#define PRINT_LOG(x) std::clog<<"LOG: "<<x<<std::endl;
#define PRINT(x) std::cout<<x<<std::endl;


if possible, please point out the blunder mistakes i&#39;ve done in the code that might or might not affect this question. 
Thanks in advance!!
</details>
# 答案1
**得分**: 1
TCP 与数据流一起工作,因此使用 `write` 一次发送的内容可能会需要多次调用 `read` 才能读取完。当处理 `localhost` 时,它们通常会对应一一,但当数据传输到“真实”的网络时,通常不会如此。
例如,假设我试图发送一个名为 `text.txt` 的文件,其中包含 "This is some text"。按照你现在发送数据的方式,接收端收到的内容将如下所示:
```cpp
text.txtThis is some text

在这里没有任何内容告诉接收方文件名何时结束,文件内容何时开始。目前,你依赖于一次 write 调用中发送的内容会在一次 read 调用中读取完毕,但这不一定会发生。例如,即使上述数据非常短,也完全有可能你会在第一次 read 调用中接收到所有数据(包括文件名和文件内容)。

为了处理这种情况,你必须编写代码,使其不依赖于这两者一一对应。通常,你可以通过在发送的数据中写入一些信息来明确告诉接收方一个部分的结束和另一个部分的开始。

其中一种可能性是,在发送文件名时,可以在文件名前添加文件名的长度,因此在接收端,你将首先读取长度字段,然后使用它来读取文件名,而不将文件内容误读为文件名的一部分。

bool sendFile(std::string_view fileName, socket dest) {
    uint32_t len = htonl(fileName.length());

    write(dest, (char *)&amp;len, sizeof(len)); // 写入文件名长度
    write(dest, filename.c_str(), len);     // 写入文件名本身

    // 针对文件的主体部分重复基本相同的操作。
    // 先写入文件的长度,然后写入文件的内容
}

然后在接收端,你基本上要镜像这个过程:

uint32_t len;

read(src, (char *)&amp;len, sizeof(len)); // 读取文件名长度
len = ntohl(len);
read(src, buffer, len);                // 读取文件名本身

// 然后读取文件主体的长度,接着读取主体本身

注意:为了支持超过4 GB的文件,你可能希望将主体长度字段扩展为8字节,而不是4字节。

但这只是过于简化的描述。

  • 在读取端,通常要分配一个合理固定大小的缓冲区来读取,而不是一次性尝试读取整个文件。
  • 单次读取或写入可能不会发送/接收你请求的完整数量,因此通常需要编写一个小循环:
while (bytes_to_send &gt; 0) {
    auto bytes_sent = write(dest, buffer, len);
    if (bytes_sent &gt; 0) 
        bytes_to_send -= bytes_sent;
    else {
       // 写入失败--要弄清楚发生了什么
    }
}

[...以及类似的读取情况。]

根据情况,你可能还希望考虑在每个部分(文件名和文件主体)的开头添加一个小标识符(例如,文件名标识为 "FN",主体标识为 "BD")。这将使将来扩展格式变得更容易,如果决定传输其他内容(例如,文件权限)也会更方便。

英文:

TCP works with a stream of data, so what you send with one call to write may or may not be read with a single call to read. When you're dealing with localhost, they typically will correspond--but when the data goes over a "real" network, they usually won't.

For example, let's say I'm trying send a file named text.txt, that contains "This is some text". The way you're sending things now, this will arrive on the receiving end, looking like this:

text.txtThis is some text

There's nothing there to tell the receiver where the name of the file ends, and the content of the file begins. Right now, you're depending on what you send in one call to write being read with one call to read. But that's not always what will happen. For example, as short as the data above is, it's entirely possible you'd receive all the data (file name and file body) in your first call to `read.

To deal with that, you have to write your code so it doesn't depend on the two corresponding. You usually do that by writing something into the data you send to explicitly tell the receiver where one thing ends, and another begins.

For one possibility, when you're sending the file name, you could prefix it with the length of the filename, so on the receiving end, you'd read the length field, then use that to read the file name--without reading any of the content of the file as if it were part of the file name.

bool sendFile(std::string_view fileName, socket dest) {
    uint32_t len = htonl(fileName.length());

    write(dest, (char *)&amp;len, sizeof(len)); // write file name length
    write(dest, filename.c_str(), len);     // write file name itself

    // repeat essentially the same thing for the body of the file.
    // write the length of the file, followed by the content of the file
}

Then in the receiving end, you basically mirror that:

uint32_t len;

read(src, (char *)&amp;len, sizeof(len)); // read filename length
len = ntohl(len);
read(src, buffer, len);                // read filename itself

// then read a length for the body, then the body itself

Note: to support files over 4 gigabytes, you may want to expand the field for the length of the body to 8 bytes instead of 4.

This is over-simplifying though.

  • On the read end, you typically want to allocate a buffer of reasonable, fixed size to read into, not attempt to read the whole file at once.
  • A single read or write may not send/receive the whole amount you've
    asked for, so you typically have to write little loop:
while (bytes_to_send &gt; 0) {
    auto bytes_sent = write(dest, buffer, len);
    if (bytes_sent &gt; 0) 
        bytes_to_send -= bytes_sent;
    else {
       // writing failed--sort out what happened
    }
}

[...and similarly for reading.]

Depending on the situation, you may also want to consider adding a little ID to the beginning of each of those pieces (file name and file body). (e.g., "FN" for file name, and "BD" for the body). This makes it a lot easier to extend the format in the future, in you decided to transmit something else as well (e.g., file permissions).

huangapple
  • 本文由 发表于 2023年6月19日 11:15:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76503402.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定