使用TCP协议通过HTTP发送HTML文件,浏览器显示错误。

4
我正在编写一个HTTP Web服务器,当我发送一个具有HTML文件内容等效的文本文件时,浏览器能正确地显示它,但是当我直接发送HTML文件时,浏览器会在一秒钟内显示HTML页面,然后出现“连接被重置”的错误。

我注意到文本文件比HTML文件大,但我不知道为什么。

文本大小=286字节

HTML大小=142字节

以下是HTML代码:

<!DOCTYPE html>
<html>
<body>

<p>This is a paragraph.</p>
<p>This is a paragraph.</p>
<p>This is a paragraph.</p>

</body>
</html>

这是我的代码:

char sendBuffer[500];

FILE *sendFile = fopen("foo.html", "r");
fseek(sendFile, 0L, SEEK_END);
int sz = ftell(sendFile);
fseek(sendFile, 0L, SEEK_SET);

string s1;
s1="HTTP/1.1 200 OK\nContent-length: " + to_string(sz) + "\n";
std::vector<char> writable(s1.begin(), s1.end());
writable.push_back('\0');

strcpy(sendBuffer,(const char *)&writable[0]);
int c=send(connected,(const char*)&sendBuffer,strlen(&writable[0]),0);
printf("\nSent : %s\n",sendBuffer);
strcpy(sendBuffer,"Content-Type: text/html\n\n");
c=send(connected,(const char*)&sendBuffer,strlen("Content-Type: text/html\n\n"),0);
printf("\nSent : %s\n",sendBuffer);

char send_buffer[300];

while( !feof(sendFile) )
{
    int numread = fread(send_buffer, sizeof(unsigned char), 300, sendFile);
    if( numread < 1 ) break; // EOF or error

    char *send_buffer_ptr = send_buffer;
    do {
        int numsent = send(connected, send_buffer_ptr, numread, 0);
        if( numsent < 1 ) // 0 if disconnected, otherwise error
         {
            if( numsent < 0 ) {
                if( WSAGetLastError() == WSAEWOULDBLOCK )
                {
                    fd_set wfd;
                    FD_ZERO(&wfd);
                    FD_SET(connected, &wfd);

                    timeval tm;
                    tm.tv_sec = 10;
                    tm.tv_usec = 0;

                    if( select(0, NULL, &wfd, NULL, &tm) > 0 )
                        continue;
               }
           }

        break; // timeout or error
    }

    send_buffer_ptr += numsent;
    numread -= numsent;
}
while( numread > 0 );
}

以下是在上述代码之前使用的其他部分:

这里是之前使用的代码:

int sock, connected, bytes_recieved , _true = 1 , portNumber;
char send_data [1024] , recv_data[1024];      
struct sockaddr_in server_addr,client_addr;   
int sin_size;

time_t t = time(NULL);
struct tm tm = *localtime(&t);
char date[50];

if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
    perror("Unable to create the Socket");
    exit(1);
}

if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(const char*)&_true,sizeof(int)) == -1) {
    perror("Unable to Setsockopt");
    exit(1);
}
char *server_address="127.1.1.1";
portNumber=8080;
server_addr.sin_family = AF_INET; 
server_addr.sin_port = htons(portNumber);
server_addr.sin_addr.s_addr = inet_addr("127.1.1.1");//inet_pton(AF_INET,"127.0.0.1",&server_addr.sin_addr);//INADDR_ANY;

string host=server_address+':'+to_string(portNumber);


memset(&(server_addr.sin_zero),0,8);//sockaddr_in zero padding is needed
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))==-1) //bind the socket to a local address
{
    perror("Unable to bind");
    exit(1);
}

if (listen(sock, 5) == -1) //listen to the socket with the specified waiting queue size
{
    perror(" Listen");
    exit(1);
}

cout << "MyHTTPServer waiting on port 8080" << endl;
fflush(stdout);

sin_size = sizeof(struct sockaddr_in);
connected = accept(sock, (struct sockaddr *)&client_addr,&sin_size);

cout<< "I got a connection from (" << inet_ntoa(client_addr.sin_addr) << "," << ntohs(client_addr.sin_port) << ')' << endl;

投一票以抵消匿名用户的负评。此外,问题似乎合理。 - Craig S. Anderson
2
HTTP需要"\r\n"换行符,"\n"是不够的。如果这不能解决你的问题,请尝试提供一个最小化、完整的示例。 - Phillip
@Phillip 你说得没错,但我见过只用\n就能正常工作的实现。而且正如我所说,当它以文本格式存在时,它可以正常工作。问题出在HTML格式上。你知道为什么HTML和文本文件的大小会不同吗? - Freelancer
2个回答

2
我发现你有两个重要问题,这两个问题与IT技术有关。请看下面的内容:

  1. Your are passing send paremeters wrong, this line (very important)

    int c=send(connected,(const char*)&sendBuffer,strlen(&writable[0]),0);
    

    should be

    int c=send(connected,(const char*) sendBuffer,strlen(&writable[0]),0);
    /*                                ^ 
     *                                No ampersand
     */
    

    since the sendBuffer array decays to a pointer and you don't need that.

  2. You are passing the first parameter of select wrong too from the manual

    nfds is the highest-numbered file descriptor in any of the three sets, plus 1

    so in your case it should be

    if (select(connected + 1, NULL, &wfd, NULL, &tm) > 0)
    

    and you are using it after you call send you must call it before to see if it is possible to write to the file descriptor.

