华为鸿蒙 自定义指令 resultMap Java Spring SLAM javafx serialization highcharts flexbox scope Avalon Parsley Uploadify react脚手架搭建 react python中的map函数 python程序实例 python怎么下载 python传递参数 java抽象类 java发邮件 java实现多线程 java代码 linux密码 javascript基础 js判断字符串相等 惠普战99 亚索刀光 联想小新键盘灯怎么开 js字符转数字 ios删除描述文件 js转数字 上单塞拉斯 头条视频解析 cinema4d下载 微信群群发软件 lol修改皮肤 adb安装 脚本录制 服务器系统安装教程
当前位置: 首页 > 学习教程  > 编程语言

开源项目学习-Tinyhttpd

2020/9/19 15:40:21 文章标签:

Tinyhttpd

作者源码地址:https://sourceforge.net/projects/tinyhttpd/

Demo(注释):

主函数模块与宏

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>

#define Max_connection_limit 20
//客户端最大连接数
#define ISspace(x) isspace((int)(x))
//检测是否是空白符(空格,回车/r,换行/n)
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
//服务器ID

int start_up(u_short *);
void* accept_request(void *);
void serve_file(int, const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void cat(int, FILE *);
void error_interrupt(const char *);
void headers(int, const char *);
void bad_request(int);
void cannot_execute(int);
void not_found(int);
void unimplemented(int);
/**********************************************************************/
int main(void)
{
  u_short port = 0;       //服务器配置
  int server_sock = start_up(&port);
  printf("httpd running on port = %d\n", port);

  int client_sock = -1;   //客户端配置
  struct sockaddr_in client_name;
  socklen_t client_name_len = sizeof(client_name);//socklen_t == int
  pthread_t new_thread;

  while (1)
  {
    /********** 尝试 select/epoll IO复用 ? ***********/
    client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
    if (-1 == client_sock)
    {
      error_interrupt("accept is error");
    }
    if (pthread_create(&new_thread, NULL, accept_request, (void*)&client_sock) != 0)
    {
      perror("pthread_create is error");
    }
  }
  close(server_sock);
  return 0;
}

初始化模块

/**********************************************************************/
/* 功能:创建 TCP 协议的 socket,并完成一系列初始化工作(绑定,监听等)
 * 参数: *port 是指向要连接的端口的变量的指针(若为 0 则使用 http 默认端口号 80 )
 * 返回: server_sock */
/**********************************************************************/
int start_up(u_short *port)
{
  struct sockaddr_in name;
  socklen_t name_len = sizeof(name);                 //socklen_t == int
  int server_sock = socket(AF_INET, SOCK_STREAM, 0); //创建 TCP 连接
  if (-1 == server_sock)
  {
    error_interrupt("server_sock is error");
  }
  memset(&name, 0, name_len);
  name.sin_family = AF_INET;
  name.sin_port = htons(*port);             //主机->网络字节序(16位)
  name.sin_addr.s_addr = htonl(INADDR_ANY); //(任意地址)主机->网络字节序(32位)
  if (bind(server_sock, (struct sockaddr *)&name, name_len) < 0)
  {
    error_interrupt("bind is error");
  }
  if (0 == *port)
  { //端口为 0 则使用 http 默认端口号 80 
    if (getsockname(server_sock, (struct sockaddr *)&name, &name_len) == -1)
    { //获取获取套接字的本地名字信息
      error_interrupt("getsockname is error");
    }
    *port = ntohs(name.sin_port); //16位网络->主机字节序(回显)
  }
  if (listen(server_sock, Max_connection_limit) < 0)
  {
    error_interrupt("listen");
  }
  return server_sock;
}

处理请求

/**********************************************************************/
/* 功能:客户端连接服务器(accept()调用)成功,处理客户端的请求信息
 * 参数:client_socket */
/**********************************************************************/
void* accept_request(void *v_client)
{
  int client = *(int*)v_client;
  char buf[1024];            //请求消息缓冲区
  int numchars;              //读取消息大小
  char method[255];          // http的请求方法
  char url[255];             // url 统一资源标识符
  char path[512];            //文件路径
  struct stat st;            //文件信息
  size_t i = 0;              // metho/url 下标
  size_t j = 0;              // buf 的下标
  int cgi = 0;               //如果接下来的程序判定这是一个 CGI,则变为真(1)
  char *query_string = NULL; //查询字符串->动态网页(如CGI)的参数
  //在 GET 的 url 中 或 在 POST 的消息主体中

  numchars = get_line(client, buf, sizeof(buf));
  //获取 请求消息 的 请求行
  while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
  { //读取 http 的请求方法(空白符跳出)
    method[i++] = buf[j++];
  }
  method[i] = '\0'; //为 method 添加结束符
  // strcasecmp 忽略大小写比较字符串,相等返回 0
  if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
  {//判断是否为 GET or POST 方法,都不是则抛出错误
    unimplemented(client); 
    return NULL;
  }
  if (strcasecmp(method, "POST") == 0)
  {
    cgi = 1;
  }
  i = 0;
  for (; ISspace(buf[j]) && (j < sizeof(buf)); ++j); //跳过空格
  for (; !ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf));
       url[i++] = buf[j++]);
