简单web服务器程序(配置文件读取端口)

来源:互联网 发布:下载枪林弹雨刷枪软件 编辑:程序博客网 时间:2024/06/06 13:05
#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include <sys/types.h>
//全局宏
#define MAX_LINE 1024
#define BUG 1
#ifdef BUG
#define DEBUG_PRINT0(str); printf(str);
#else
#define DEBUG_PRINT0(str);
#endif
int init(struct sockaddr_in* sin,int* lfd,int* port);
int error_page(int sock_fd);
int get_path(int cfd,char* path);
int write_page(int cfd,char* path);
static int configuration(int *port,char* path);
struct sockaddr_in sinaddr,cin;
   socklen_t len=sizeof(cin);
   int lfd,cfd,fd;
   pid_t pid;
   int sock_opt=1;  //套接字选项
   int port;
   char path[MAX_LINE];  //文件路径缓冲区
   struct stat statbuf;  //文件状态结构
int main(void)
{
   signal(SIGCHLD,SIG_IGN);  //安装信号处理函数
   signal(SIGPIPE,SIG_IGN);
   printf("initializing ...\n");
   if(init(&sinaddr,&lfd,&port)==-1)  //初始化
   {
      DEBUG_PRINT0("error during initializing\n");
      exit(1);
   }
  
   while(1)
   {
       DEBUG_PRINT0("waiting connection...\n");
       cfd=accept(lfd,(struct sockaddr*)&cin,&len); //连接
       if(cfd==-1)
       {
           perror("fail to accept");
           exit(1);
        }
       printf("客户端###:%s\n ",inet_ntoa(cin.sin_addr));
        pid=fork(); //创建子进程,用于并发处理请求
        if(pid<0)
        {
           perror("fail to fork");
           exit(1);
         }else if(pid==0)   //子进程,处理连接请求
         {
             close(lfd);  //处理连接,关闭监听套接字
 
             //分析客户端发来的信息,得到请求文件的路径
              if(get_path(cfd,path)==-1)    ///////////////////
              {
                   DEBUG_PRINT0("error during geting filepath\n");
                   exit(1);
              }
               if(stat(path,&statbuf)!=0)  //得到文件的状态
               { 
                    perror("fail to get file status");
                    exit(1);
                }

                //非普通文件
                if(!S_ISREG(statbuf.st_mode))  
                {
                     if(error_page(cfd)==-1) //输出出错页面
                     {
                        DEBUG_PRINT0("error during writing error-page\n");
                        close(cfd);
                        exit(1);
                      }
                      close(cfd);  //关闭连接套接字,连接结束
                      exit(0);  //子程序正常退出
                 }

                //如果是可执行文件,说明是一个CGI文件
                if(statbuf.st_mode & S_IXOTH)  
                {
                    dup2(cfd,STDOUT_FILENO);
                    if(execl(path,path,NULL)==-1)   //执行该CGI文件
                    {
                         perror("fail to exec");
                         exit(1);
                     }
                 }

             //非CGI文件,则需要回送文件内容给客户端
             if((fd=open(path,O_RDONLY))<0) 
             {
                  if(error_page(cfd)==-1)
                   {
                      DEBUG_PRINT0("error during writing error-page\n");
                      close(cfd);
                      exit(1);
                    }
                    close(cfd);
                    exit(0);   //子进程退出
              } 
              if(write_page(cfd,path)==-1)
              {
                   DEBUG_PRINT0("error during writing page\n");
                   exit(1);
               }
               close(fd);  //关闭文件
               close(cfd);//关闭连接套接字,服务器主动关闭,表示数据传输完毕
               exit(0);  //子进程退出
          }else  //父进程,继续监听连接请求
          close(cfd);  //继续监听,关闭连接套接字  
    }//end of while(1)
    return 0;
}//end of main
 
