【问题标题】:Sending a file over a TCP/IP socket (web server)通过 TCP/IP 套接字(Web 服务器)发送文件
【发布时间】:2015-09-23 21:35:56
【问题描述】:

我正在编写 Web 服务器的准系统,但我无法弄清楚为什么我的文件没有通过我的套接字发送,我正在连接它以及它只是不是 send()ing 我的文件...我错过了什么?

//代码 (server.c)

#include<netinet/in.h>    
#include<stdio.h>    
#include<stdlib.h>    
#include<sys/socket.h>    
#include<sys/stat.h>    
#include<sys/types.h>    
#include<unistd.h>    

int main(void) {    
   int create_socket, new_socket;    
   socklen_t addrlen;    
   int bufsize = 1024;    
   char *buffer = malloc(bufsize);    
   struct sockaddr_in address;    

   if ((create_socket = socket(AF_INET, SOCK_STREAM, 0)) > 0){    
      printf("The socket was created\n");
   }

   address.sin_family = AF_INET;    
   address.sin_addr.s_addr = INADDR_ANY;    
   address.sin_port = htons(80);    

   if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == 0){    
      printf("Binding Socket\n");
   }

    long fsize;
    FILE *fp = fopen("index.html", "r");
    fseek(fp, 0, SEEK_END);
    fsize = ftell(fp);
    rewind(fp);
    char *msg = malloc(fsize + 1);
    fread(msg, sizeof(msg), 1, fp);

   while (1) {    
      if (listen(create_socket, 10) < 0) {    
         perror("server: listen");    
         exit(1);    
      }    

      if ((new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen)) < 0) {    
         perror("server: accept");    
         exit(1);    
      }    

      if (new_socket > 0){    
         printf("The Client is connected...\n");
      }

        recv(new_socket, buffer, bufsize, 0);    
        printf("%s\n", buffer);    
        write(new_socket, "HTTP/1.1 200 OK\n", 16);
        write(new_socket, "Content-length: 46\n", 19);
        write(new_socket, "Content-Type: text/html\n\n", 25);
/*      write(new_socket, "<html><body><H1>Hello world</H1></body></html>",46); */
        if((send(new_socket, msg, fsize+1, 0)) > 0){
            printf("success");
        }     
        else{
            printf("failed");
        }
      close(new_socket);    
   }    
   close(create_socket);    
   return 0;    
}

//文件 (index.html) *同一目录

<html>
<body>
    <h1>Hello World</h1>
</body>
</html>

【问题讨论】:

  • 这个“准系统”服务器有很多问题,这并不好笑。你没有做很好的错误处理。您正在以文本模式而不是二进制模式打开文件。 sizeof(msg) 是错误的,请改用 fsize (就此而言,我不建议将整个文件读入内存,而是分块读取并发送,否则您以后将无法处理大文件) .您调用listen() 的次数太多,请调用一次,而不是循环调用。您没有检查 recv() 是否失败...
  • ... 在发送回复之前,您根本没有尝试解析客户端的正确性请求。您必须在回复标题中使用\r\n 而不是\n。您的 Content-length 标头未指定 实际 fsize 值(文件是 not 46 字节)。您不确定 send() 是否成功发送 all 请求的字节。并且发送fsize+1 字节是错误的,因为文件只有fsize 字节数。并且没有发送Connection: close 标头。
  • @JordanDavis 不,你没有。您刚刚添加了成功报告。这段代码实际上没有任何故障处理。
  • @JordanDavis:显然,他没有。由于十几个不同的原因,代码完全错误..
  • @JordanDavis:我刚刚发布了一个示例。

标签: c sockets http


【解决方案1】:

代码完全损坏了,原因有很多。尝试更多类似的方法:

#include <netinet/in.h>    
#include <stdio.h>    
#include <stdlib.h>    
#include <sys/socket.h>    
#include <sys/stat.h>    
#include <sys/types.h>    
#include <unistd.h>    

bool writeDataToClient(int sckt, const void *data, int datalen)
{
    const char *pdata = (const char*) data;

    while (datalen > 0){
        int numSent = send(sckt, pdata, datalen, 0);
        if (numSent <= 0){
            if (numSent == 0){
                printf("The client was not written to: disconnected\n");
            } else {
                perror("The client was not written to");
            }
            return false;
        }
        pdata += numSent;
        datalen -= numSent;
    }

    return true;
}

bool writeStrToClient(int sckt, const char *str)
{
    return writeDataToClient(sckt, str, strlen(str));
}

