Linux程序设计综合训练之简易Web服务器 1.功能需求: 2.实现的功能: 3.开发环境: 4.实现细节: 5.实现代码:在我的上传资源中有完整代码

(1)学习网络套接字编程、HTPP协议、Web服务器等知识;

(2)设计一简单Web服务器,提供静态网页浏览服务功能。

2.实现的功能:

(1)C语言实现基于socket的Web服务器
(2)使用socket通信,使用进程运行
(3)实现遍历指定目录
(4)实现对静态网页的浏览
(5)访问普通文本
(6)执行cgi程序
(7)执行shell程序
(8)浏览图片(jpg,jpeg,gif)
(9)记录日志文件

3.开发环境:

Vmware Workstation 6.4 虚拟机下,用C语言进行开发,开发工具包括:vim,gcc,gdb。

4.实现细节:

客户和服务器都是进程,服务器设立服务,然后进入循环接收和处理请求。客户连接到服务器,然后发送,接收挥着交换数据,最后退出。该交互过程中主要包含三个操作:
(1) 服务器设立服务
a) 建立服务器端socket
i. 创建一个socket
ii. 绑定地址
iii. 监听接入请求
Socket=make_soerver_socket(int protnum)
return -1 if error,
or a server socket listening at port”protnum”
(2) 客户连接到服务器(浏览器)
a) 建立到服务器的链接
i. 创建一个socket
ii. 连接到服务器
(3) 服务器和客户处理事
a) 具体的会话内容
b) 使用fork
i. 当有一个新的访问请求时便创建一个进程来完成事务。
Process_request(fd);
c) 服务器的功能
i. 列举目录信息
ii. Cat文件
iii. 运行程序
(4) web服务器协议
a) http请求:get
Telnet创建一个socket并调用connect来连接web服务器。服务器接受连接请求,并创建一个基于socket的从客户端的键盘到web服务进程的数据通道。
b) http应答:ok
服务器读取请求,检查请求,然后返回一个请求。应答有两部分:头部和内容。头部以状态起始。状态行含有两个或更多的字符串。第一个字符串是协议的版本,第二个是返回码。
结构体: 
sockaddr_in(在netinet/in.h中定义):
struct sockaddr_in {
short int sin_family;               /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;           /* Internet address */
unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};
struct hostent { 
   char *h_name;  /*地址的正式名称*/
   char **h_aliases; /* 空字节-地址的预备名称的指针*/
   int h_addrtype; /*地址类型; 通常是AF_INET*/
   int h_length; /*地址的比特长度*/
   char **h_addr_list; /* 零字节-主机网络地址指针。网络字节顺序*/
   }; 
struct in_addr {
    in_addr_t s_addr; /*结构体in_addr 用来表示一个32位的IPv4地址*/
};

5.实现代码:在我的上传资源中有完整代码

socklib.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <strings.h>
#include <netdb.h>
#include <netinet/in.h>
#include <time.h>
#include <sys/utsname.h>

#define HOSTLEN 256
#define BACKLOG 10

int make_server_socket_q(int,int);

int make_server_socket(int protnum)
{
    return make_server_socket_q(protnum,BACKLOG);
}

int make_server_socket_q(int portnum,int backlog)
{
    struct sockaddr_in saddr;
    int sock_id;
    //创建服务器socket
    sock_id=socket(PF_INET, SOCK_STREAM, 0);

    if(sock_id==-1)//失败
    {
        return -1;
    }
    bzero((void *)&saddr,sizeof(saddr));
    saddr.sin_addr.s_addr=htonl(INADDR_ANY);
    saddr.sin_port=htons(portnum);
    saddr.sin_family=AF_INET;
    //绑定
    if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr))!=0)
        return -1;
    //监听
    if(listen(sock_id,backlog)!=0)
        return -1;
    return sock_id;

}

int connect_to_server(char *host,int portnum)
{
    int sock;
    struct sockaddr_in servadd;//the number to call
    struct hostent *hp;//used to get number
    //得到一个socket
    sock = socket(PF_INET,SOCK_STREAM,0);//get a line
    if(sock==-1)
        return -1;
    //链接
    bzero(&servadd,sizeof(servadd));
    hp = gethostbyname(host);
    if(hp==NULL)
        return -1;
    bcopy( hp->h_addr,(struct sockaddr*)&servadd.sin_addr, hp->h_length);
    // servadd.sin_addr=htonl(INADDE_ANY);
    servadd.sin_port=htons(portnum);
    servadd.sin_family=AF_INET;
    if(connect(sock,(struct sockaddr*)&servadd,sizeof(servadd))!=0)
        return -1;
    return sock;
}
webserv.c