/*配置文件的格式端口“port: 8000",目录“root-path";冒号后面有个空格*/
static int configuration(int *port,char* path)//读取配置文件获取端口和服务器的根目录
{
    int i;
     FILE* fp;
     char* p;
     char  buf[50];  //文件内容的缓冲
      fp=fopen("./config.ini","r");
      if(fp==NULL)
      {
         perror("fail to open config.ini");
         return -1;
      }
      while(fgets(buf,50,fp)!=NULL) //读取每行的内容
      {
          if(buf[strlen(buf)-1]!='\n')  //判断文件格式
          return -1;
          else
          buf[strlen(buf)-1]='\0';  //添加结束符
          if(strstr(buf,"port")==buf) //寻找“port”字样,读取端口号
          {
               if((p=strchr(buf,':'))==NULL)
               {
                  printf("config.ini expect ':'\n");
                  return -1;
               }
               *port=atoi(p+2);  //跳过”:"和空格,得到端口号
               if(*port<=0)
               {
               printf("error port\n");
               return -1;
               }
           }else if(strstr(buf,"root-path")==buf)  //得到根目录
           {
                if((p=strchr(buf,':'))==NULL)
                 {
                  printf("config.ini expect':'\n");
                  return -1;
                 }
                  p++;
                  p++;  //跳过‘:’
                  strcpy(path,p);
            }else
            {
                 printf("error in config.ini\n");
                 return -1;
             }
       }
       return 0;
}
//初始化函数,读取配置文件,获得端口和根目录;创建监听套接字,并且绑定地址;使用套接字启动监听
int init(struct sockaddr_in * sin,int* lfd,int *port)
{
    if(configuration(port,path)==-1)
    {
           printf("fail to get th port and path from configuration\n");
           return -1;
     }
    *lfd=socket(PF_INET,SOCK_STREAM,0); //创建socket
    sin->sin_family=PF_INET;
    sin->sin_port=htons(*port);
    inet_aton("192.168.1.213",&sin->sin_addr);
    setsockopt(*lfd,SOL_SOCKET,SO_REUSEADDR,&sock_opt,sizeof(sock_opt));
    //绑定
    if(bind(*lfd,(struct sockaddr*)sin,sizeof(*sin))==-1)
     {
         printf("bind error\n");
         return -1;
     }
    if(listen(*lfd,100)==1) //监听
    {
         printf("listen error\n");
         return -1;
     }
    return 0;
}
//分析所需文件的路径
int get_path(int cfd,char *path)
{
     char buf[MAX_LINE];
     if(read(cfd,buf,MAX_LINE)==-1)  //读取http协议
     return -1;
     //http协议头第一行的格式为:"GET / HTTP/1.1"
     if(strstr(buf,"GET")!=buf)
     {
          DEBUG_PRINT0("wrong request\n");
          return -1;
      }
      if((buf[4]=='/') && (buf[5]==' '))  //没有指定文件名,使用默认文件“index.html"
            strcat(path,"/index.html");
      else{
         strtok(&buf[4]," "); //否则使用客户端指定的文件名
         strcat(path,&buf[4]);
      }
     return 0;
}
//error_page函数向客户端输出一个出错的页面,这个页面包含出错信息,用来调试
int error_page(int sock_fd)
{
     char err_str[1024];
      #ifdef DEBUG
       sprintf(err_str,"http/1.1 404 %s\r\n",strerror(errno.h));
      #else
       sprintf(err_str,"HTTP/1.1 404 Not Exsit\r\n");
      #endif
      if(write(sock_fd,err_str,strlen(err_str))==-1)
      return -1;
      return 0;
}
//write_page函数将需要的文件内容发送给客户端
int write_page(int cfd,char* path)
{
     int n;
     char buf[MAX_LINE];
     //协议头格式输出
     if(write(cfd,"HTTP/1.1 200 OK\r\n",strlen("HTTP/1.1 200 OK\r\n"))==-1)
     return -1;
     //页面的类型,则需要根据文件的扩展名来判断
     if(write(cfd,"Content-Type: ",strlen("Content-Type: "))==-1)
      return -1;
     n=strlen(path);
     //三种图片格式,表示图片类型
     if(strcasecmp(&path[n-3],"jpg")==0 || strcasecmp(&path[n-4],"jpeg")==0)
           if(write(cfd,"image/jpeg",strlen("image/jpeg"))==-1)
               return -1;
     else if(strcasecmp(&path[n-3],"gif")==0)
           if(write(cfd,"image/gif",strlen("image/gif"))==-1)
               return -1;
     else if(strcasecmp(&path[n-3],"png")==0)
           if(write(cfd,"image/png",strlen("image/png"))==-1)
               return -1;
     else  //纯文本类型
           if(write(cfd,"text/html",strlen("text/html"))==-1)
               return -1;
     if(write(cfd,"\r\n\r\n",4)==-1)  //添加协议结尾,最后多出一个”\r\n"
          return -1;
     while((n=read(fd,buf,MAX_LINE))>0)
          write(cfd,buf,n);
     return 0;
}
 
0 0
原创粉丝点击