我目前正在编写一个服务器和客户端应用程序,试图传输屏幕截图,但无法正常工作。我是这样实现的。
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);
}
感谢您的时间和帮助。
答案 0 :(得分: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
加上位图的前四千个字节。客户端代码使用该字符串,并丢弃其余缓冲区。
您需要建立一个位于套接字和文件写入之间的通信协议。有很多不同的方法来处理此问题。读取字符串的常见技巧是
发件人
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
};
好!这样可以正确地移动琴弦。假设我那里没有错误。这个想法是正确的,但是实际的代码来自内存,可能包含脑筋。
您想要的是一堆函数,每种发送的数据类型都有一个。这些功能中的每一个都可以并且应该分别进行测试,以便在将它们集成到程序中时,程序看起来像
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
可能会失败,并且未对此进行检查。始终检查返回码。人们除非重要就不会把它们放在那里。