如何使用C++通过HTTP发送文件

3
我想编写服务器端代码,它应该能够与流行的浏览器和wget一起使用。我的服务器会检查文件是否存在,如果存在,则浏览器可以下载它。但是我遇到了一些问题。 老实说,我读了很多问题和答案(例如:使用C sockets发送HTTP响应中的二进制文件),但是我没有找到解决方法。我的浏览器(Chrome)可以获取文本,但我无法发送任何二进制数据或图像等。我按照下载文件的方式更改标题,但我仍未能成功发送可下载的文件。
我有一些问题:
void *clientWorker(void * acceptSocket) {
     int newSocket = (int) acceptSocket;
     char okStatus[] = "HTTP/1.1 200 OK\r\n"
                       "Content-Type: text/html\r\n"
                       "Connection: close\r\n"
                       "Content-Length: 20\r\n"
                       "\r\n"
                       "s";
     writeLn(newSocket, okStatus);
     const char * fileName = "/home/tyra/Desktop/example.txt";
     sendF(newSocket, fileName);
}

1- 如果我在okStatus中不写“s”或其他字符,我的消息将无法发送。我对此一无所知。

这是writeLn函数:

void writeLn(int acceptSocket, const char * buffer) {
    int n = write(acceptSocket, buffer, strlen(buffer) - 1);
    if (n < 0) {
        error("Error while writing");
    }
}

这是 sendF 函数:

string buffer;
string line;
ifstream myfile(fileName);
struct stat filestatus;
stat(fileName, &filestatus);
int fsize = filestatus.st_size;
if (myfile.is_open()) {
    while (myfile.good()) {
        getline(myfile, line);
        buffer.append(line);
    }
    cout << buffer << endl;
}
writeLn(acceptSocket, buffer.c_str());
cout << fsize << " bytes\n";

有点混乱,我还没有使用文件大小。如果我发送一个文件,那么我会重新排列这些东西。

2- 我可以发送文本,浏览器会展示它,但是浏览器不理解换行符。 如果文本文件包含(123\n456\n789),浏览器会展示(123456789)。我想我应该更改Content-Type头,但我没有找到方法。

我不希望浏览器展示文本文件。浏览器应该下载它。我该如何发送可下载的文件?

抱歉,我的解释可能有点复杂。


我认为问题之一是okStatus[]末尾没有零终止字符。尝试在末尾添加它,而不是s - Tony The Lion
@Tony The Tiger 谢谢你的帮助。我尝试了但是没有成功。 - Baskaya
@Tony The Tiger 字符串字面量会自动添加空字符终止符。 - Andy Johnson
我不知道这是否相关,但是okStatus中的Connection: close部分只跟随一个\n。它应该跟随\r\n - Andy Johnson
@Andy,嗯,我不是完全确定。 - Tony The Lion
它在这个表单中可以工作,但我改成了\r\n以避免复杂性。 - Baskaya
3个回答

2
关于您的第一个问题,您需要找出文件的精确大小并在"Content-Length: xxxx\r\n"头部中指定。当然,您还需要确保数据完全发送出去。
实际上,在您的writeF函数中,您使用std::string作为缓冲区:
string buffer;

这不适用于二进制数据。您应该分配一个正确大小的原始char数组:

int fsize = file.tellg();
char* buffer = new char[fsize];
file.seekg (0, ios::beg);
file.read (buffer, size);
file.close();

关于第二点,如果您的数据不是HTML,请指定Content-Type: text/plain; 否则,您的换行符应该用<br>代替"\r\n"。
在进行二进制下载时,若要将数据下载为文件(而非在浏览器中显示),您应该指定。
Content-Type: application/octet-stream

你好Sergio。如果我发送二进制数据,Chrome或Firefox会自动下载它吗? - Baskaya
1
你好 Thorn,看一下我的修改。你需要 Content-Type: application/octet-stream - sergio
我能否仅通过更改Content-Type来使用相同的方法(getline())发送rar文件或其他文件? - Baskaya
Thorn,你可以发送rar文件。不过,getline方法并不是最好的选择。请查看我的编辑,其中包含特定于二进制数据的代码片段。 - sergio
如果我想发送可下载文件,我应该以二进制形式发送,对吗?这样,我们可以获得与服务器上相同的文件吗?(不会丢失 '\n') - Baskaya

1

问题出在strlen。当strlen遇到'\0'字符时就会终止。在二进制文件中,你会发现有许多'\0'字符。

读取文件时,应该找出文件大小。这个大小应该在int n = write(acceptSocket, buffer, strlen(buffer) - 1);中使用,而不是strlen

writeLn(acceptSocket, buffer.c_str());改为writeLn(acceptSocket, buffer.c_str(), buffer.size());并尝试一下...

对于123\n456\n789的情况,你需要发送<PRE>123\n456\n789</PRE>,因为浏览器会将此文本解析为HTML,而不是像操作系统解析和显示输出。另一种方法是用<BR>替换所有的\n...


你好。感谢你的回答。我的第一个问题已经被你的帮助解决了。我进行了更改,现在它可以正常工作了。关于我的第二个问题,我不想展示文本,而是让浏览器下载它。我认为我应该更改Content-Type并以正确的方式发送它(不是getline),但我不确定。 - Baskaya
1
@Thom 如果浏览器能够解析mime "text/html",那么很可能它会这样做,因为这更方便。但是有一个方法可以绕过:使用标题指示您要使用Content-Disposition: attachment;filename="test.jpg"进行下载。有关更多详细信息,请参见http://apptools.com/phptools/force-download.php。这是php,但主要的http概念仍然适用。 - RedX
1
我不确定我是否理解正确。我猜你想要下载文本文件而不是在浏览器中显示它。如果是这种情况,就像@RedX建议的那样,你需要使用Content-Disposition: attachment;filename="test.txt" - Mayank
@Thorn:你可以看一下这篇文章Content-Type,它很不错。 - Mayank
@RedX 谢谢你的建议。它起作用了。你的链接非常有帮助。@Mayank 确实:) - Baskaya
显示剩余2条评论

0
关于问题1 - 如果您不想发送任何内容,则从okStatus的末尾删除s,并在标头中指定Content-Length: 0\r\n

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接