简单的Web服务器程序实现(server--server code, browser--client)

x86/debian GNU Linux/gcc


1. 题目

实现一个简单的Web服务器myhttpd。服务器程序启动时要读取配置文件/etc/myhttpd.conf,其中需

要指定服务器监听的端口号和服务目录,例如:

Port=8000

Directory=/var/www

注意,1024以下的端口号需要超级用户才能开启服务。如果你的系统中已经安装了某种Web服务器(例如Apache),应该为myhttpd选择一个不同的端口号。当浏览器向服务器请求文件时,服务器就从服务目录(例如/var/www)中找出这个文件,加上HTTP协议头一起发给浏览器。就从服务目录(例如/var/www)中找出这个文件,加上HTTP协议头一起发给浏览器。


但是,如果浏览器请求的文件是可执行的则称为CGI程序,服务器并不是将这个文件发给浏览器,而是在服务器端执行这个程序,将它的标准输出发给浏览器,服务器不发送完整的HTTP协议头,CGI程序自己负责输出一部分HTTP协议头。(Code has not include this part)


2. 总结

This code below is a server, browser is like a client(see基于TCP/IP的简单的聊天程序). If client know server's “ip address”and “port number”, then client can connect to the server.


The browser send information below when first connect to server, we can call read() function to receive this information. When server call write() function send information to client, server must include “HTTP Head” that browser can Distinguish server's information's type. After receive server's information, browser automatic analysis server's information then handle it(such as show it on browser).


3. Ready

Create myhttp.conf file:

vi /etc/myhttp.conf

Then write below content to myhttp.conffile:

Port=8000

Directory=/var/www


Create index.html file:

vi /var/www/index.html

Then write below content to index.html:

<html><body><h1>It works!</h1>

<p>This is the default web page for this server.</p>

<p>Hefei, liyue, dengbo, caibosong, yangtao, lxr in lab1012 of 25</p>

</body></html>


4. code

