ubuntu下搭建简易web服务器(二)

ubuntu下搭建简易web服务器(二)

前言

ubuntu下搭建简易web服务器(一)中已经简述了在本机上搭建时间服务器。本文将就搭建局域网内可访问的Web服务器进行简述。

绑定局域网IP

在绑定本地主机IP的时候,我们使用的是gethostbyname函数返回一个包含主机的地址和端口信息的struct hostend类型结构体。那如何获取局域网的IP呢,可以先使用ifconfig查看下当前网络下的ip,并记下ip前面的名字,比如我的是“wls6s0”
函数getifaddrs用于获取本地网络接口信息,将至存储于链表中,在man手册中有具体介绍,并且还提供了例程,这里不过多介绍,直接贴代码获取网络ip。

int make_server_socket(int portname,int queue)
{
        int sock_id;
        struct sockaddr_in saddr;
        struct hostent *hp;
        char hostname[100];
        char **pptr;
        char str[32];
        struct ifaddrs *ifaddr,*ifa;
        int family,n;
        char host[NI_MAXHOST];

        if((getifaddrs(&ifaddr))==-1) //获取信息链表
        {
        //      oops("getifaddrs");
                return -1;
        }

        for(ifa=ifaddr,n=0;ifa!=NULL;ifa=ifa->ifa_next,n++)
        {//循环链表,找出你之前记的ip名字(“wlp6s0”),找到就退出循环
                family=ifa->ifa_addr->sa_family;
                if((family==2) && (strcmp(ifa->ifa_name,"wlp6s0")==0))
                        break;
        }
        if(ifa->ifa_addr==NULL)
        {
                fprintf(stderr,"ifa_addr NULL");
                return -1;
        }

        family=ifa->ifa_addr->sa_family;
        //printf("af_INET is %d\n",AF_INET);
        //printf("family :%d\n",family);
        if(family==AF_INET||family==AF_INET6)
        {
                if((getnameinfo(ifa->ifa_addr,(family==AF_INET)?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6),host,NI_MAXHOST,NULL,0,NI_NUMERICHOST))!=0)
                {
        //              oops("getnameinfo");
                        return -1;
                }
        }
        printf("address %s\n",host);

        if((sock_id=socket(PF_INET,SOCK_STREAM,0))==-1)
        {
        //      oops("socket");
                return -1;
        }
        if((gethostname(hostname,100))==-1)
        {
        //      oops("gethostname");
                return -1;
        }
        bzero((void *)&saddr,sizeof(saddr));
        hp=gethostbyname(hostname);
        //bcopy((void *)hp->h_addr,(void *)&saddr.sin_addr,hp->h_length);
        inet_aton(host,(struct in_addr *)&saddr.sin_addr.s_addr);
        saddr.sin_port=htons(portname);
        saddr.sin_family=AF_INET;
        if((bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)))==-1)
        {
        //      oops("bind");
                return -1;
        }
        pptr=hp->h_addr_list;
        for(;*pptr!=NULL;pptr++)
        {
                printf("addr is %s\n",inet_ntop(hp->h_addrtype,*pptr,str,sizeof(str)));
        }
        printf("first address:%s\n",inet_ntop(hp->h_addrtype,hp->h_addr,str,sizeof(str)));

        if((listen(sock_id,queue))==-1)
        {
                oops("listen");
                return -1;
        }
        return sock_id;
}

Web服务器协议

客户端(浏览器)与web服务器之间的交互主要包含了客户的请求和服务器的应答。请求和应答的格式在超文本传输协议(HTTP)中有定义。可以使用telnet和web服务器进行交互。在这里对HTTP协议不做更多详细的介绍,主要是了解发送请求与应答的格式。可以通过简单实验进行验证。
在终端输入telnet www.baidu.com 80,输入请求命令GET /index.html HTTP/1.0,再输入两次回车。即可接收到来自web服务器的应答响应,HTTP/1.1 200 OK.还有后续一大段信息。客户端和web服务器交互的基本结构如下:
(1)客户端发送请求:
GET filename HTTP/version
可选参数
空行
(2)服务器发送应答
HTTP/version status-code status-message
附加信息
空行
内容
web服务器必须接收客户的HTTP请求,并发送HTTP应答。
程序结构如下:

 while(1)
 {
 		接收连接;
 		获取HTTP请求;
 		获取空行;
 		请求处理;
 		关闭连接
 }

其中“接受连接”就是调用accept去建立连接生出文件描述符。“获取HTTP请求”分为两部分,第一部分检查接收到的字符串是否符合http请求格式,第二部分是额外获取一个换行。“请求处理”通过fork子进程进行处理相关请求操作,在本文的操作是ls目录,查看图片等。“关闭连接”即结束此次通话。

直接上代码,注释已经比较详细,就不单独描述了:

#include "socklib.h"
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#include<signal.h>

#define isfile(m) ((m & S_IFMT)==S_IFREG)
#define isdir(m) ((m & S_IFMT)==S_IFDIR)


void header(FILE *fp,char *str) //f返回给客户端成功信息——信息头
{

        fprintf(fp,"HTTP/1.0 200 OK\r\n");
        if(str)
                fprintf(fp,"Contenit-type: %s\r\n",str);

}


void do_ls(char *path,int fd)//执行ls命令,其中fd表示接入服务器的客户端的文件描述符,由accept创建
{
        FILE *fp=fdopen(fd,"w");
        header(fp,"text/plaint");//发送信息头
        fprintf(fp,"\r\n");//空行
        fflush(fp);//立即打印

        dup2(fd,1);//复制fd到标准输出上
        dup2(fd,2);//复制fd到标准错误上
        close(fd);//关闭fd

        execlp("ls","ls","-l",path,NULL);//执行ls -l 输出到标准输出上,也就是fd上,及发送给客户端
        perror(path);
        exit(1);
}

