第12章 连接和协议:编写web服务器

 服务器/客户系统的三个步骤:
(1):服务器设立服务
(2):客户连接到服务器
(3):服务器与客户处理事务
 步骤一与步骤二加起来就是建立起服务器与客户的连接

步骤1:建立服务器端socket
过程比较固定,将三个步骤合成一个函数:make_server_socket

//返回sock,可以用fdopen进行读写
sock=make_server_socket(int portnum)	

在这里插入图片描述
步骤2:建立到服务器的连接
 将两个操作合成一个函数:connect_to_server

//返回对应的文件描述符,可以用read和write读写
fd=connect_to_server(houstname,portnum)

在这里插入图片描述

步骤3:客户/服务器的会话
●一般的服务器端
 调用process_request函数来处理请求
在这里插入图片描述
●一般的客户端
 调用talk_with_server来与服务器通信
在这里插入图片描述
●服务器处理请求:process_request函数
 有两种处理请求的方法:DIY或者代理
(1)DIY:自己做,服务器接收请求,自己处理工作
 自己做用于快速简单的任务,计算当前的日期和时间需要系统调用time和库函数ctime,而使用代理的话需要fork和exec来运行date,至少需要三个系统调用核创建一个新的进程。所以此时效率最高的方法就是服务器自己来完成工作并且在listen中限制队列的大小。

(2)代理:服务器接收请求,然后创建一个新进程来处理工作
 处理耗时的任务或等待资源时,需要代理来完成工作,服务器可以使用fork创建新进程来处理每个请求。通过这种方式,服务器可以同时处理多个任务。

 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程。它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态,当父进程查询完后,子进程全部删除。要注意使用SIGCHLD调用wait来阻止僵尸进程问题,但是当多个子进程同时退出时,要使用waitpid函数,wait是waitpid的简单版本,调用wait(&status)相当于调用waitpid(-1,&status,0)

编写web服务器

●具备的用户操作:
(1)列举目录信息
(2)cat文件
(3)运行程序

●web服务器协议
(1)客户发送请求
GET filename HTTP/version
(2)服务器发送应答
HTTP/version status-code status-message

/* socklib.c
 * This file contains functions used lots when writing internet Client/Server programs. The two main function here are:
 * -------int make_server_socket(portnum) return a server socket or -1 if error 
 * -------int make_server_socket_q(portnum,backlog) 
 * -------int connect_to_server(char * hostname,int portnum) return a connected socket or -1 if error
 */
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netdb.h>
 #include <time.h>
 #include <strings.h>

 #define HOSTLEN 256
 #define BACKLOG 1

 int make_server_socket_q(int,int);

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

 int make_server_socket_q(int portnum,int backlog)  //返回sock_id
 {
     struct sockaddr_in saddr;        /* build our address here */
     struct hostent * hp;            /* this is part of our */
     char hostname[HOSTLEN];            /* address */
     int sock_id;                    /* the socket */

     sock_id = socket(PF_INET,SOCK_STREAM,0);    /* get a socket */
     if(sock_id == -1)
         return -1;

     /* build address andbind it to socket */
     bzero((void *)&saddr,sizeof(saddr));    /* clear out struct */
     gethostname(hostname,HOSTLEN);            /* where am I ? */
     hp = gethostbyname(hostname);            /* get infoabout host fill in host part */

     bcopy((void *)hp -> h_addr,(void *)&saddr.sin_addr,hp->h_length);
     saddr.sin_port = htons(portnum);        /* fill in socket port */
     saddr.sin_family = AF_INET;                /* fill in addr family */
     if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)) != 0)
         return -1;

     /* arrange for incoming calls */
     if(listen(sock_id,backlog) != 0)
         return -1;
     return sock_id;
 }

 int connect_to_server(char * host,int portnum)     //返回sock
 {
     int sock;
     struct sockaddr_in servadd;            /* the number to call */
     struct hostent    *hp;                /* used to get number */

     /* step 1 : GEt a socket */
     sock = socket(AF_INET,SOCK_STREAM,0);
     if(sock == -1)
         return -1;
     /* Step 2: connect to server */
     bzero(&servadd,sizeof(servadd));    /* zero the address */
     hp = gethostbyname(host);            /* lookup host‘s ip # */
     if(hp == NULL)
         return -1;
     bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
     servadd.sin_port = htons(portnum);    /* fill in port number */
     servadd.sin_family = AF_INET;        /* fill in socket type */

     if(connect(sock,(struct sockaddr *)&servadd,sizeof(servadd)) != 0)
         return -1;
     return sock;
 }

/* webserv.c-一个最小服务器
 * 只支持GET
 * 在当前目录运行
 * fork子进程来解决每个请求
 * 有很多安全漏洞
 */ 
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>


