Simple webserver won't work

2019-09-16 13:01发布

问题:

I'm trying to write a webserver in C. I know I could use many different libraries for that, but I want it to run only with winsock. The server and a client can send data back and forth but loading the webpage is impossible. Every time I get a 'server unreachable' message, but on the server I get the message that the body was sent. What am I doing wrong?

edit I'm using chrome and microsoft edge

#include <stdio.h>
#include <conio.h>

#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")

void error(const char* err){
    printf("ERROR: %s\n", err);
    exit(1);
}

char* readline(SOCKET s){
    FILE * f = tmpfile();
    char rec;
    int i=0;
    while(recv(s, &rec, 1, 0)>0){
        i++;
        if(rec!='\r' && rec!='\n')
            fwrite(&rec, 1, 1, f);
        if(rec=='\r' || rec=='\n')
            break;
    }
    if(i==0){
        fclose(f);
        return NULL;
    }
    fseek(f, 0, SEEK_END);
    long long size = ftell(f);
    fseek(f, 0, SEEK_SET);
    char * d = (char*) malloc(size);
    memset (d,0,size);
    fread(d, 1, size, f);
    fclose(f);
    return d;
}

void flush(SOCKET s){
    while(recv(s, NULL, 1, 0)>0){
    }
}

void main(){

    // CREATE SERVER
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
        error("WSAStartup");
    SOCKET s;
    if((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
        error("socket");
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(80);
    if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
        error("bind");

    // SERVER CREATED
    while(1){
        listen(s, 1);
        int c = sizeof(struct sockaddr_in);
        SOCKET csock;
        struct sockaddr_in client;
        if((csock = accept(s, (struct sockaddr*)&client, &c))==INVALID_SOCKET)
            continue;
        char *client_ip = inet_ntoa(client.sin_addr);
        printf("Incomming connection: %s\n", client_ip);

        char * head = readline(csock);
        if(strncmp(head, "GET", 3)==0){
            flush(csock);

            char response[] = "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html\r\n\r\n"
            "<html><head><title>test</title>"
            "</head><body>Test123</body></html>";
            send(csock, response, strlen(response), 0);
            printf("%s\n", "HTML body sended");
        }else if(strncmp(head, "HEAD", 4)==0){
            flush(csock);

            char response[] = "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html\r\n\r\n";
            send(csock, response, strlen(response), 0);
            printf("%s\n", "HTML head sended");
        }

        closesocket(csock);
    }

    closesocket(s);
    WSACleanup();
}

回答1:

This function:

void flush(SOCKET s){
    while(recv(s, NULL, 1, 0)>0){
    }
}

reads nothing letting all in the buffer until the client (the browser) reset the connection. If you check the value returned by send(csock, response, strlen(response), 0); you'll see a SOCKET_ERROR. Calling WSAGetLastError() will return

Code 0x2745 = An established connection was aborted by the software in your host machine.

The way you use to read socket is really ugly! Use ioctlsocket() with FIONREAD to obtain the number of chars to read:

char* readline(SOCKET s)
{
    size_t MsgLen = 0;
    char *Msg = NULL;
    unsigned long Len;
    int res;
    while (0 == (res=ioctlsocket(s, FIONREAD, &Len)))
    {
        if (SOCKET_ERROR == Len)
        {
            if (Msg)
            {
                free(Msg);
                Msg = NULL;
            }
            return NULL;
        }

        if (!Len && MsgLen)
            break;

        if (!Msg)
            Msg = malloc(Len);
        else
            Msg = realloc(Msg, MsgLen + Len);

        recv(s, Msg+MsgLen, Len, 0);

        MsgLen += Len;
    }
    return Msg;
}

To use it fix your source:

char response[] = "HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/html\r\n\r\n"
                  "<html><head><title>test</title>"
                  "</head><body>Test123</body></html>\r\n";

char response[] = "HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/html\r\n\r\n";

int main(int argc, char *argv[])
{

    // CREATE SERVER
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
        error("WSAStartup");
    SOCKET s;
    if((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
        error("socket");
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(80);
    if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
        error("bind");

    // SERVER CREATED
    while(1){
        listen(s, 1);
        int c = sizeof(struct sockaddr_in);
        SOCKET csock;
        struct sockaddr_in client;
        if((csock = accept(s, (struct sockaddr*)&client, &c))==INVALID_SOCKET)
            continue;
        char *client_ip = inet_ntoa(client.sin_addr);
        printf("Incoming connection: %s\n", client_ip);

        char * head = NULL;
        do
        {
            head = readline(csock);
        } while (!head);
        if(strncmp(head, "GET", 3)==0)
        {
            if (SOCKET_ERROR  == send(csock, response, strlen(response), 0))
            {
                WSAGetLastError();
            }
            printf("%s\n", "HTML body sended");
        }
        else
            if(strncmp(head, "HEAD", 4)==0)
            {
                send(csock, response, strlen(response), 0);
                printf("%s\n", "HTML head sended");
            }

        closesocket(csock);
    }

    closesocket(s);
    WSACleanup();
}