/*读取 URL
  URL 由三部分组成:资源类型、存放资源的主机域名、资源文件名
  也可认为由4部分组成:协议、主机、端口、路径
  URL 的一般语法格式为:(带方括号[]的为可选项)
  协议:// 主机名[:端口号] / 路径 / [;特殊参数][?给动态网页提供参数]#信息片断
*/
  url[i] = '\0'; //为 url 添加结束符
  if (strcasecmp(method, "GET") == 0)
  { //对 http 请求中 url 所带的数据进行解析
    for (query_string = url; (*query_string != '?') && (*query_string != '\0');
         ++query_string); //遍历 URL 判断有无特殊参数
    if (*query_string == '?')
    {
      cgi = 1;
      *query_string = '\0'; //分隔开 url 与 query
      ++query_string;       //指向 query 信息处
    }
  }
  sprintf(path, "./htdocs%s", url); //组成文件路径( htdocs/ )
  if (path[strlen(path) - 1] == '/')
  { //判断是否为目录
    strcat(path, "index.html");
  }
  if (stat(path, &st) == -1)
  { //无法获取该文件信息
    while ((numchars > 0) && strcmp("\n", buf))
    {//读取并丢弃 请求消息头
      numchars = get_line(client, buf, sizeof(buf));
    }
    not_found(client); //向客户端报告所请求的资源不存在
  }
  else
  { //与文件类型的位遮罩 位与 来进行比较
    if ((st.st_mode & S_IFMT) == S_IFDIR)
    { //判断是否为目录
      strcat(path, "/index.html");
    }
    if ((st.st_mode & S_IXUSR) || //文件所有者具可执行权限
        (st.st_mode & S_IXGRP) || //用户组具可执行权限
        (st.st_mode & S_IXOTH))   //其他用户具可执行权限
    { 
      cgi = 1;
    }
    if (!cgi)
    { //服务器回送本地文件至客户端
      serve_file(client, path);
    }
    else
    { //加载外部 CGI 脚本
      execute_cgi(client, path, method, query_string);
    }
  }
  //close(client); //关闭
  return NULL;
}
/**********************************************************************/
/* 功能:向客户端发送常规文件,并报告成功信息
 * 如果发生错误,则向客户端报告错误信息
 * 参数:client_socket 
 *       filename 资源文件的路径 */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
  int numchars = 1;
  char buf[1024];
  buf[0] = 'A';
  buf[1] = '\0';
  while ((numchars > 0) && strcmp("\n", buf))
  { //读取并丢弃 请求消息头
    numchars = get_line(client, buf, sizeof(buf));
  }
  FILE *resource = fopen(filename, "r");
  if (NULL == resource)
  {
	perror("fopen is error:");
    not_found(client); //发送 文件未找到 的消息
  }
  else
  {
    headers(client, filename); //发送 成功 的消息
    cat(client, resource);     //传送文件内容
  }
  fclose(resource);
}

执行服务器端脚本

/**********************************************************************/
/* 功能:执行 CGI 脚本(将需要设置环境变量)
 * 参数:client_socket 
 *       path         CGI脚本的路径
 *       method       请求方法
 *       query_string 方法带有的CGI参数 */