/*Filename:     myhttp.c
 *Brife:        As a web server, response browse's text and CGI reqest
 *Author:       One fish
 *Date:         2014.9.11 Thu
 *Last modifed: 2014.9.12-17:28 Fri
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>


//---------------------------------------------------------Macro define-------------------
#define MAXLINE         80
#define BACKBLOG        10
#define HTTP_SIZE       800
#define HTTP_FILENAME   "myhttp.txt"            //The file save browser
#define HTML_FILENAME   "index.html"            //The file send to browser when browser connect to server

#define CONF_FILENAME   "/etc/myhttp.conf"      //Web dirctory information
#define HTTP_OK_HEAD    "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n"
//---------------------------------------------------------Macro define-------------------


//----------------------Global varible----------------------------------------------------
typedef struct listen{
        int     port;
        char    dir[20];
}myweb;
//----------------------Global varible----------------------------------------------------


//--------------------------------------Function declaration-----------------------------
void    perr_exit(const char *s);
int     socket_init(int ser_port);
void    handle_close(int socktfd);
myweb   read_web_conf(const char *confile);
int     read_http_client(int connfd, const char *filename);
int     write_http_client(int connfd, const char *htmlname);
//--------------------------------------Function declaration-----------------------------



int main(void)
{
        int     connfd;
        char    wbuf[MAXLINE];
        myweb   mywebads;


        //Read web port number and web dirctory from "/etc/myhttp.conf"
        mywebads        = read_web_conf(CONF_FILENAME);


        //Create socket, bind to special ip adress and port number
        connfd  = socket_init(mywebads.port);


        //After browse connect server,
        //then read information into HTTP_FILENAME from browse
        read_http_client(connfd, HTTP_FILENAME);


        //Get the web dirctory and index.html
        memset(wbuf, 0, MAXLINE);
         strcpy(wbuf, mywebads.dir);
        strcat(wbuf, "/");
        strcat(wbuf, HTML_FILENAME);
        //Response to browse
        write_http_client(connfd, wbuf);


        //Close server
        handle_close(connfd);

        return 0;
}



//---------------------------------------------------Function define--------------------------------------------
//Read port number and web-dirctory from "/etc/myhttp.conf"
myweb read_web_conf(const char *confile)
{
        FILE    *fp;
        char    *pstr;
        char    buf[MAXLINE];
        myweb   webaddress;

        if ( NULL == (fp = fopen(confile, "r") ) ) {
                perr_exit("fopen confile");
        }

        memset(buf, 0, MAXLINE);
        //Read port number
        if (NULL == fgets(buf, MAXLINE - 1, fp)) {
                perr_exit("fgets error");
        }
        pstr    = strchr(buf, '=');
        pstr++;
        webaddress.port = atoi(pstr);
       memset(buf, 0, MAXLINE);
        //Read wen dirctory
        if (NULL == fgets(buf, MAXLINE - 1, fp)) {
                perr_exit("fgets error");
        }
        pstr    = strchr(buf, '=');
        pstr++;
        strcpy(webaddress.dir, pstr);

        //Clear end '\n' 
        pstr    = strchr(webaddress.dir, '\n');
        *pstr   = '\0';

        return webaddress;
}

//Create server's socket and handle the error
int create_socket(int family, int type, int protocol)
{
        int scktfd;
        if ( (scktfd = socket(family, type, protocol)) < 0 )
                perr_exit("socket error");
        return scktfd;
}

//Bind socket description to port(ip + port)
void bind_server_socket(int scktfd, const struct sockaddr *sa, socklen_t salen)
{
        if ( bind(scktfd, sa, salen) < 0)
                perr_exit("bind error");
}

 
//Set server's listen number
void set_listen(int scktfd, int backlog)
{
        if (listen(scktfd, backlog) < 0)
                perr_exit("listen error");
}

//Server accept client connect function
int handle_accept(int scktfd, struct sockaddr *sa, socklen_t *salenptr)
{
        int clifd;
again:
        if ( ( clifd = accept(scktfd, sa, salenptr) ) < 0) {
                if ( (errno == ECONNABORTED) || (errno == EINTR) ) {
                        goto again;
                } else {
                        perr_exit("accept error");
                }

                return clifd;
        }
}

//Create one socket and bind it to special address and port
int socket_init(int ser_port)
{
        int                     socktfd;
        int                     connfd;
        int                     cliaddr_len;
        char                    str[INET_ADDRSTRLEN];
        struct sockaddr_in      servaddr;
        struct sockaddr_in      cliaddr;

        connfd  = -1;
        socktfd = -1;

        //Create a socket and return its description
        socktfd = create_socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family             = AF_INET;
        servaddr.sin_addr.s_addr        = htonl(INADDR_ANY);
        servaddr.sin_port               = htons(ser_port);
        //Bind socktfd to any ip address and a special port
        bind_server_socket(socktfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

        //Set server's listen number
        set_listen(socktfd, BACKBLOG);

        cliaddr_len     = sizeof(cliaddr);

        printf("Accepting browse visting...\n");

        //Wait clients connect
        connfd  = handle_accept(socktfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

        //Print client's information
        printf("received from %s at port %d\n",
                inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                ntohs(cliaddr.sin_port));

        return connfd;
}

//Read form browse via read()
ssize_t read_http_infor(int fd, void *ptr, size_t nbytes)
{
        ssize_t rv;
again:
        if ( -1 == (rv = read(fd, ptr, nbytes)) ) {
                if (errno == EINTR) {
                        goto again;
                } else {
                        return -1;
                }

                return rv;
        }
}

//Write http information via write()
ssize_t write_http_infor(int fd, const void *ptr, size_t nbytes)
{
        ssize_t rv;
again:
        if ( -1 == (rv = write(fd, ptr, nbytes)) ) {
                if (errno == EINTR) {
                        goto again;
                } else {
                        return -1;
                }
        }
        return rv;
}

//Handle close socket file description error
void handle_close(int scktfd)
{
        if (-1 == close(scktfd))
                perr_exit("close error");
}

//Error handle function
void perr_exit(const char *s)
{
        perror(s);
        exit(1);
}

//Recive information client(browse) send, save them into file
int read_http_client(int connfd, const char *filename)
{
        int     rv;
        FILE    *fp;
        char    buf[HTTP_SIZE];

        rv      = -1;
        fp      = NULL;

        if ( NULL == (fp = fopen(filename, "w+") ) ) {
                perr_exit("fopen file");
        }
        
        if ( -1 == (rv = read_http_infor(connfd, buf, HTTP_SIZE)) ) {
                perr_exit("read browse");
        }

        if ( 0 > (rv = fprintf(fp, "%s\n", buf)) ) {
                perr_exit("fprintf output");
        }

        return rv;
}

//Response to browse via write() according to http information which browse send
int write_http_client(int connfd, const char *htmlname)
{
        int     rv;
        char    lbuf[MAXLINE];
        char    wbuf[800];
        FILE    *fp;

        if ( NULL == (fp = fopen(htmlname, "r")) ) {
                perr_exit("fopen file");
        }

        memset(wbuf, 0, sizeof(wbuf));
        //HTTP protocol head which used to know the information's type by browse
        strncpy(wbuf, HTTP_OK_HEAD, strlen(HTTP_OK_HEAD));

        //The information's content
        while ( NULL != fgets(lbuf, MAXLINE - 1, fp) ) {
                strncat(wbuf, lbuf, strlen(lbuf));
        }

        //Write information to browse
        write_http_infor(connfd, wbuf, sizeof(wbuf));

        fclose(fp);

        return 0;
}
//---------------------------------------------------Function define--------------------------------------------

5. run

compile and run this code:

gcc myhttp.c -o mythhp

./myhttp

Accepting browse visting...



打开浏览器,输入服务器IP(ifconfig)and port number:192.168.1.168:8000. At this time, server's status:

root@debian:/home/lly/mydir/lly_books/linux_c_programming_osl/socket#./myhttp

Accepting browse visting...

received from 192.168.1.118 at port 41377

root@debian:/home/lly/mydir/lly_books/linux_c_programming_osl/socket#


browser send information to server(savein myhttp.txt):

vi myhttp.txt

This content save in myhttp.txt by server code: int read_http_client(int connfd, const char *filename)


其中每一行的末尾都是回车加换行(C语言的"\r\n"),第一行是GET请求和协议版本,其余几行选项字段我们不讨论,HTTP协议头的最后有一个空行,也是回车加换行。


我们实现的Web服务器只要能正确解析第一行就行了,这是一个GET请求,请求的是服务目录的根目录/(在本例中实际上是/var/www),Web服务器应该把该目录下的索引页(默认是index.html)发给浏览器,也就是把/var/www/index.html发给浏览器。

The interface on browser is:


This text send by server with this code:int write_http_client(int connfd, const char *htmlname)


Server must add http-head before thistext, then send to browser. One of the http-head is:HTTP/1.1 200OK\r\nContent-Type:text/html\r\n\r\n".服务器应答的HTTP头也是每行末尾以回车加换行结束,最后跟一个空行的回车加换行。


HTTP头的第一行是协议版本和应答码,200表示成功,后面的消息OK其实可以随意写,浏览器是不关心的,主要是为了调试时给开发人员看的。虽然网络协议最终是程序与程序之间的对话,但是在开发过程中却是人与程序之间的对话,一个设计透明的网络协议可以提供很多直观的信息给开发人员,因此,很多应用层网络协议,HTTPFTPSMTPPOP3等都是基于文本的协议,为的是透明性(transparency)


The text on the browser was server send just now. Server send this information from /var/www/index.html,index.html's content is:

vi /var/www/index.html

Server must add http-head before thistext, then send to browser. One of the http-head is:HTTP/1.1 200OK\r\nContent-Type:text/html\r\n\r\n".服务器应答的HTTP头也是每行末尾以回车加换行结束,最后跟一个空行的回车加换行。


HTTP头的第一行是协议版本和应答码,200表示成功,后面的消息OK其实可以随意写,浏览器是不关心的,主要是为了调试时给开发人员看的。虽然网络协议最终是程序与程序之间的对话,但是在开发过程中却是人与程序之间的对话,一个设计透明的网络协议可以提供很多直观的信息给开发人员,因此,很多应用层网络协议,HTTPFTPSMTPPOP3等都是基于文本的协议,为的是透明性(transparency)

HTTP头的第二行表示即将发送的文件的类型(称为MIME类型),这里是text/html,纯文本文件text/plain,图片则是image/jpgimage/png等。


然后就发送文件的内容,发送完毕之后主动关闭连接,这样浏览器就知道文件发送完了。这一点比较特殊:通常网络通信都是客户端主动发起连接,主动发起请求,主动关闭连接,服务器只是被动地处理各种情况,HTTP协议规定服务器主动关闭连接。


This is a practice of communication ofserver and browser. CGI and multi-process situation not included inthis code. If server need handle more complicated situation, serverapplication can follow below steps:

1.解析浏览器的请求,在服务目录中查找相应的文件,如果找不到该文件就返回404错误页面

2. 如果找到了浏览器请求的文件,stat(2)检查它是否可执行

3. 如果该文件可执行:

        a. 发送HTTP/1.1200 OK给客户端

      b.fork(2),然后用dup2(2)重定向子进程的标准输出到客户端socket

      c.在子进程中exec(3)CGI程序

      d.关闭连接

4. 如果该文件不可执行:

      a.发送HTTP/1.1200 OK给客户端

      b.如果是一个图片文件,根据图片的扩展名发送相应的Content-Type给客户端

      c.如果不是图片文件,这里我们简化处理,都当作Content-Type:text/html

      d.简单的HTTP协议头有这两行就足够了,再发一个空行表示结束

      e.读取文件的内容发送到客户端

      f.关闭连接


[2014.9.12--20:06]

LCNote Over.

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值