我想在C++上通过TCP套接字发送文件,所有的文件都工作得很好,但是我不能发送这样的大文件,我知道TCP作为任何协议都有它的局限性,就像我不能发送超过64 12的每个包,我的方法适用于小文件大小(测试所有测试都高达12 12),但是我想发送大文件,比如ubuntu或Windows的iso映像,这些文件肯定大于12个完全打包的数据包等等。
服务器
int filesize = 0;
int err = recv(conn, (char*)&filesize, sizeof(filesize), 0);
if (err <= 0)
{
printf("recv: %d\n", WSAGetLastError());
clean(conn);
}
printf("recv %d bytes [OK]\n", err);
char* buffer = new char[filesize];
ZeroMemory(buffer, filesize);
err = recv(conn, buffer, filesize, MSG_WAITALL);
if (err <= 0)
{
printf("recv: %d\n", WSAGetLastError());
clean(conn);
}
printf("recv %d bytes [OK]\n", err);
ofstream file("a.txt", ios::binary);
file.write(buffer, filesize);
delete[] buffer;
file.close();客户端
ifstream file("a.txt", ios::binary);
file.seekg(0, ios::end);
int size = file.tellg();
file.seekg(0, ios::beg);
char* buffer = new char[size];
file.read(buffer, size);
file.close();
int* fsize = &size;
int err = send(client, (char*)fsize, sizeof(int), 0);
if (err <= 0)
{
printf("send: %d\n", WSAGetLastError());
}
printf("send %d bytes [OK]\n", err);
err = send(client, buffer, size, 0);
if (err <= 0)
{
printf("send: %d\n", WSAGetLastError());
}
printf("send %d bytes [OK]\n", err);
delete[] buffer;双方的所有值都被初始化,错误处理做得很好,如果我有问题的话,我会说的。我决定使用MSG_WAITALL是因为我认为它适合于这种情况,请更正我的接收/发送代码,如果可能的话,如果有解释的话,会更好,这样每个人都可以学到更好的代码,谢谢)
发布于 2020-08-19 23:37:55
在你的问题下面的评论中,有一点应该被删除,那就是send和recv是变化无常的。编写send(buffer with 100 bytes)并不意味着它将发送100个字节。它可以发送25个字节,或者99个字节,或者完全失败。这取决于您获取返回值并计算仍然需要发送的内容。
recv也是如此。如果您编写recv(buffer with 100 bytes)是因为您期望有100个字节,那么它只能获取25个字节,或者99个字节,或者完全失败。同样,这取决于您使用返回值并计算仍然需要接收的内容。
文件I/O完全不同。如果要将100个字节写入文件,则如果方法没有失败,将保证写入这100个字节。因此,当处理过文件I/O的人移动到套接字时,I/O通常会混淆为什么事情没有正确发送或接收。
套接字编程中最棘手的部分之一是知道需要接收多少数据。您首先发送文件的长度来说明这一点。服务器将知道如何读取该值,然后继续读取,直到该值满足为止。
有些协议,比如HTTP,将使用分隔符(在HTTP的例子中是\r\n\r\n)来在数据包结束时发出信号。因此,作为一个套接字程序员,您需要在循环中使用recv,直到读取这4个字节为止。
我举了一个例子,说明如何完成发送和接收一个大文件(这将处理长达9,223,372,032,854,775,807的文件)。这不是纯粹的C++,我在一些地方作弊是因为时间不够。出于同样的原因,我使用了一些只使用Windows的构造。
让我们来看一看:
int64_t GetFileSize(const std::string& fileName) {
// no idea how to get filesizes > 2.1 GB in a C++ kind-of way.
// I will cheat and use Microsoft's C-style file API
FILE* f;
if (fopen_s(&f, fileName.c_str(), "rb") != 0) {
return -1;
}
_fseeki64(f, 0, SEEK_END);
const int64_t len = _ftelli64(f);
fclose(f);
return len;
}
///
/// Recieves data in to buffer until bufferSize value is met
///
int RecvBuffer(SOCKET s, char* buffer, int bufferSize, int chunkSize = 4 * 1024) {
int i = 0;
while (i < bufferSize) {
const int l = recv(s, &buffer[i], __min(chunkSize, bufferSize - i), 0);
if (l < 0) { return l; } // this is an error
i += l;
}
return i;
}
///
/// Sends data in buffer until bufferSize value is met
///
int SendBuffer(SOCKET s, const char* buffer, int bufferSize, int chunkSize = 4 * 1024) {
int i = 0;
while (i < bufferSize) {
const int l = send(s, &buffer[i], __min(chunkSize, bufferSize - i), 0);
if (l < 0) { return l; } // this is an error
i += l;
}
return i;
}
//
// Sends a file
// returns size of file if success
// returns -1 if file couldn't be opened for input
// returns -2 if couldn't send file length properly
// returns -3 if file couldn't be sent properly
//
int64_t SendFile(SOCKET s, const std::string& fileName, int chunkSize = 64 * 1024) {
const int64_t fileSize = GetFileSize(fileName);
if (fileSize < 0) { return -1; }
std::ifstream file(fileName, std::ifstream::binary);
if (file.fail()) { return -1; }
if (SendBuffer(s, reinterpret_cast<const char*>(&fileSize),
sizeof(fileSize)) != sizeof(fileSize)) {
return -2;
}
char* buffer = new char[chunkSize];
bool errored = false;
int64_t i = fileSize;
while (i != 0) {
const int64_t ssize = __min(i, (int64_t)chunkSize);
if (!file.read(buffer, ssize)) { errored = true; break; }
const int l = SendBuffer(s, buffer, (int)ssize);
if (l < 0) { errored = true; break; }
i -= l;
}
delete[] buffer;
file.close();
return errored ? -3 : fileSize;
}
//
// Receives a file
// returns size of file if success
// returns -1 if file couldn't be opened for output
// returns -2 if couldn't receive file length properly
// returns -3 if couldn't receive file properly
//
int64_t RecvFile(SOCKET s, const std::string& fileName, int chunkSize = 64 * 1024) {
std::ofstream file(fileName, std::ofstream::binary);
if (file.fail()) { return -1; }
int64_t fileSize;
if (RecvBuffer(s, reinterpret_cast<char*>(&fileSize),
sizeof(fileSize)) != sizeof(fileSize)) {
return -2;
}
char* buffer = new char[chunkSize];
bool errored = false;
int64_t i = fileSize;
while (i != 0) {
const int r = RecvBuffer(s, buffer, (int)__min(i, (int64_t)chunkSize));
if ((r < 0) || !file.write(buffer, r)) { errored = true; break; }
i -= r;
}
delete[] buffer;
file.close();
return errored ? -3 : fileSize;
}发送和接收缓冲区
在顶部,我们有两种方法可以处理内存中的缓冲区。您可以向它发送任意大小的缓冲区(在这里保持合理),这些方法将发送和接收,直到传入的所有字节都已被传输。
这正是我上面所说的。它接受缓冲区和循环,直到所有字节都被成功地发送或接收。这些方法完成后,将保证所有数据都被传输(只要返回值为零或正)。
您可以定义“块大小”,这是方法用于发送或接收数据的数据块的默认大小。我确信可以通过使用比当前设置的值更合适的值来优化这些值,但我不知道这些值是什么。在默认情况下留下它们是安全的。我不认为,随着今天的计算机速度,你会注意到太多的差别,如果你把它改为其他东西。
发送和接收文件
执行文件的代码在本质上与缓冲区代码几乎相同。同样的想法,除了现在我们可以假设,如果缓冲区方法的返回值大于零,那么它是成功的。所以代码更简单一些。我用64 of的块大小..。没有什么特别的理由。这一次,块大小决定从文件I/O操作中读取了多少数据,而不是从套接字I/O读取多少数据。
测试服务器和客户端
为了完整起见,我使用了下面的代码来测试磁盘上的5.3GB文件。基本上,我只是用一种非常精简的方式重写了微软的客户端/服务器示例。
#pragma comment(lib, "Ws2_32.lib")
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <fstream>
DWORD __stdcall ClientProc(LPVOID param) {
struct addrinfo hints = { 0 }, * result, * ptr;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if (getaddrinfo("127.0.0.1", "9001", &hints, &result) != 0) {
return ~0;
}
SOCKET client = INVALID_SOCKET;
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
client = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (client == SOCKET_ERROR) {
// TODO: failed (don't just return, cleanup)
}
if (connect(client, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR) {
closesocket(client);
client = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (client == SOCKET_ERROR) {
std::cout << "Couldn't create client socket" << std::endl;
return ~1;
}
int64_t rc = SendFile(client, "D:\\hugefiletosend.bin");
if (rc < 0) {
std::cout << "Failed to send file: " << rc << std::endl;
}
closesocket(client);
return 0;
}
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
{
struct addrinfo hints = { 0 };
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
struct addrinfo* result = NULL;
if (0 != getaddrinfo(NULL, "9001", &hints, &result)) {
// TODO: failed (don't just return, clean up)
}
SOCKET server = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (server == INVALID_SOCKET) {
// TODO: failed (don't just return, clean up)
}
if (bind(server, result->ai_addr, (int)result->ai_addrlen) == INVALID_SOCKET) {
// TODO: failed (don't just return, clean up)
}
freeaddrinfo(result);
if (listen(server, SOMAXCONN) == SOCKET_ERROR) {
// TODO: failed (don't just return, clean up)
}
// start a client on another thread
HANDLE hClientThread = CreateThread(NULL, 0, ClientProc, NULL, 0, 0);
SOCKET client = accept(server, NULL, NULL);
const int64_t rc = RecvFile(client, "D:\\thetransmittedfile.bin");
if (rc < 0) {
std::cout << "Failed to recv file: " << rc << std::endl;
}
closesocket(client);
closesocket(server);
WaitForSingleObject(hClientThread, INFINITE);
CloseHandle(hClientThread);
}
WSACleanup();
return 0;
}https://stackoverflow.com/questions/63494014
复制相似问题