int make_server_socket(int);
int make_server_socket_q(int ,int);
int connect_to_server(char*,int);
void read_til_crnl(FILE *);
void process_rq(char*,int);
void cannot_do(int fd);
void do_404(char *item,int fd);
int isadir(char *f);
int not_exist(char *f);
void do_ls(char * dir,int fd);
char * file_type(char *f);
int ends_in_cgi(char *f);
void do_exec(char * prog,int fd);
void do_cat(char *f,int fd);

int main(int ac,char *av[])
{
    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]));
    if(sock==-1)    exit(2);
    /*主循环*/
    while(1){
        /*接收呼叫*/
        fd=accept(sock,NULL,NULL);
        fpin=fdopen(fd,"r");

        /*读请求*/
        fgets(request,BUFSIZ,fpin);
        printf("got a call,request=%s",request);
        read_til_crnl(fpin);

        /*处理请求*/
        process_rq(request,fd);

        fclose(fpin);
    }
}
/*
 *一直读直到\r\n
 */ 
void read_til_crnl(FILE *fp)
{
    char buf[BUFSIZ];
    while(fgets(buf,BUFSIZ,fp)!=NULL&&strcmp(buf,"\r\n")!=0);
}

/*
 *处理请求并回复
 *在新的进程中处理请求
 *rq是一个HTTP command rq=GET filename HTTP/version
 */
void process_rq(char *rq,int fd)
{
    char cmd[BUFSIZ],arg[BUFSIZ];
    if(fork()!=0)
        return;
    strcpy(arg,"./");   //arg中加入./
    if(sscanf(rq,"%s%s",cmd,arg+2)!=2)  //将GET放到cmd中,filename放到./后
        return ;
    if(strcmp(cmd,"GET")!=0)    //如果cmd不等于GET,不能处理
        cannot_do(fd);
    else if(not_exist(arg)) //如果是get请求但找不到arg,404
        do_404(arg,fd);
    else if(isadir(arg)) //如果arg是目录,执行ls并回复
        do_ls(arg,fd);
    else if(ends_in_cgi(arg))   //如果后缀是cgi,执行
        do_exec(arg,fd);
    else 
        do_cat(arg,fd);     //如果不是目录也不是cgi,显示内容
    
}

/*
 *所有回复共同的头部
 *content_type可以为空
 */ 
void header(FILE *fp,char *content_type)
{
    fprintf(fp,"HTTP/1.0 200 OK\r\n");
    if(content_type)
        fprintf(fp,"Content-type:%s\r\n",content_type);
}

/*
 *无法执行的命令
 */
void cannot_do(int fd)
{
    FILE * fp = fdopen(fd,"w");
    fprintf(fp,"HTTP/1.0 501 Not Implemented\r\n");
    fprintf(fp,"Content_type:text/plain\r\n");
    fprintf(fp,"\r\n");

    fprintf(fp,"That command is not yet Implemented");
    fclose(fp);
}

/*
 *找不到文件
 */ 
void do_404(char *item,int fd)
{
    FILE * fp = fdopen(fd,"w");
    fprintf(fp,"HTTP/1.0 404 Not Found\r\n");
    fprintf(fp,"Content_type:text/plain\r\n");
    fprintf(fp,"\r\n");

    fprintf(fp,"The item you requested:%s\r\n is not found \r\n",item);
    fclose(fp);
}
/*
 *调用stat查看是不是目录
 */ 
int isadir(char *f)
{
    struct stat info;
    return (stat(f,&info)!=-1&&S_ISDIR(info.st_mode));
}

/*
 *调用stat看文件存不存在
 */ 
int not_exist(char *f)
{
    struct stat info;
    return (stat(f,&info)==-1);
}

void do_ls(char * dir,int fd)
{
    FILE * fp;
    fp = fdopen(fd,"w");
    fprintf(fp,"text/plain\r\n");
    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)
/* returns 'extension' of file */
{
    char * cp;
    if((cp = strrchr(f,'.')) != NULL)
        return cp+1;
    return "";
}

/*
 *利用file_type看扩展名是否为cgi
 */ 
int ends_in_cgi(char *f)    
{
    return (strcmp(file_type(f),"cgi") == 0);
}

/*
 *执行cgi文件
 */ 
void 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);
}

/*
 * 显示不同的文件
*/
void do_cat(char *f,int fd)
{
    char * extension = file_type(f);
    char * content = "text/plain";  //文件类型
    FILE * fpsock, * fpfile;
    int c;

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

    fpsock = fdopen(fd,"w");	
    fpfile = fopen(f,"r");
    if(fpsock != NULL && fpfile != NULL)
    {
        header(fpsock,content); //加入头部即HTTP应答
        fprintf(fpsock,"\r\n");
        while((c = getc(fpfile)) != EOF)
            putc(c,fpsock);
        fclose(fpfile);
        fclose(fpsock);
    }
    exit(0);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值