void do_cat(char *path,int fd) //查看文件
{
        char *extension=strrchr(path,'.')+1;//函数strrchr为分割函数,即讲path按“.”分割,返回最后一次出现"."位置的指针,以获取文件后缀
        char *content="text/plain";
        FILE *file_fp=fopen(path,"r");//将该文件准换成流形式
        FILE *sock_fp=fdopen(fd,"w");//讲客户端文件描述符转换成流打开
        int c;
//判断文件后缀
        if(strcmp(extension,"jpeg")==0)
                content="iamge/ jpeg";
        else if(strcmp(extension,"jpg")==0)
                content="image/ jpeg";
        else if(strcmp(extension,"html")==0)
                content="text/html";
        else if(strcmp(extension,"gif")==0)
                content="image/gif";

        header(sock_fp,content);
        fprintf(sock_fp,"\r\n");

        while((c=getc(file_fp))!=EOF)//获取文件内容,并通过客户端文件流发送到客户端
                putc(c,sock_fp);

        fclose(file_fp);
        fclose(sock_fp);
        close(fd);
        exit(0);
}
//执行文件
void do_exec(char *path,int fd)
{
        FILE *fp=fdopen(fd,"w");
        header(fp,NULL);
        fflush(fp);

        dup2(fd,1);
        dup2(fd,2);
        close(fd);
        execlp(path,path,NULL);
        perror(path);
//      exit(1);
}

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\r\n");
        fclose(fp);
//      exit(1);
}

void no_exit(int fd)
{
        FILE *fp=fdopen(fd,"w");

        fprintf(fp,"HTTP/1.0 404 Not found\r\n");
        fprintf(fp,"Conten-type:text/plain\r\n");
        fprintf(fp,"\r\n");

        fprintf(fp,"that command is not yet implemented\r\n");
        fclose(fp);
//      exit(1);
}
//判断cgi文件
int ends_in_cgi(char *pathname)
{
        char *p=strrchr(pathname,'.')+1;
        return (strcmp(p,"cgi")==0);
}

int process_request(char *request,int fd)
{
        char cmd[10],pathname[BUFSIZ],prot[100];
        char *info;
        struct stat statbuf;
        if(fork()!=0)
                return 0;

        strcpy(pathname,"./");//限制为当前文件夹
        sscanf(request,"%9s%100s%99s",cmd,pathname+2,prot);//获取命令,文件名,端口号信息
//      printf("cmd is:%s,prot is:%s pathname is:%s\n",cmd,prot,pathname);

        if(strcmp(cmd,"GET")!=0)//判断GET命令
                cannot_do(fd);
        if(access(pathname,F_OK)!=0)//判断文件是否存在
                no_exit(fd);
        stat(pathname,&statbuf);
        if(isdir(statbuf.st_mode))//判断文件类型,判断是否为目录文件
                do_ls(pathname,fd);
        else if(ends_in_cgi(pathname))
                do_exec(pathname,fd);
        else
                do_cat(pathname,fd);

}

void read_untill_crnl(FILE *fp)
{
        char buf[BUFSIZ];
        while((fgets(buf,BUFSIZ,fp)!=NULL) && (strcmp(buf,"\r\n")));
}

void child_waiter(int signum)//若有多个客户端申请,集体回收子进程
{
        while(waitpid(-1,NULL,WNOHANG)>0);
}

int main(int ac,char *av[])//命令行参数为端口号和等待队列长度
{
        int sock_server,sock_client,port,queue;
        FILE *fp;
        struct sockaddr_in saddr;
        int len;
        char request[BUFSIZ];

        printf("begin\n");
        signal(SIGCHLD,child_waiter);

        if(ac!=3)
        {
                fprintf(stderr,"usage:port queue\n");
                exit(1);
        }

        port=atoi(av[1]);
        queue=atoi(av[2]);

        if((sock_server=make_server_socket(port,queue))==-1)//创建服务器
        {
                printf("sock_server error\n");
                exit(1);
        }
        while(1)
        {
                printf("wait for connect\n");
                if((sock_client=accept(sock_server,(struct sockaddr *)&saddr,&len))==-1)//接收客户端连接
                        break;
                printf("connected\n");
                printf("get a connect:%d %d\n",saddr.sin_addr.s_addr,saddr.sin_port);
                fp=fdopen(sock_client,"r");
                fgets(request,BUFSIZ,fp);//获取请求
                printf("get request is %s\n",request);
                 read_untill_crnl(fp);   //获取空行
                process_request(request,sock_client);//执行
                fclose(fp);
        }
}                                                                                                                       

执行方法:
执行命令sudo ./webserver 13000 3启动服务器,事先用ifconfig查看ip地址,我的是192.168.0.103.打开浏览器在地址栏输入hhttp://192.168.0.103:13000即可访问服务器所在的当前文件夹内容,在后面添加相关的文件即为对该文件的操作,本文中没有对C文件进行cat操作,所以添加c文件无效。可以把一张jpg图片放到服务器文件夹中,然后浏览器地址栏输入hhttp://192.168.0.103:13000/test.jpg即可显示。

执行结果:
打印当前文件夹内容
打印文件夹内容

查看服务器文件夹中的图片
在这里插入图片描述
启动服务器,以及显示接收到的请求信息
在这里插入图片描述

最后

以上便完成了一个简易的web服务器的搭建,希望能给大家带来帮助。有不足支出还望指正。本文为作者根据《UNIX/LINUX编程实践教程》一书进行编写。后续也将继续写相关的项目博客希望大家关注。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值