int main(void){
    int create_socket, new_socket;    
    char *buffer;
    int bufsize = 1024;    
    struct sockaddr_in address;    
    socklen_t addrlen;    

    buffer = (char*) malloc(bufsize);    
    if (!buffer){
        printf("The receive buffer was not allocated\n");
        exit(1);    
    }

    create_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (create_socket == -1){    
        perror("The socket was not created");    
        exit(1);    
    }

    printf("The socket was created\n");

    memset(&address, 0, sizeof(address));    
    address.sin_family = AF_INET;    
    address.sin_addr.s_addr = INADDR_ANY;    
    address.sin_port = htons(80);    

    if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == -1){    
        perror("The socket was not bound");    
        exit(1);    
    }

    printf("The socket is bound\n");    

    long fsize;
    FILE *fp = fopen("index.html", "rb");
    if (!fp){
        perror("The file was not opened");    
        exit(1);    
    }

    printf("The file was opened\n");

    if (fseek(fp, 0, SEEK_END) == -1){
        perror("The file was not seeked");
        exit(1);
    }

    fsize = ftell(fp);
    if (fsize == -1) {
        perror("The file size was not retrieved");
        exit(1);
    }
    rewind(fp);

    char *msg = (char*) malloc(fsize);
    if (!msg){
        perror("The file buffer was not allocated\n");
        exit(1);
    }

    if (fread(msg, fsize, 1, fp) != 1){
        perror("The file was not read\n");
        exit(1);
    }
    fclose(fp);

    printf("The file size is %ld\n", fsize);

    if (listen(create_socket, 10) == -1){
        perror("The socket was not opened for listening");    
        exit(1);    
    }    

    printf("The socket is listening\n");

    while (1) {    

        addrlen = sizeof(address);
        new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen);

        if (new_socket == -1) {    
            perror("A client was not accepted");    
            exit(1);    
        }    

        printf("A client is connected from %s:%hu...\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));    

        // I will leave it as an exercise for you to implement
        // a proper HTTP request parser here...
        int numRead = recv(new_socket, buffer, bufsize, 0);
        if (numRead < 1){
            if (numRead == 0){
                printf("The client was not read from: disconnected\n");
            } else {
                perror("The client was not read from");
            }
            close(new_socket);
            continue;
        }
        printf("%.*s\n", numRead, buffer);    

        if (!writeStrToClient(new_socket, "HTTP/1.1 200 OK\r\n")){
            close(new_socket);
            continue;
        }

        char clen[40];
        sprintf(clen, "Content-length: %ld\r\n", fsize);
        if (!writeStrToClient(new_socket, clen)){
            close(new_socket);
            continue;
        }

        if (!writeStrToClient(new_socket, "Content-Type: text/html\r\n")){
            close(new_socket);
            continue;
        }

        if (!writeStrToClient(new_socket, "Connection: close\r\n\r\n") == -1){
            close(new_socket);
            continue;
        }

        //if (!writeStrToClient(new_socket, "<html><body><H1>Hello world</H1></body></html>")){
        if (!writeDataToClient(new_socket, msg, fsize)){
            close(new_socket);
            continue;
        }

        printf("The file was sent successfully\n");
        close(new_socket);    
   }    

   close(create_socket);    
   return 0;    
}

【讨论】:

  • VG 但是你忽略了fread()的返回值,如果read()返回零,你就调用perror(),这是无效的。
  • 我没有忽略fread() 的返回值,但您对recv() 的返回值是正确的(我没有调用read())。我已经解决了。
  • 我对代码做了一些调整。再次尝试编译。
  • @Pilpel:在此示例中不需要,因为它被硬编码为仅使用 IPv4 并且仅在 INADDR_ANY 上侦听。使用getaddrinfo() 将产生相同的sockaddr_in,除非您启用IPv6,在这种情况下您需要分配具有多个绑定的多个套接字,那么getaddrinfo 是有意义的。或者你可以创建一个双栈套接字来同时处理 IPv4 和 IPv6,在这种情况下你不会再使用getaddrinfo
  • @Pilpel:它可以在服务器端使用,但在客户端更有用
【解决方案2】:
fsize = ftell(fp);
rewind(fp);
char *filebuff = malloc(fsize + 1);

为什么是fsize+1?你不需要+1

fread(filebuff, sizeof(filebuff), 1, fp);

未经检查的返回值。第二个参数应该是fsize。目前您只传递了sizeof 指针。