/*
    build :gcc webserv.c socklib.c -o webserv -w
    run :./webserv 8080
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

main(int ac,char*av[])
{
    //socket描述符和accept描述符
    int sock,fd;
    FILE *fpin;
    //保存请求
    char request[BUFSIZ];
    if(ac==1)
    {
        fprintf(stderr,"usage:ws portnum
");
        exit(1);
    }
    sock =make_server_socket(atoi(av[1]));//atoi方法将字符串变成整型
    if(sock==-1) exit(2);

    //创建日志文件
    createLog();

    while(1)
    {
        //该函数会阻塞等待客户端请求到达
        fd =accept(sock,NULL,NULL);
        //只读方式接收请求(文件流)
        fpin=fdopen(fd,"r");
        //得到请求
        fgets(request,BUFSIZ,fpin);
        //打印到控制台请求记录
        printf("got a call :request = %s",request);
        //记录日志文件
        writeLog(request);
        read_til_crnl(fpin);
        //处理请求
        process_rq(request,fd);
        //结束本次请求
        fclose(fpin);
    }
}


//创建日志文件
createLog()
{
    if((access("./log",F_OK))!=-1)
    {
        //日志文件存在则清空内容
        int ret = open("./log", O_WRONLY | O_TRUNC);
        if(ret == -1)
        {
            printf("打开日志文件失败!
");
            return;
        }
        close(ret);
    }
    else
    {
        //日志文件不存在则创建
        system("touch ./log");
        system("chmod 744 ./log");
    }
}


//记录日志文件
writeLog(char *request)
{
    char temp[225]="got a call : request = ";
    strcat(temp,request);
    int logfd;
    //打开文件
    if((logfd=open("./log",O_RDWR|O_APPEND,0644))<0)
    {
        perror("打开日志文件出错!");
        exit(1);
    }
    //获取当前时间
    int z;
    struct tm *t;
    time_t tt;
    time(&tt);
    t = localtime(&tt);
    char time[18];
    sprintf(time,"%4d年%02d月%02d日 %02d:%02d:%02d
", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
    z=write(logfd,time,150);
    if(z<0)
    {
        perror("写入日志文件出错!");
        exit(1);
    }
    //关闭文件
    if(close(logfd))
    {
        perror("关闭日志文件出错!");
        exit(1);
    }

}


//读取完整的请求
read_til_crnl(FILE*fp)
{
    char buf[BUFSIZ];
    while(fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"
")!=0);
}


//处理请求
process_rq(char *rq, int fd)
{
    char cmd[BUFSIZ],arg[BUFSIZ];
    //创建子进程,如果不是子进程则结束
    if (fork()!=0)
        return;
    strcpy(arg,"./");
    if (sscanf(rq,"%s%s",cmd,arg+2)!=2)
        return;
    if(strcmp(cmd,"GET")!=0)//只能处理静态网页get方式
        cannot_do(fd);
    else if (not_exist(arg))//请求出错
        do_404(arg,fd);
    else if (isadir(arg))//判断是否为目录
        do_ls(arg,fd);
    else if (ends_in_cgi(arg))//是否为cgi程序
        do_exec(arg,fd);
    else if (ends_in_sh(arg))//是否为sh程序
        do_exec_sh(arg,fd);
    else
        do_cat(arg,fd);
}


//获取头部信息
header(FILE *fp,char*content_type)
{
    fprintf(fp,"HTTP/1.0 200 OK
");
    if(content_type)
        fprintf(fp,"Content-type: %s
",content_type);
}

//请求501错误
cannot_do(int fd)
{
    FILE *fp =fdopen(fd,"w");

	fprintf(fp,"HTTP/1.0 501 Not Implemented
");
    fprintf(fp,"Content-type:text/plain
");
    fprintf(fp,"
");
    fprintf(fp,"Sorry,HTTP 501!

无法处理请求!");
    fclose(fp);

}


//请求出错404
do_404(char *item,int fd)
{
    FILE *fp=fdopen(fd,"w");

    fprintf(fp,"HTTP/1.0 404 Not Found
");
    fprintf(fp,"Content-type:text/plain
");
    fprintf(fp,"
");
    fprintf(fp,"Sorry,HTTP 404!

The item you requested: %s 
is not found
",item);
    fclose(fp);
}


//判断是否为目录
isadir(char*f)
{
    struct stat info;
    return (stat(f,&info)!=-1&&S_ISDIR(info.st_mode));

}


//不存在
not_exist(char *f)
{
    struct stat info;
    return (stat(f,&info)==-1);
}


//显示目录下内容
do_ls(char*dir,int fd)
{
    FILE *fp;

    fp = fdopen(fd,"w");
    header(fp,"text/plain;charset=UTF-8");
    fprintf(fp,"
");
    fflush(fp);

    dup2(fd,1);
    dup2(fd,2);
    close(fd);
    execlp("ls","ls","-l",dir,NULL);
    perror(dir);
    exit(1);
}


//返回文件类型
char* file_type(char *f)//return 'extension' of file
{
    char *cp;
    if((cp=strrchr(f,'.'))!=NULL)
        return cp+1;
    return " ";
}

//cgi类型文件
ends_in_cgi(char *f)
{
    return(strcmp(file_type(f),"cgi")==0);
}



//执行shell程序
ends_in_sh(char *f)
{
    return(strcmp(file_type(f),"sh")==0);
}

do_exec_sh(char *prog,int fd)
{
    system(prog);
}//shell


//执行可执行程序cgi
do_exec(char *prog,int fd)
{
    FILE *fp;
    fp =fdopen(fd,"w");
    header(fp,NULL);
    fflush(fp);
    
    dup2(fd,1);
    dup2(fd,2);
    close(fd);
    execl(prog,prog,NULL);
    perror(prog);
    
}


//显示当前目录下全部文件或目录
do_cat(char*f,int fd)
{
    char *extension=file_type(f);
    char *content="text/html";
    FILE *fpsock,*fpfile;
    int c;

    if(strcmp(extension,"html")==0)
        content="text/html";
    else if (strcmp(extension,"gif")==0)
        content="image/gif";
    else if(strcmp(extension,"jpg")==0)
        content="image/jpeg";
    else if(strcmp(extension,"jpeg")==0)
        content="image/jpeg";

    fpsock = fdopen(fd,"w");
    fpfile = fopen(f,"r");
    if(fpsock!=NULL&&fpfile!=NULL)
    {
        header(fpsock,content);
        fprintf(fpsock,"
");
        while((c=getc(fpfile))!=EOF)
            putc(c,fpsock);
        fclose(fpfile);
        fclose(fpsock);
    }
    exit(0);
}