/**********************************************************************/
void execute_cgi(int client, const char *path, const char *method, const char *query_string)
{
  char buf[1024];
  int cgi_output[2];      //执行 CGI 子进程的输出端口
  int cgi_input[2];       //执行 CGI 子进程的输入端口
  int status;             //接收 CGI 子进程的退出状态
  pid_t pid;
  int numchars = 1;
  int content_length = -1;

  buf[0] = 'A';
  buf[1] = '\0';
  if (strcasecmp(method, "GET") == 0)
  {// GET 方法
    while ((numchars > 0) && strcmp("\n", buf))
    {//读取并丢弃 请求消息头
      numchars = get_line(client, buf, sizeof(buf));
    }
  }
  else 
  {// POST 方法
    numchars = get_line(client, buf, sizeof(buf));// get_line 会在 buf 末尾添加结束符'\0'
    while ((numchars > 0) && strcmp("\n", buf))
    {//读取并丢弃 请求消息头
      buf[15] = '\0';//将空格替换为'\0',分隔 Content-Length: 与 content_length
      if (strcasecmp(buf, "Content-Length:") == 0)
      {//读取 Content-Length 大小
        content_length = atoi(&(buf[16]));//将以 buf[16] 为首位的字符串转为整数(会跳过空白符\r\n)
      }
      numchars = get_line(client, buf, sizeof(buf));
    }
    if (content_length == -1)
    {
      bad_request(client);
      return;
    }
  }

  sprintf(buf, "HTTP/1.0 200 OK\r\n");//回送客户端成功信息
  send(client, buf, strlen(buf), 0);

  if ((pipe(cgi_output) < 0) || (pipe(cgi_input) < 0))
  {//建立无名管道文件
    cannot_execute(client);
    return;
  }
  if ((pid = fork()) < 0)
  {//创建父、子进程---共享父进程打开的文件(管道 and 标准输入\输出\错误文件)
    cannot_execute(client);
    return;
  }
  if (pid == 0) 
  {//子进程:执行 CGI 脚本
    char meth_env[255];
    char query_env[255];
    char length_env[255];

    dup2(cgi_output[1], STDOUT_FILENO); //子进程 标准输出 重定向至 cgi_output[1] 写端
    dup2(cgi_input[0], STDIN_FILENO);   //子进程 标准输入 重定向至 cgi_input[0] 读端
    close(cgi_output[0]);               //关闭子进程 cgi_output[0] 读端
    close(cgi_input[1]);                //关闭子进程 cgi_input[1] 写端
    sprintf(meth_env, "REQUEST_METHOD=%s", method);
    putenv(meth_env);                   //设置环境变量
    if (strcasecmp(method, "GET") == 0)
    {// GET 方法
      sprintf(query_env, "QUERY_STRING=%s", query_string);
      putenv(query_env);
    }
    else
    {// POST 方法
      sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
      putenv(length_env);
    }
    execl(path, path, NULL);//执行 CGI 
    exit(0);//子进程退出,关闭所有文件
  }
  else
  {//父进程
    char c;
    close(cgi_output[1]);     //关闭父进程 cgi_output[1] 写端
    close(cgi_input[0]);      //关闭父进程 cgi_input[0] 读端
    if (strcasecmp(method, "POST") == 0)
    {// POST 方法
      for (int i = 0; i < content_length; ++i)
      {
        recv(client, &c, 1, 0);
        write(cgi_input[1], &c, 1);        //向子进程的标准输入写入数据
      }
    }
    while (read(cgi_output[0], &c, 1) > 0) //读取 cgi 脚本处理后的数据,并发送给客户端
    {
      send(client, &c, 1, 0);
    }
    close(cgi_output[0]);
    close(cgi_input[1]);
    waitpid(pid, &status, 0); //等待子进程退出先
  }
}

通讯模块