//create/bind socket
if ((create_socket = socket(AF_INET, SOCK_STREAM, 0)) > 0)
{    
  printf("The socket was created\n");
}

如果套接字创建失败,您必须 (a) 打印如下所述的正确错误消息,并且 (b) 继续执行,就好像没有出现错误一样发生了。

if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == 0)
{    
  printf("Binding Socket\n");

}

同上。

//listen, create new_sock, write headers, send file 
while (1){   
   if (listen(create_socket, 10) < 0) {    
       perror("server: listen");    
       exit(1);    
    }

listen() 调用应该在循环之前,而不是在循环内部。这是您第一次实际处理失败案例。

    new_sock = accept(sock, (struct sockaddr *) &address, &addrlen);            
    recv(new_socket, buffer, bufsize, 0);    
    printf("%s\n", buffer);    

无效。未经检查的返回码。缓冲区只有在recv() 返回一个正整数时才有效,并且只有它的那么多字节是有效的。应该是:

    int count = recv(new_socket, buffer, bufsize, 0);    
    printf("%.*s\n", count, buffer);    

然后我们从 HTTP 开始:

    write(new_sock, "HTTP/1.1 200 OK\n", 16);
    write(new_sock, "Content-length: 46\n", 19);
    write(new_sock, "Content-Type: text/html\n\n", 25);

HTTP 中的行终止符继承自 Telnet,指定为\r\n,而不是\n

    if(send(new_sock, filebuff, fsize+1, 0) > 0){
        printf("success");
    }     
    else{
        printf("failed");
    }

不足。如果您从任何系统调用中得到错误,您必须调用perror(),或在错误消息中使用errnostrerror()。 “失败”没有传达任何有用的信息,调试就变成了一场猜谜游戏。不要写这样的代码。您应该使用perror() 或您为上述所有其他未经检查的返回值决定的任何内容。

但是还有一个更大的问题。您假设该文件适合内存。不需要这种假设。只需使用 8k 缓冲区复制文件,如下所示:

int count;
while ((count = read(in, buffer, sizeof buffer)) > 0)
{
    send(out, buffer, count, 0);
}
if (count < 0)
{
    perror("send failed");
}

为此我会避免使用stdio,它有太多问题,例如设计不佳的fread()fwrite() 函数API。

【讨论】:

  • 我刚刚更新了添加值的代码...为了清楚起见,我离开了检查,我把它写在了这个问题的顶部...
  • 感谢您输入@EJP
【解决方案3】:

除了在不同地方使用的错误大小(如 mathematician1975 所述),您的“真正”问题是您尝试与需要 HTTP 服务器的浏览器进行通信。

HyperText Ttransfer Protocol 是 a protocol。它比简单的连接和内容转储更复杂。

你必须根据它来解析请求,并以某种方式发送头部和内容。

【讨论】:

  • 是的,我认为它是标题.... 将在发送文件(index.html)之前先将它们写在套接字上?
  • @JordanDavis 阅读了该协议。这个答案太长了,无法表达,但是网上有很多资料。
  • 我知道协议我不是一个完整的白痴:),并且让服务器将 html 作为“字符串”发送,但现在当我尝试将它作为文件发送时它失败了。 .我的猜测是我需要在发送包含html的缓冲区之前将标头发送到套接字......或者我需要在发送缓冲区之前将标头写入缓冲区?
  • 大图,是的,标题在内容之前。但实际上,如果您有任何认真对待此事的计划,请广泛阅读有关该协议的内容
  • @JordanDavis:TCP 是一种流传输。您可以根据需要多次send(),数据将根据需要缓存在网络层上。重要的是您发送的什么
【解决方案4】:

检查bind() 是否失败并报告是否失败。您正在绑定到端口 80;在类 Unix 操作系统下,只有 root 可以绑定到保留端口(小于 1024)。

更新 1:

在调用accept() 之前,您必须将addrlen 初始化为sizeof(address)。来自http://linux.die.net/man/2/accept

addrlen 参数是一个值结果参数:调用者必须 初始化它以包含指向的结构的大小(以字节为单位) 通过地址;返回时它将包含对等方的实际大小 地址。

【讨论】:

  • 我知道这不是因为我事先让它使用了 HTML 的“字符串”,并且编译器在 bind() 上没有失败
猜你喜欢
  • 1970-01-01
  • 2020-12-03
  • 2018-02-05
  • 1970-01-01
  • 2020-04-28
  • 2017-03-14
  • 2016-02-17
  • 2011-05-03
  • 2019-12-29
相关资源
最近更新 更多