• Linux程序设计综合训练之简易Web服务器


    1.功能需求:

    (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);
    }
    




  • 相关阅读:
    连接数据库及出现System.AccessViolationException错误的解决方法
    WCF REST 工作总结
    jquery easyui 扩展验证
    正则表达式语法
    SQL select语句执行顺序
    添加头文件afxwin.h后引起异常的解决办法
    imagej基本操作
    医学图像处理(一)
    灰度图像的自动阈值分割(Otsu 法)
    关于glog使用中遇到的问题
  • 原文地址:https://www.cnblogs.com/jasonhaven/p/7355049.html
Copyright © 2020-2023  润新知