/**********************************************************************/
/* 功能:按行读取客户端发送过来的请求消息(以 \r\n 为每行的终结)
 * 参数: 客户端 socket
 *       消息缓冲区 buf
 *       缓冲区容量 size
 * 返回: 实际读取到的消息的大小 */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
  int i = 0;
  char c = '\0';
  int n; //每次接收的字节数

  while ((i < size - 1) && (c != '\n'))
  {
    n = recv(sock, &c, 1, 0);
    //DEBUG printf("%02X\n", c);
    if (n > 0)
    {
      if (c == '\r') //读取到回车符
      {              //flags 设置为 MSG_PEEK,仅仅是把 socket 缓冲区中的数据读取到 buf 中
                     //没有把已读取的数据从 socket 缓冲区中移除
                     //如果再次调用 recv()函数 仍然可以读到刚才读到的数据
        n = recv(sock, &c, 1, MSG_PEEK);
        //DEBUG printf("%02X\n", c);
        if ((n > 0) && (c == '\n'))
        { //判断是否紧接着换行符,并从缓冲区中移除
          recv(sock, &c, 1, 0);
        }
        else
        { //信息格式错误,添加 换行符 跳出
          c = '\n';
        }
      }
      buf[i++] = c; //保存信息
    }
    else
    { //读取到信息头末尾
      c = '\n';
    }
  }
  buf[i] = '\0'; //添加结束符
  return i;
}
/**********************************************************************/
/* 功能:读取资源文件并发送给客户端
 * 参数:client_socket
 *       resource 指向一个已经打开了的文件流指针*/
/**********************************************************************/
void cat(int client, FILE *resource)
{
  char buf[1024];

  fgets(buf, sizeof(buf), resource); //读取文件一行数据 到换行符\n为止
  while (!feof(resource))            //判断文件是否结束
  {
    send(client, buf, strlen(buf), 0); //发送文件数据
    fgets(buf, sizeof(buf), resource);
  }
}

状态模块

/**********************************************************************/
/* 功能:抛出错误信息,并中断程序 
 * 参数:提示报错信息 */
/**********************************************************************/
void error_interrupt(const char *str)
{
  perror(str);
  exit(1);
}

/**********************************************************************/
/* 功能:向客户机发送一条 200 OK 状态的消息,并准备发送文件(html)
 * 参数: client_socket */
/**********************************************************************/
void headers(int client, const char *filename)
{
  char buf[1024];
  //(void)filename; //可以使用文件名来确定文件类型

  strcpy(buf, "HTTP/1.0 200 OK\r\n");
  send(client, buf, strlen(buf), 0); //思考为什么这里使用 sizeof,而不使用 strlen
  strcpy(buf, SERVER_STRING);
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "Content-Type: text/html\r\n");
  send(client, buf, strlen(buf), 0);
  strcpy(buf, "\r\n");
  send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* 功能:向客户机发送一条 400 BAD REQUEST 状态的消息
 * 参数: client_socket */
/**********************************************************************/
void bad_request(int client)
{
  char buf[1024];
  //客户端请求的语法错误,服务器无法理解
  sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
  send(client, buf, sizeof(buf), 0);
  sprintf(buf, "Content-type: text/html\r\n");
  send(client, buf, sizeof(buf), 0);
  sprintf(buf, "\r\n");
  send(client, buf, sizeof(buf), 0);
  sprintf(buf, "<P>Your browser sent a bad request, ");
  send(client, buf, sizeof(buf), 0);
  sprintf(buf, "such as a POST without a Content-Length.\r\n");
  send(client, buf, sizeof(buf), 0);
}

/**********************************************************************/
/* 功能:向客户机发送一条 404 NotFound 状态的消息
 * 参数:client_socket */
/**********************************************************************/
void not_found(int client)
{
  char buf[1024];
  //服务器无法根据客户端的请求找到资源(网页)
  sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, SERVER_STRING);
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "Content-Type: text/html\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "your request because the resource specified\r\n");
  send(client, buf, strlen(buf), 0);
  
  sprintf(buf, "is unavailable or nonexistent.\r\n");
  send(client, buf, strlen(buf), 0);

  sprintf(buf, "</BODY></HTML>\r\n");
  send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* 功能:向客户机发送一条 500 Internal Server Error 状态的消息
 * 参数: client_socket */
/**********************************************************************/
void cannot_execute(int client)
{
  char buf[1024];
  //请求未完成——服务器遇到不可预知的情况
  sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "Content-type: text/html\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
  send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* 功能:向客户机发送一条 501 Method Not Implemented 状态的消息
 * 参数:client_socket */
/**********************************************************************/
void unimplemented(int client)
{
  char buf[1024];
  //服务器不支持请求的功能(方法未定义),无法完成请求
  sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, SERVER_STRING);
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "Content-Type: text/html\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "</TITLE></HEAD>\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
  send(client, buf, strlen(buf), 0);
  sprintf(buf, "</BODY></HTML>\r\n");
  send(client, buf, strlen(buf), 0);
}

本文链接: http://www.dtmao.cc/news_show_200283.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?