请稍侯

轻量级服务器tinyhttpd源码分析-本地运行

20 June 2015

  从sourceforge上下载源码到本地,我在看源码之前喜欢先将程序运行起来看下程序运行的效果,这样对于程序的功能先有一个感性的认识。
  下载下来的源码是在Solaris 2.6上编译运行的,在httpd.c中写道:在如果你想要在Linux上运行的话,需要进行一些修改:

1) 注释掉 #include <pthread.h>   
2) 注释掉newthread变量的定义  
3) 注释掉pthread_create()函数的调用  
4) 将accept_request()的调用取消注释  
5) 在Makefile中移除-lsocket  

  修改完之后Server变成了单线程的程序,只能够一个客户端进行连接,所以决定不按照上面的修改,本机的运行环境:OS X。首先将程序make下看看有什么错误。

httpd.c:437:52: warning: passing 'int *' to parameter of type 'socklen_t *' (aka 'unsigned int *') converts between pointers to integer types with different
      sign [-Wpointer-sign]
  if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
                                                   ^~~~~~~~
/usr/include/sys/socket.h:566:74: note: passing argument to parameter here
int     getsockname(int, struct sockaddr * __restrict, socklen_t * __restrict)
                                                                             ^
httpd.c:491:24: warning: passing 'int *' to parameter of type 'socklen_t *' (aka 'unsigned int *') converts between pointers to integer types with different
      sign [-Wpointer-sign]
                       &client_name_len);
                       ^~~~~~~~~~~~~~~~
/usr/include/sys/socket.h:560:69: note: passing argument to parameter here
int     accept(int, struct sockaddr * __restrict, socklen_t * __restrict)
                                                                        ^
httpd.c:495:40: warning: incompatible pointer types passing 'void (int)' to parameter of type 'void *(*)(void *)' [-Wincompatible-pointer-types]
 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
                                       ^~~~~~~~~~~~~~
/usr/include/pthread.h:314:11: note: passing argument to parameter here
                void *(*)(void *), void * __restrict);
                        ^
httpd.c:495:56: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'void *' [-Wint-conversion]
 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
                                                       ^~~~~~~~~~~
/usr/include/pthread.h:314:39: note: passing argument to parameter here
                void *(*)(void *), void * __restrict);
                                                    ^
4 warnings generated.

  有四个警告,让我们一一解决掉,首先是:

httpd.c:437:52: warning: passing 'int *' to parameter of type 'socklen_t *' (aka 'unsigned int *') converts between pointers to integer types with different

  这个警告的意思就是getsockname函数的第三个参数应该是socklen_t *类型,但是实际传入的参数类型是int *类型,只需要修改下namelen定义的类型修改为socklen_t类型。

httpd.c:491:24: warning: passing 'int *' to parameter of type 'socklen_t *' (aka 'unsigned int *') converts between pointers to integer types with different

  这个错误与上面错误类似,也是类型不匹配,将client_name_len定义为socklen_t类型就好了。

httpd.c:495:40: warning: incompatible pointer types passing 'void (int)' to parameter of type 'void *(*)(void *)' [-Wincompatible-pointer-types]

  这个错误也是参数类型不匹配,pthread_create函数的第三个参数是函数指针类型,这个函数指针应该是void ()(void *)类型,但是定义的accept_request声明为;

void accept_request(int);  

  所以需要对accept_request进行一些修改,首先将accept_request声明为:

void *accept_request(void *);  

  因为参数类型和函数返回值都改变了,所以在函数内部需要做些改动,在client使用之前将ptr_client转为client,还有就是需要在最后增加返回语句return NULL;,在中间需要返回的时候也需要返回NULL,修改完的accept_request代码如下:

void *accept_request(void *pclient)
{
    char buf[1024];
    int numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st;
    int cgi = 0;      /* becomes true if server decides this is a CGI program */
    char *query_string = NULL;
    size_t client = *(size_t *)pclient;

    numchars = get_line(client, buf, sizeof(buf));
    i = 0; j = 0;
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
    {
        method[i] = buf[j];
        i++; j++;
    }
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        unimplemented(client);
        return NULL;
    }

    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';

    if (strcasecmp(method, "GET") == 0)
    {
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        if (*query_string == '?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    sprintf(path, "htdocs%s", url);
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    if (stat(path, &st) == -1) {
    while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        numchars = get_line(client, buf, sizeof(buf));
        not_found(client);
    }
    else
    {
        if ((st.st_mode & S_IFMT) == S_IFDIR)
            strcat(path, "/index.html");
        if ((st.st_mode & S_IXUSR) ||
            (st.st_mode & S_IXGRP) ||
            (st.st_mode & S_IXOTH)    )
            cgi = 1;
        if (!cgi)
            serve_file(client, path);
        else
            execute_cgi(client, path, method, query_string);
    }

    close(client);
    return NULL;
}

  再将-lsocketMakefile中删去即可。
  最后,还有一处错误需要修改:

execl(path, path, NULL);  

  改为

execl(path, query_string, NULL);  

  再次运行make,没有警告了,运行可执行文件httpd:./ httpd,显示程序在哪个端口监听,打开浏览器,输入127.0.0.1:<端口号>,页面如图所示:

  在框中输入red或者blue,点击按钮跳转,跳转之后的页面显示为你填写的颜色:

  经过上述的修改tinyhttpd便能在本地运行起来了,接下来对它的源码进行简要的分析。