您的代码对于其设计的任务来说有些过于复杂,因此我建议采用以下解决方案,解决了提到的问题并改进了一些其他问题。

string       text;
stringstream stream;

FILE *sendFile = fopen("foo.html", "r");
if (sendFile == NULL) /* check it the file was opened */
    return;

fseek(sendFile, 0L, SEEK_END);
/* you can use a stringstream, it's cleaner */
stream << "HTTP/1.1 200 OK\nContent-length: " << ftell(sendFile) << "\n";
fseek(sendFile, 0L, SEEK_SET);

text = stream.str();
/* you don't need a vector and strcpy to a char array, just call the .c_str() member
 * of the string class and the .length() member for it's length
 */
send(connected, text.c_str(), text.length(), 0);

std::cout << "Sent : " <<  text << std::endl;

text = "Content-Type: text/html\n\n";
send(connected, text.c_str(), text.length(), 0);

std::cout << "Sent : %s" << text << std::endl;
while (feof(sendFile) == 0)
{
    int  numread;
    char sendBuffer[500];

    numread = fread(sendBuffer, sizeof(unsigned char), 300, sendFile);
    if (numread > 0)
    {
        char *sendBuffer_ptr;

        sendBuffer_ptr = sendBuffer;
        do {
            fd_set  wfd;
            timeval tm;

            FD_ZERO(&wfd);
            FD_SET(connected, &wfd);

            tm.tv_sec  = 10;
            tm.tv_usec = 0;
            /* first call select, and if the descriptor is writeable, call send */
            if (select(1 + connected, NULL, &wfd, NULL, &tm) > 0)
            {
                int numsent;

                numsent = send(connected, sendBuffer_ptr, numread, 0);
                if (numsent == -1)
                    return;
                sendBuffer_ptr += numsent;
                numread        -= numsent;
            }
        } while (numread > 0);
    }
}
/* don't forget to close the file. */
fclose(sendFile);

@AliNfr 我进行了测试,仍然不清楚你所说的“文本文件”是什么意思?你是指直接发送文本吗? - Iharob Al Asimi
正如我在问题中提到的,如果您将HTML文件保存为.txt,例如foo.txt,则文件可以正常发送并在浏览器中正确显示。我一直在尝试使用c语言编写HTTP服务器,但是这些问题迫使我使用Python。当我用Python编写服务器时,我没有遇到这样的问题,现在我的项目已经完成了。 - Freelancer
@AliNfr,你使用的编译器、集成开发环境和操作系统是什么? - Iharob Al Asimi
Windows 7,Visual Studio 2012均已安装最新更新。我一直在尝试使用RESTclient和fiddler客户端测试程序,但两者都显示程序存在缺陷。 - Freelancer
@AliNfr 改变文件扩展名确实没有真正的区别,但我对 Windows 不是很确定,因为自从我感到快乐以来已经有很长时间了。抱歉,我的意思是,我不再使用它了,我在虚拟机上安装了它,所以我会尝试在那里,这真的很奇怪。你的整个程序在没有任何运行时错误的情况下完美地运行。我使用 valgrindfirefox 客户端和 KDE 浏览器 rekonq 进行了测试。 - Iharob Al Asimi
显示剩余3条评论

1
半个答案。首先,即使“使用\n可以工作”,它也会违反标准。应该使用CRLF。使用CRLF。句号。
对于其余的代码。我怀疑这不会改变很多事情,但我会重新组织一下代码。在发送函数中要做太多事情了。
已将数据发送分离为自己的函数。如果您找到了良好的结构方式,您还可以考虑将发送标题分离为自己的函数-。当您扩展以发送文本或HTML等等时,您肯定应将标题分离为自己的函数。在早期阶段这样做会很有帮助。
只是一个粗略的开始。
int send_data(int soc, const char *buf, size_t len)
{
    ssize_t sent;

    do {

        /* Use iharob code or similar here */
        /* Return something <> 0 on error. */

        sent = send(soc, buf, len, 0);

        buf += sent;
        len -= sent;
    } while (len > 0);

    return 0;
}

int send_file(int soc, const char *fn)
{
    char buf[500];
    FILE *fh;
    long sz;
    size_t len;
    int err = 0;

    if (!(fh = fopen(fn, "r"))) {
        perror("fopen");
        return 1;
    }
    fseek(fh, 0L, SEEK_END);
    sz = ftell(fh);
    fseek(fh, 0L, SEEK_SET);

    /* Consider adding Date + Server here. */
    len = sprintf(buf,
            "HTTP/1.1 200 OK\r\n"
            "Content-length: %ld\r\n"
            "Content-Type: text/html\r\n"
            "Server: FooBar/0.0.1\r\n"
            "\r\n", sz
    );
    if (len < 0) {
        err = 3;
        fprintf(stderr, "Error writing header.\n");
        goto fine;
    }

    /* Debug print. */
    fprintf(stderr, "Header[%d]:\n'%s'\n", len, buf);

    if ((err = send_data(soc, buf, len)) != 0) {
        fprintf(stderr, "Error sending header.\n");
        goto fine;
    }

    while (!feof(fh)) {
        len = fread(buf, sizeof(char), 500, fh);
        if (len < 1)
            break;
        if ((err = send_data(soc, buf, len))) {
            fprintf(stderr, "Error sending file.\n");
            goto fine;
        }
    }
    if ((err = ferror(fh))) {
        fprintf(stderr, "Error reading file.\n");
        perror("fread");
    }
fine:
    fclose(fh);
    return err;
}

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