精华内容
下载资源
问答
  • 几个月没写教程了,今天登录看一下,账号还没被封,高兴之余写个http服务器demo。http超文本传输协议,是互联网上应用最广泛的网络协议。它是应用层的协议,底层是基于TCP通信的。HTTP协议的工作过程:客户通过...

    几个月没写教程了,今天登录看一下,账号还没被封,高兴之余写个http服务器demo。

    http超文本传输协议,是互联网上应用最广泛的网络协议。它是应用层的协议,底层是基于TCP通信的。HTTP协议的工作过程:客户通过浏览器向服务器发送文档请求,浏览器将请求的资源回应给浏览器,然后关闭连接。即:连接->请求->响应->关闭连接。

    我们基于http1.1版本编写,因为从零搭建的,所以我决定采用python写,当然我个人是搞java和python开发的,至于为啥不用java 。因为java写太麻烦。但是原理都一样,又不是汇编,有编程基础的人应该都懂。

    关于tcp协议的三次握手和四次挥手,不介绍了,网上有太多详细的教程

    关于线程,进程,协程,正则表达式,文件读写等等也不介绍了,这些基本的东西,自己弄懂后再看,不然我一个个讲清楚,得写好长好长。需要注意的代码前,我也有好好写注释,要是感觉我没说清楚的可以自己按照我的代码,自己试一下就懂了。纸上得来终觉浅,绝知此事要躬行

    1.创建服务器套接字,并设定相关的参数

    1.1关于bind()这个方法,ip地址不写,默认也就是本地环回地址127.0.0.1

    1.2关于setsockopt()方法是为了防止,服务器先关闭,服务器本地资源暂存,导致address占用的问题。出现这个问题,和tcp四次挥手原理有关。当然实际中都是客户端先关闭

    552b6b1550497d73220652b361821dcd.png

    2.服务器接收来自浏览器参数(示例中使用了多进程,线程,协程示例)

    2.1client_service(client_socket)这个方法是用来处理服务器响应得

    2.1因为tcp.accept()方法默认是堵塞的,所以我们必须使用循环加多线程解决这个问题

    2.2使用多进程创建得时候,套接字必须关闭两次(其中一次在client_service方法内部关闭),但是创建多进程会复制程序所有的资源,所以套接字存在两个。如果想具体指导要关两次,就得学习下Linux中文件硬链接这个概念了。

    abc55cf4e505997c7167e462ae6a4dec.png

    3.获取浏览器请求数据

    3.1正则表达式部分是为了匹配请求头中具体的页面名称 如:GET /index.html HTTP/1.1。 ret.group(1)这个是为了获取index.html。

    49acb6b5ad2b7a4574df87ce99d96625.png

    4.响应数据给浏览器

    4.1关于响应头和响应体也可以拼接成一个新的字符串一起返回给浏览器,我这样写是为了让大家看得更清楚。其中读取某个html页面,页面没啥内容 只是为了看到效果就好

    8e40bc1572d00e6b2432c7251b9736d8.png

    运行编写的程序,然后使用浏览器访问测试成功

    05b0ab9cd0d1ed756945db3957652a95.png

    一般来说在实际开发中,进程占用资源最大,进程次之,协程最小。上面示例我们使用的是协程,虽然说资源最小,但是还是有改进空间。比如说,使用单进程,单线程,非堵塞的方式实现服务器的并发(关于并发和并行的概念,不懂得自己查阅相关资料吧)

    代码改动得地方只有服务器接受请求数据得地方,关于响应无需改动。因为代码比较简单,我就不具体阐述了。具体的逻辑,tcp.accept默认会堵塞,调用tcp.setblocking(False)设置非堵塞,当服务器未接受到数据时,就会产生异常,我们只要捕捉这个异常。将正常的套接字添加到一个新建的列表中(Java称为数组),然后遍历这个列表,循环取出数据即可

    81ca745b20e8419913838a2de1d0f371.png

    为了区别,新的demo端口采用12345 测试如下:

    1cf73d52299f9f5403ba0aa4289e8e14.png
    b921b26b3317776ea550ec25e71cec27.png

    这样单进程,线程,非堵塞的方式服务器的并发,资源占用最小。当然还有一种类似,设置比这种方式好太多的方法epoll

    nginx就是基于epoll原理写的,像国内大型的互联网公司如百度,阿里京东等,他们的服务器虽然都是自家的,但是都是基于nginx编写的。因为epoll只运行在linux系统上,所以我只能写一个epoll 的demo给大家看一下,当作了解。原理类似上一个。这个东西已经比较深入,一两句话说不清粗,大家可以先去看下epoll的教程,然后参照我的demo多试试,就应该可以理解了。实际开发中也不会让你手写服务器。不然apache不然早就回家种地了

    cec77d1e600e3b4dc8874dc1f63ec248.png

    好久不写这个了,一般都是在github上直接上传源码,一起交流的,写这个自己都觉得有些没说清楚,见谅。但是大体的流程就这样。刚入门的小伙伴不要纠结后面的,能写出第一个demo就够了,不然头皮发麻。em.....就这些了吧。有空的话,教大家谢谢爬虫,搞搞有意思的东西。bye

    6512ee5e8b1d40e8f0fc9fc568839c13.png
    展开全文
  • 上一篇文章《FastDFS 分布式文件系统...如果我们想通过 HTTP 访问 FastDFS 中保存的图片信息,需要借助其他工具实现,Nginx 就是一个不错的选择,它是一个高性能的 HTTP 和反向代理 Web 服务器。关于 Nginx 更多的...

    81762753fbd4d4930cda83f20aecc115.png

    上一篇文章《FastDFS 分布式文件系统详解》中带大家详细了解它的核心概念,架构体系及 FastDFS 环境的搭建与使用。但是此时还无法通过 HTTP 对文件进行访问,这篇文章将带大家解决这个问题。

    如果我们想通过 HTTP 访问 FastDFS 中保存的图片信息,需要借助其他工具实现,Nginx 就是一个不错的选择,它是一个高性能的 HTTP 和反向代理 Web 服务器。关于 Nginx 更多的内容请阅读《一篇文章搞定 Nginx 反向代理与负载均衡》。

    c63055edfe870717d5e46d7ad8f80997.png

    那么问题来了:既然文件都上传至 Storage 服务器了,在 Storage 服务器中直接安装 Nginx 反向代理至文件资源路径,好像就能实现浏览器直接访问文件的效果。究竟是不是这样玩的呢?继续往下看。

    其实真实环境中并不是这样使用的,因为 FastDFS 是通过 Tracker 服务器将文件存储在 Storage 服务器中,而且同组存储服务器之间还需要进行文件复制,会有同步延迟的问题。

    假设 Tracker 服务器将文件上传到了 192.168.10.102,上传成功以后文件相关信息已经返回给客户端了。此时 FastDFS 的集群存储机制会将这个文件同步到同组中的其他机器上进行存储 192.168.10.103,在文件还没有复制完成的情况下,客户端如果用这个文件的相关信息在 192.168.10.103 上进行获取,就会出现文件无法访问的错误。

    解决办法就是使用 fastdfs-nginx-module,它可以重定向文件链接到源服务器进行获取,避免客户端由于复制延迟导致的文件无法访问的错误。

    配置 fastdfs-nginx-module

    下载资源

    Nginx 整合 FastDFS 时 Nginx 需要添加 fastdfs-nginx-module 模块。直接通过 Github:https://github.com/happyfish100/fastdfs-nginx-module 下载压缩包或者使用 git 命令下载,或者通过资源地址:https://sourceforge.net/projects/fastdfs/files/ 下载。

    下载以后,上传资源 fastdfs-nginx-module-master.zip 至服务器 /usr/local/src 目录后并解压。

    unzip fastdfs-nginx-module-master.zip

    修改配置

    复制配置文件 mod_fastdfs.conf/etc/fdfs 目录中。

    cp /usr/local/src/fastdfs-nginx-module-master/src/mod_fastdfs.conf /etc/fdfs/

    vim /etc/fdfs/mod_fastdfs.conf 编辑配置文件,主要关注以下部分。

    # tracker 服务器的 IP 和端口
    tracker_server = 192.168.10.101:22122
    # url 地址是否包含组名/卷名
    url_have_group_name = true
    # 数据组/卷对应的路径地址
    store_path0 = /fastdfs/storage/store

    拷贝资源

    复制 fastdfs 安装包中的两个配置文件 http.confmime.types/etc/fdfs 目录中。

    cp /usr/local/src/fastdfs-master/conf/http.conf /etc/fdfs/
    cp /usr/local/src/fastdfs-master/conf/mime.types /etc/fdfs/

    安装 Nginx

    下载资源

    下载 Nginx 并解压。

    # 下载 nginx 压缩包
    wget -P /usr/local/src http://nginx.org/download/nginx-1.18.0.tar.gz
    # 解压
    tar -zxvf /usr/local/src/nginx-1.18.0.tar.gz -C /usr/local/src

    安装依赖

    Nginx 是基于 C 语言开发的,HTTP 模块使用了 pcre 来解析正则表达式,且可以使用 zlib 对 HTTP 包的内容进行 gzip 压缩,Nginx 不仅支持 HTTP 协议,还支持 HTTPS,HTTPS 需要 openssl 提供支持,所以安装 Nginx 之前必须先安装它所依赖的环境。

    yum install -y gcc gcc-c++ pcre pcre-devel zlib zlib-devel openssl-devel

    安装 Nginx

    编译并安装。

    # 切换至 nginx 的解压目录
    cd /usr/local/src/nginx-1.18.0
    # 创建 nginx 的安装目录
    mkdir -p /usr/local/nginx
    # 添加 fastdfs-nginx-module 模块,指定 nginx 的安装路径
    ./configure --add-module=/usr/local/src/fastdfs-nginx-module-master/src --prefix=/usr/local/nginx/
    # 编译并安装
    make && make install

    配置 Nignx

    vim /usr/local/nginx/conf/nginx.conf 编辑配置文件,在 80 端口下添加以下内容。关于 Nginx 启动用户的问题请根据自身实际环境进行配置。

    location ~/group[0-9]/ {
        ngx_fastdfs_module;
    }

    58a81a2f28909f3d9df1a2321049174d.png

    测试

    启动 Nginx,命令为:/usr/local/nginx/sbin/nginx

    此时客户端上传图片以后得到文件名为:group1/M00/00/00/wKgKZl9tkTCAJAanAADhaCZ_RF0495.jpg

    浏览器访问:http://192.168.10.102/group1/M00/00/00/wKgKZl9tkTCAJAanAADhaCZ_RF0495.jpg 效果如下:

    ca4ef8a69d974e86e6de6816fdf6197f.png

    至此 Nginx 整合 FastDFS 实现文件服务器就到这里,其实目前的环境只是一个单节点的 FastDFS,如果要实现高可用,TrackerStorage 都需要搭建多个节点形成集群环境,后续文章中会详细的教大家搭建 FastDFS 的集群环境。下文我们先解决 Java 客户端如何操作 FastDFS 实现文件上传下载替换删除。

    ee33ee054b91e68dfa326c8d71bc1c7c.gif

    本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议

    大家可以通过 分类 查看更多关于 FastDFS 的文章。

    您的点赞转发是对我最大的支持。

    扫码关注 哈喽沃德先生「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~

    b5fc71edb008dfbb8a18ecd40096dabf.gif

    fcd12eae13851fd2e333db14bb24c501.png
    展开全文
  • 一.void HttpServerStart(const char* ip,short port )来启动服务器 分为如下几步 1.创建socket 2.绑定端口号 3.监听 4.进入事件循环,当每次有新的连接的时候创建一个新线程,使accept被快速调用到,调用...

    基于TCP协议

    HTTP协议解析请求,构造响应

     

    用命令行参数来传入参数

    一.void   HttpServerStart(const char* ip,short port )来启动服务器

    分为如下几步

      1.创建socket

      2.绑定端口号

      3.监听

      4.进入事件循环,当每次有新的连接的时候创建一个新线程,使accept被快速调用到,调用越频繁的话性能越高

      在线程入口函数ThreadEntry()中,对HTTP协议解析请求,构造相应,完成主体的业务逻辑

    分为如下步骤

         1.解析请求

      (1)按行读socket读出HTTP请求的首行,分别解析方法,和URL,注意的从socket中读取一行数据,一个一个字符的读,如果当前字符是航分割符

    。就认为这一行读完了,而不同的浏览器的航分割符不尽相同,有\n  \r  \r\n   做法是把他们都归结为\n处理,上述函数实现的行为是读出的>一行末尾带\n,int  ReadLine(int new_sock,char  line[])

        (2)解析首行,把空格换成\0.用一组指针记录切分出的每个部分。应切出三个部分。分别为方法,URL,版本号   ParseFirstLine(char first_line[],char ** p_method,char ** p_url)

     (3)解析URL,以问好为分割符左边就是url_path右边就是query_string ,int ParseUrl(char  url[],char **  p_url_path,char **  p_query_string )

       (4) 解析Header,在我的程序里只处理了Centend_length,注意当遇到了\n 说明处理完成

     

         2.构造响应

        (1) 处理页面找不到void  Handler404(int new_sock) 并发出

     (2)处理静态页面 (本质是在url_path中存在的页面,和服务其上的这个路经是匹配的,注意这个路径看起来是一个绝对路径,但其实是一个相

    对路径,是相对于HTTP服务器的根路径,把允许对外访问的文件集中放入某个目录下,作为服务器的跟目录,我的服务器使用./wwwroot 作为根目

     录,所以可以看到url_path=>/index.html其实访问的就是./wwwroot/index.html) 并发出int HandlerStaticFile(const   HttpRequest*  req>,int   new_sock)

         ((1))拼接目录(根据url_path构造出当前文件在文件系统上的真实路径,,如果用户传的是一个目录,那么会有这个目录下的默认文件供人访

    >问,这时需要用系统提供的API,也就造成了不可跨平台的问题。)GetFilePath (const char*url_path,char   file_path[])

         ((2))打开文件,读取文件内容,根据文件内容构造HTTP响应,将相应内容写入sock中。其中文件的内容就叫做HTTP相应的body部分,这时需要

    先把数据从内核到用户(读),又从用户到内核(写),系统开销较大,于是我们用ssize_t    sendfile (int  out_fd,int in_fd,off_t  *offset,size_t  count)来优化,这个函数的操作全都在内核中,这里out_fd必须是socket,当我们发现乱码时大部分都是编解码不一致造成的,只是>可以打开浏览器随意找一个包,把这个包中header部分的编解码信息加入我们要发送的信息中。另外说明的一点是当客户端发起一次静态页面的请

    求时,浏览器不一定只发一次请求,如果发现还有链接信息时,或其他路径的信息时,会继续发起请求,比如我的服务器中请求静态页面时就会发

    起两次请求,一次显示Hello world,一次显示猫的图片,WriteStaticFile(const  char * file_path,int new_sock) ,写成功返回200,写失

    败返回404

    (3)处理动态页面  并发出(使用CGI技术.这时一个协议)

        协议的内容是

       ((1))HTTP服务器需要创建子进程

       ((2))子进程进行程序替换到磁盘上的某个可执行程序,这里需要把HTTP的请求方法,query_string(GET) ,body(POST),因为要进行

    程序替换,程序替换虽然不改变PCB,但是要替换了代码段数据段,堆栈上内存从新分配,所以前两个要用环境变量出入子进程,而body用匿名管>道传输。最后要把构造好的动态页面通过匿名管道传回子进程。因为管道是单向通信所以要用两个管道,又因为创建管道其实也就是要创建两个变>量,程序替换之后,也就找不到了,但是管道的文件描述符还是在的,为了可以不通过这两个已经访问不到的变量访问文件,我们在进行程序替换

    之前,把输入输出定向导管道上。

        也就是说在这一步应做的是如下几点

        ((1))先创建一对匿名管道

        ((2))创建子进程

        ((3))父进程执行父进程的相关逻辑(在最开始关掉不用的文件描述符)

             int HanderCGIFather(const HttpRequest*  req,int new_sock,int father_read,int father_write)

            (((1)))父进程把HTTP请求的body部分写到管道中

            (((2)))父进程尝试读取子进程(进程等待)

            (((3)))父进程构造HTTP响应,写会客户端

               (  (  (4)   )   )父进程回收子进程

         ((4))子进程执行子进程的相关逻辑(在最开始关掉不用的文件描述符)

            (((1)))设定环境变量,只可在子进程中设置(子进程的环境变量相互独立)。

            (((2)))进行重定向,标准输入输出重定向到管道上。

            (((3)))根据url_path构造出CGI程序的路径。

            (((4)))进行程序替换了(一旦进行程序替换,代码就执行到了CGI程序内部,也就是进入了CGI程序的内部)

    3.把相应返回到客户端上

     

    一般用sprintf ()函数构造字符串

     

    这个项目中我将会做一个计算器来测试我的动态页面,其中提交数据是静态页面,返回结果为动态页面

    后面是这个自制服务器的缺点;

    CGI经典协议的问题在于性能问题。核心在于CGI协议中要求创建子进程。

    改进方法,基于线程池的思想出现了一个改进版本FastCGI当前主流的HTTP服务器和应用程序交互的协议之一。

     

     

    下面是我的代码,为了让大家看清楚服务器的根路径与虚拟机的根路径,我会把每个源代码文件的路径都加上

    /home/a/Desktop/Mycode/http_server/http_server.c

    
      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <pthread.h>
      4 #include <stdlib.h>
      5 #include <unistd.h>
      6 #include <sys/socket.h>
      7 #include <netinet/in.h>
      8 #include <arpa/inet.h>
      9 #include <sys/stat.h>
     10 #include <fcntl.h>
     11 #include <sys/sendfile.h>
     12 #include <sys/wait.h>
     13 #include "http_server.h"
     14 
     15 
     16 typedef struct   sockaddr sockaddr;
     17 typedef struct   sockaddr_in sockaddr_in ;
     18 
     19 
     20 
     21 //一次从socket中读取以行数据
     22 //把数据放在buf缓冲区中
     23 //如果读取失败,返回值就是-1;
     24 //遇到\n或\r\n或\r证明读取完成
     25 int ReadLine(int  sock,char  buf[],ssize_t  size){
     26         //1.从socket中一次读取一个字符
     27         char  c='\0';
     28         ssize_t   i=0;//当前读了多少个字符
     29         //结束条件;
     30         //a)读到的长度太长,达到了缓冲区的上限
     31         //b)读到了结束标志,并把所有结束标字都转换成\n
     32 
     33         while(i<size-1&&c!='\n'){
     34                 ssize_t  read_size=recv(sock,&c,1,0);
     35                 //读取失败有两种
     36                 if(read_size<0){
     37                         return -1;
     38                 }
     39                 if(read_size==0){
     40                         //因为预期是要读到\n这样的换行符,
     41                         //结果还没有读得到就先读了EOF,这种情况我们也暂时认为是失败的
     42                         return -1;
     43                 }
     44                 if(c=='\r')
     45                 {
     46                         //当前遇到了\r,但还需要确定下一个字符是不是\n
     47                         //MSG_PEEK选项从内核的缓冲区中读取数据,
     48                         //但是读到的数据不会从缓冲区中删除掉。
     49                         recv(sock,&c,1,MSG_PEEK);
     50                         if (c=='\n'){
     51                                 //此时的分隔符就是\r\n
     52                                 recv(sock,&c,1,0);
     53                         }else
     54                         {
     55                                 //当前分隔符确定是\r,此时把分隔符转换成\n
     56                                 c='\n';
     57                         }
     58                 }
     59                 //只要上面的c读到的是\r,那么if结束后,c都变成了\n
     60                 //这种方式就是把前面的\r和\r\n两种情况都统一成了\n
     61                 buf[i++]=c;
     62 
     63         }
     64         buf [i]='\0';
     65         //  printf ("ReadLine\n");
     66         return i;//真正想缓冲区中放置的字符的个数
     67 }
     68 
     69 
     70 int Split(char  input[], const char *split_char,char* output[],int output_size)
     71 {
     72         //使用strtok
     73         int i=0;
     74         char *tmp=NULL;//保存上次的切分结果
     75         char * pch;
     76         //使用线程安全的strtok_r代替strtok
     77         //这一点是以后非常容易出错的一点
     78         pch = strtok_r (input,split_char,&tmp);
     79         while (pch != NULL)
     80         {
     81                 if (i>=output_size)
     82                 {
     83                         return i;
     84                 }
     85                 output[i++]=pch;
     86                 pch = strtok_r (NULL, split_char,&tmp);
     87         }
     88 
     89         //printf ("Split\n");
     90         return i;
     91 
     92 }
     93 
     94 int ParseFirstLine(char  first_line[],char**p_url,char  **p_method)
     95 {
     96         //把首行按空格进行字符串切分
     97         //切分得到的每一个部分,就放在tok数组里
     98         //返回值,就是tok数组包含几个元素。
     99 
    100         char  *tok[10];
    101         //最后一个参数10表示tok数组中最多可以放几个元素
    102         int tok_size=Split(first_line," ",tok,10);
    103         if (tok_size!=3)
    104         {
    105                 printf ("Split  failed!   tok_size =%d\n",tok_size);
    106                 return -1;
    107         }
    108         *p_method=tok[0];
    109         *p_url=tok[1];
    110 
    111         // printf ("ParseFirstLine\n");
    112         return 0;
    113 }
    114 
    115 
    116 
    117 int  ParseQueryString (char  *url,char  **p_url_path,char **p_query_string )
    118 {
    119         *p_url_path=url;
    120         char *p=url;
    121         for (;*p!='\0';++p)
    122         {
    123                 if (*p=='?')
    124                 {
    125                         *p='\0';
    126                         *p_query_string =p+1;
    127                         return 0;
    128                 }
    129         }
    130         //循环结束都没有找到?,说明这个请求不带query_string
    131         *p_query_string=NULL;
    132         // printf ("ParseQueryString\n");
    133         return 0;
    134 }
    135 
    136 
    137 
    138 int    ParseHeader (int sock ,int *content_length)
    139 {
    140         //1.循环从socket中读取一行。
    141         //2.判断当前行是不是Content—Length
    142         //3.如果是Content-Length就直接把value读出来
    143         //4.如果不是就直接丢弃
    144         //5.读到空行,循环结束。
    145         char buf [SIZE]={0};
    146         while (1)
    147         {
    148                 //1.循环从socket中读取一行。
    149                 ssize_t  read_size=ReadLine (sock,buf,sizeof (buf));
    150                 //处理读失败的情况
    151                 if (read_size<=0)
    152                 {
    153                         return -1;
    154                 }
    155                 //处理读完的情况
    156                 if (strcmp (buf ,"\n")==0)
    157                 {
    158                         return 0;
    159                 }
    160                 //2.判定当前行是不是Content-Length
    161                 //如果是Content-Length就直接把value读出来
    162                 //如果不是就直接丢弃
    163                 const  char *content_length_str="Content-Length: ";
    164                 if (content_length!=NULL&&strncmp(buf,content_length_str,strlen(content_length_str))==0)
    165                 {
    166                         *content_length=atoi(buf+strlen(content_length_str));
    167 
    168                 }
    169 
    170 
    171         }
    172         return 0;
    173         //读到空行循环结束
    174 }
    175 
    176 
    177 
    178 void Handler404(int sock)
    179 {
    180         //构造一个完整的HTTP响应
    181         //状态码是404
    182         //body部分也应该是一个404相关的错误页面。
    183         const char*  first_line="HTTP/1.1 404 Not Found\n";
    184         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    185         const char *  blank_line="\n";
    186         const char * html="<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"></head>"
    187                 "<h1>您的页面被喵星人吃掉了!!!</h1>";//提示浏览器按照utf8方式解码
    188         send (sock,first_line ,strlen(first_line),0);
    189         send (sock,type_line ,strlen(type_line),0);
    190         send (sock,blank_line ,strlen(blank_line),0);
    191         send (sock,html ,strlen(html),0);
    192         return;
    193 }
    194 
    195 
    196 
    197 void PrintRequest (Request*req)
    198 {
    199         printf ("method: %s\n",req->method);
    200         printf ("url_path: %s\n",req->url_path);
    201         printf ("query_string: %s\n",req->query_string);
    202         printf ("content_length: %d\n",req->content_length);
    203         return;
    204 }
    205 
    206 
    207 
    208 int IsDir(const char *file_path)
    209 {
    210         struct stat st;
    211         int ret=stat(file_path,&st);
    212         if (ret<0)
    213         {
    214                 return 0;
    215         }
    216         if (S_ISDIR(st.st_mode))
    217         {
    218                 return 1;
    219         }
    220         return 0;
    221 }
    222 
    223 
    224 void HandlerFilePath(const  char*url_path,char  file_path[])
    225 {
    226         //(a,给url_path 加上前缀名(HTTP服务器的根目录)
    227         //url_path-->/index.html
    228         //file_path--->./wwwroot/index.html
    229         sprintf (file_path,"./wwwroot%s",url_path);
    230         //b)例如url_path是/,此时url_path 其实就是一个目录。
    231         //如果是目录的话,就演给这个目录之中追加一个index.html 
    232         //url_path /或者 /image/
    233         if (file_path[strlen(file_path)-1]=='/')
    234         {
    235                 strcat(file_path,"index.html");
    236         }
    237         //url_path=>/image
    238         if (IsDir(file_path))
    239         {
    240                 strcat (file_path,"/index.html");
    241         }
    242         return ;
    243 
    244 }
    245 
    246 
    247 
    248 ssize_t  GetFileSize(const char*  file_path)
    249 {
    250         struct stat  st;
    251         int ret=stat(file_path,&st);
    252         if (ret<0)
    253         {
    254                 //打开文件失败,很可能是文件不存在
    255                 //此时直接返回文件长度为0
    256                 return  0;
    257         }
    258         return st.st_size;
    259 }
    260 
    261 
    262 
    263 
    264 int WriteStaticFile (int sock,const char*  file_path)
    265 {
    266         //1.打开文件
    267         int fd=open (file_path,O_RDONLY);
    268         if (fd<0)
    269         {
    270                 //文件描述符不够用
    271                 //文件不存在(404)不好意思找不到
    272                 perror ("open");
    273                 return 404;
    274         }
    275         //2.把构造出来的HTTP响应写到socket之中
    276         //1).写入首行
    277         //2).写入head
    278         //3).写入空行
    279         //4).写入body(文件内容)
    280         const char *  first_line="HTTP/1.1 200 OK\n";
    281         send (sock,first_line,strlen(first_line ),0);
    282         //const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    283         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    284         //send (sock,type_line ,strlen(type_line),0);
    285         //两个都不添,让浏览器自己决定
    286         const char *  blank_line="\n";
    287         send (sock,blank_line ,strlen(blank_line),0);
    288         /*ssize_t  file_size=GetFileSize(file_path);
    289           ssize_t  i=0;
    290           for(;i<file_size;i++)
    291           {
    292           char c;
    293           read(fd,&c,1);
    294           send (sock,&c,1,0);
    295           }
    296           */
    297         sendfile(sock,fd,NULL,GetFileSize(file_path));
    298         //3.关闭文件
    299         close (fd);
    300         return 200;
    301 }
    302 
    303 
    304 
    305 int HandlerStaticFile (int sock ,Request *req)
    306 {
    307         //1根据url_path获取到文件在服务器上的真实路径。
    308         char  file_path[SIZE]={0};
    309         HandlerFilePath(req->url_path,file_path);
    310         //2。读取文件,把文件的内容直接写道socket之中
    311         int err_code=WriteStaticFile (sock,file_path);
    312         return err_code;
    313 }
    314 
    315 int    HandlerCGIFather(int new_sock,int   father_read,int father_write,int  child_pid,Request  *req){
    316         //1如果是POST请求,就把body写到管道中
    317         if (strcasecmp(req->method,"POST")==0){
    318                 int i=0;
    319                 char  c='\0';
    320                 for (;i<req->content_length;++i){
    321                         read(new_sock,&c,1);
    322                         write(father_write,&c,1);
    323                 }
    324         }
    325         //2构造HTTP响应
    326         const char *  first_line="HTTP/1.1 200 OK\n";
    327         send (new_sock,first_line,strlen(first_line ),0);
    328         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    329         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    330         send (new_sock,type_line ,strlen(type_line),0);
    331         //两个都可以不添(长度和类型),让浏览器自己决定
    332         const char *  blank_line="\n";
    333         send (new_sock,blank_line ,strlen(blank_line),0);
    334         //循环的从管道中读取数据并写入数据到socket
    335         char  c='\0';
    336         while (read(father_read,&c,1)>0){
    337         //从管道中读数据,如果(父进程,子进程)所有写端关闭,read将会读到EOF,从而返回0
    338                 send(new_sock,&c,1,0);
    339         }
    340         //4回收子进程的资源
    341         waitpid(child_pid,NULL,0);
    342         return 200;
    343 }
    344 
    345 
    346 int   HandlerCGIChild(int child_read,int child_write,Request*  req){
    347         //1.设置必要的环境变量
    348         char   method_env[SIZE]={0};
    349         sprintf (method_env,"REQUEST_METHOD=%s",req->method);
    350         putenv(method_env);
    351         //还需要设置QUERY_STRING或者是CONTENT_LENGTH
    352         if(strcasecmp(req->method,"GET")==0){
    353                 char  query_string_env[SIZE]={0};
    354                 sprintf (query_string_env,"QUERY_STRING=%s",req->query_string);
    355                 putenv(query_string_env);
    356 
    357         }else {
    358                 char content_length_env[SIZE]={0};
    359                 sprintf (content_length_env,"CONTENT_LENGTH=%d",req->content_length);
    360                 putenv(content_length_env);
    361 
    362         }
    363         //2把标准输入输出重定向到管道里
    364         dup2(child_read,0);
    365         dup2(child_write,1);
    366         //3对子进程进行程序替换
    367         //  url_path: /cgi-bin/test
    368         //  file_path: ./wwwroot/cgi-bin/test
    369         char   file_path[SIZE]={0};
    370         HandlerFilePath(req->url_path,file_path);
    371 
    372         execl(file_path,file_path,NULL);
    373         exit(1);//当程序替换失败时。一定让子进程终止,如果子进程不终止,因为父子进程是同一块代码,父进程一直在Listen状态一直等待,子进程也会等待,所以要直接结束进程,避免一直等待端口数据的返回;
    374 //有  exec   l    lp  le   v   vp   ve,首先看知不知道可执行文件的完整路径 ,如有就可以不带P,因为P是在PATH中找,再看要不要环境变量,如果不用,就不需要带e,这里因为通过putenv ()的方式直接设到了ENV里面所以不用,说一直接用execl()
    375         return 200;
    376 }
    377 
    378 
    379 
    380 int HandlerCGI(int  new_sock,Request  *req)
    381 {
    382         int err_code=200;
    383         //1.创建一对匿名管道
    384         int fd1[2],fd2[2];
    385         int ret =pipe(fd1);
    386         if (ret<0)
    387         {
    388                 return 404;
    389         }
    390         ret=pipe (fd2);
    391         if (ret<0)
    392         {
    393                 close(fd1[0]);
    394                 close(fd1[1]);
    395                 return 404;
    396         }
    397         //fd1,fd2这种变量名的描述性太差,后面直接用的话
    398         //是非常容易弄混的,所以直接在此处定义几个
    399         //更加明确的变量名来描述该文件描述符的用途
    400         int   father_read=fd1[0];
    401         int   child_write=fd1[1];
    402         int   father_write=fd2[1];
    403         int   child_read=fd2[0];
    404         //2.创建子进程
    405         ret=fork();
    406         //3.父子进程各执行不同的逻辑
    407         if (ret>0){
    408 
    409                 //father
    410                 //此处父进程优先关闭这两个管道的文件描述符
    411                 //是为了后续父进程从子进程这里读数据时,能够读到EOF,对于管道来说,所有写端关闭,继续读,才有EOF,而此时所有写端,一方面是父进程需要关闭,另一方面子进程也需要关闭。所以此处父进程先关闭不必要的写端之后,后续子进程用完了
    412                 //直接关闭,父进程也就读到了EOF
    413                 close (child_read);
    414                 close(child_write);
    415                 err_code=HandlerCGIFather(new_sock,father_read,father_write,ret,req);
    416                 close(father_read);
    417                 close(father_write);
    418         }else if (ret==0){
    419 
    420                 //child
    421                 close (father_read);
    422                 close (father_write);
    423                 err_code=HandlerCGIChild(child_read,child_write,req);
    424                 //此处代码写上也执行不到,所以不用写close文件描述符
    425 
    426         }else{
    427                 perror("fork");
    428                 close (fd1[0]);
    429                 close (fd1[1]);
    430                 close (fd2[0]);
    431                 close (fd2[1]);
    432                 return  404;
    433         }
    434         //4.收尾工作和错误处理
    435         return 200;
    436 }
    437 
    438 
    439 
    440 void  HandlerRequest(int new_sock)
    441 {
    442         int err_code=200;//错误码初始200,默认没错
    443         //1.读取并解析请求(反序列化)
    444         Request req;
    445         memset (&req,0,sizeof (req));
    446         //a)从socket中读取出首行
    447         if (ReadLine(new_sock,req.first_line,sizeof (req.first_line))<0)
    448         {
    449                 //失败处理
    450                 err_code =404;
    451                 goto END;
    452         }
    453         //b)解析首行,从首行中解析出url和method
    454         if (ParseFirstLine(req.first_line,&req.url,&req.method))
    455         {
    456                 //失败处理
    457                 err_code =404;
    458                 goto END;
    459 
    460         }
    461         //c)解析url,从url中解析出url_path和query_string
    462         if (ParseQueryString(req.url,&req.url_path,&req.query_string))
    463         {
    464                 //失败处理
    465                 err_code =404;
    466                 goto END;
    467 
    468         }
    469         //d)解析Header,丢弃了大部分header,只读取Content—Length
    470         if (ParseHeader(new_sock,&req.content_length))
    471         {
    472                 //失败处理
    473                 err_code =404;
    474                 goto END;
    475 
    476         }
    477 
    478 
    479         PrintRequest (&req);
    480         //2.静态/动态方式生成页面
    481         //3.把生成结果写回客户端上
    482         if (strcasecmp(req.method,"GET")==0&&req.query_string==NULL)
    483         {
    484                 //a)如果是GET方式请求,并且没有query_string,
    485                 //那么返回静态页面
    486                 err_code=HandlerStaticFile(new_sock,&req);
    487         }else  if (strcasecmp(req.method,"GET")==0&&req.query_string!=NULL)
    488         {
    489                 //b)如果是GET方式请求,并且有query_string,
    490                 //那么返回动态页面
    491                 err_code=HandlerCGI(new_sock,&req);
    492         }else  if (strcasecmp(req.method,"POST")==0)
    493         {
    494                 //c)如果请求是POST类型的(一定是带参的,参数是通过body来传给服务器的),那么也返回动态页面
    495                 err_code=HandlerCGI(new_sock,&req);
    496         }else
    497         {
    498                 //失败处理
    499                 err_code =404;
    500                 goto END;
    501         }
    502 
    503         //错误处理;直接返回一个404的HTTP响应
    504 END:
    505         if (err_code !=200)
    506         {
    507                 Handler404(new_sock);
    508         }
    509         close(new_sock);
    510         return;
    511 
    512 }
    513 
    514 
    515 
    516 
    517 void *ThreadEntry(void*arg)//   因为在64位机中指针占8个字节,如果强转回int会有丢失数据的危险
    518 {
    519         int64_t new_sock=(int64_t )arg;
    520         //使用HandlerRequest完成具体的处理请求过程
    521         //相当于线程入口函数只有一个包装,真正干活的是这个函数,这样最大的好处还是解耦和
    522         //一旦需要把服务器改成多进程或者IO多路复用的形式
    523         //代码的改动都是比较小的
    524         HandlerRequest(new_sock);
    525         return NULL;
    526 }
    527 
    528 
    529 
    530 
    531 //服务器启动
    532 void HttpServerStart(const  char*  ip,short   port)
    533 {
    534         int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    535         if (listen_sock<0)
    536         {
    537                 perror("socket");
    538                 return ;
    539         }
    540         int opt=1;
    541         setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof (opt));//加上这个函数是为了使端口处于TIME-WAIT时复用地址
    542         sockaddr_in addr;
    543         addr.sin_family=AF_INET;
    544         addr.sin_addr.s_addr=inet_addr(ip);
    545         addr.sin_port=htons(port);
    546 
    547         int ret=bind(listen_sock,(sockaddr*)&addr,sizeof (addr));
    548         if (ret<0)
    549         {
    550                 perror("bind");
    551                 return ;
    552         }
    553         ret=listen(listen_sock,5);
    554         if (ret<0)
    555         {
    556                 perror("listen");
    557                 return ;
    558         }
    559         printf ("ServerInit  OK\n");
    560         while (1)
    561         {
    562 
    563                 sockaddr_in peer;
    564                 socklen_t  len=sizeof (peer);
    565                 printf ("3\n");
    566                 int64_t   new_sock=accept(listen_sock,(sockaddr*)&peer,&len);
    567                 printf ("2\n");
    568                 if (new_sock<0)
    569                 {
    570                         perror("accept");
    571                         continue;
    572                 }
    573                 //使用多线程的方式来实现TCP服务器
    574                 pthread_t  tid;
    575                 printf ("ThreadEntry\n");
    576                 pthread_create(&tid,NULL,ThreadEntry,(void *)new_sock);
    577                 pthread_detach(tid);
    578         }
    579 
    580 }
    581 
    582 
    583 
    584 
    585 //主函数的参数   ./http_server  [ip]   [port]
    586 int main (int argc,char*   argv[])
    587 {
    588         if (argc!=3)
    589         {
    590                 printf ("Usage   ./http_server  [ip]   [port]\n");
    591                 return 1;
    592         }
    593         HttpServerStart( argv[1],atoi(argv[2]));
    594         return 0;
    595 }
    
    
    
                                                                                                                                                                                                                                                                             
    
    
    
    
    
    
    
    
    
                                                                                                                                                                                                                                                                          
    
                                                                                                                                                                                                                                                                             
    
    
                                                                                                                                                                                            
                                                                                                                                                                                   
    
    

    /home/a/Desktop/Mycode/http_server/http_server.h

      1 #pragma  once
      2 
      3 #define SIZE   10240
      4 //我们需要一个结构体来解析http协议请求
      5 typedef  struct   Request
      6 {
      7 //解析首行
      8 char first_line[SIZE];
      9 char *method;// [SIZE];//方法
     10 char *url;//[SIZE]; //URL
     11 //char  *version;//[SIZE];//版本号,在这里我们暂时用不上
     12 char *url_path;//带层次的文件路径,重点关注的对象内容1
     13 char *query_string;//查询字符串,重点关注的内容2
     14 //接下来解析header部分。
     15 //此处需要使用二叉搜索树或hash表
     16 //这里我们偷懒一下,其他的header都不要了,只保留一个Content——Length
     17 int content_length;
     18 }Request;
     19 /*typedef  struct   Request
     20 {
     21 char first_line[SIZE];//首行
     22 char method [SIZE];//方法
     23 char url[SIZE]; //URL
     24 char version[SIZE];//版本号
     25 char url_path[SIZE];//带层次的文件路径
     26 char query_string[SIZE];//查询字符串
     27 }Request;
     28 */

    /home/a/Desktop/Mycode/http_server/Makefile

      1 http_server:http_server.c
      2         gcc  $^  -o  $@  -lpthread
      3 
      4 
      5 .PHONY:clean
      6 clean:
      7         rm  http_server

    /home/a/Desktop/Mycode/http_server/wwwroot/index.html

    <h1>Hello   world</h1>
    <img  src="/image/1.png">

    /home/a/Desktop/Mycode/http_server/wwwroot/Makefile  

    select:select.c
            gcc  $^  -o  $@  -L  /usr/lib64/mysql  -lmysqlclient
    .PHONY:clean
    clean:
            rm select

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/calc.c

    
    
      1 //实现一个用来简单计算的CGI程序
      2 
      3 
      4 
      5 
      6 
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <unistd.h>
     10 #include <string.h>
     11 #define  SIZE  (1024*10)
     12 
     13 
     14 void  GetQueryString(char  output[]){
     15         //按照CGI的协议来实现此处的逻辑
     16         //1.先获取到方法
     17         char* method=getenv("REQUEST_METHOD");
     18         if (method==NULL){
     19                 //没有获取到环境变量
     20                 fprintf(stderr,"REQUEST_METHOD  filed\n");
     21                 return ;
     22         }
     23         if (strcmp(method,"GET")==0){
     24                 //获取QUERY_STRING
     25                 char *query_string =getenv("QUERY_STRING");
     26                 if (query_string==NULL){
     27                         fprintf(stderr,"QUERY_STRING  failed\n");
     28                         return ;
     29                 }
     30                 strcpy(output,query_string);
     31         }else{
     32 //post
     33 //获取CONTENT_LENGTH
     34 char*  content_length_env=getenv("CONTENT_LENGTH");
     35 if (content_length_env==NULL){
     36 fprintf(stderr,"CENTENT_LENGTH   failed\n");
     37 return ;
     38 }
     39 int content_length=atoi(content_length_env);
     40 int i=0;
     41 for (;i<content_length;++i){
     42 char  c='\0';
     43 read(0,&c,1);
     44 output[i]=c;
     45 }
     46 return ;
     47 }
     48 }
     49 
     50 int main (){
     51         //1.基于CGI协议获取到需要的参数
     52         char  query_string[SIZE]={0};
     53         GetQueryString(query_string);
     54         //2.根据业务逻辑进行计算
     55         //此时时获取到的QUERY_STRING的形式如下
     56         //a=10&b=20
     57         int a=0;
     58         int b=0;
     59         sscanf(query_string ,"a=%d&b=%d\n",&a,&b);
     60         int sum =a+b;
     61         //3.把结果构造成HTML写回到标准输出
     62         printf ("<html><h1>sum=%d</h1></html>",sum);
     63         return 0;
     64 }
    
    

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/index.html

    
      4 <html>
      5 <form action="/calc/calc"method="POST">
      6 a:<br>
      7 <input type="text" name="a" >
      8 <br>
      9 b:<br>
     10 <input type="text" name="b" >
     11 <br><br>
     12 <input type="submit" value="Submit">
     13 </form>
     14 </html>
    

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/Makefile

      1 calc:calc.c
      2         gcc  $^  -o  $@
      3 
      4 .PHONY:clean
      5 clean:
      6         rm  calc

    /home/a/Desktop/Mycode/http_server2/wwwroot/image/1.png 这是一张图片

    /home/a/Desktop/Mycode/http_server2/wwwroot/mysql/select.c

      1 //本程序基于MySQL  API完成对数据库的查询操作
      2 //不管用户输入什么,都尝试把TestTable这个表里的所有数据都查询出来,展示到以页面上
      3 //注意这个CGI存在的问题是,如果访问时不加参数会当成要下载的静态页面,所以访问时应该在后面加上一个参数虽然我们不用,原因是我们前面判断是动态页面还是静态页面的///根据以下步骤1,先看方法,Get方法的QUERY_STRING是否为空,为空时静态页面>    ,不为空时,是动态页面,post请求直接是动态页面
      4 //改进通过stat函数判断是否是可执行程序。
      5 
      6 
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <mysql/mysql.h>
     10 
     11 
     12 int main (){
     13         //1.获取到CGI程序需要的相关参数(此处可以不获取)
     14         //2.调用MySQL  API 来访问和操作数据库
     15         //(a)创建一个MySQL的连接句柄
     16         MYSQL * connect_fd=mysql_init(NULL);
     17         //(b)和数据库服务器建立连接
     18         if (mysql_real_connect(connect_fd,"192.168.0.104","root"," ","TestDB",3306,NULL,0)==NULL){
     19                 fprintf(stderr,"mysql_real_connect  failed\n");
     20                 return 1;
     21         }
     22         fprintf(stderr,"mysql_real_connect  ok\n");
     23         //(c)构造SQL语句,此处不需要加分号,但在SQL语句时是要加的
     24         const char* sql="select *from TestTable";
     25                 //(d)把构造的SQL语句发上给Mysql 服务器,并执行
     26                 int ret=mysql_query(connect_fd,sql);
     27         if (ret<0){
     28                 fprintf(stderr,"mysql_query  failed\n");
     29                 return 1;
     30         }
     31         //(e)解析和遍历返回结果()结果是一个表格
     32         MYSQL_RES*  result=mysql_store_result(connect_fd);
     33         //先获取行数和列数
     34         int rows=mysql_num_rows(result);
     35         int fields=mysql_num_fields(result);
     36         //获取表头(每列的意思)
     37         printf(<html>\n);
     38         printf("rows=%d,fields=%d<br>\n",rows,fields);
     39         MYSQL_FIELD*  field=mysql_fetch_field(result);//一次只取一列的表头
     40         while (field!=NULL){
     41                 printf("%s\t",field->name );
     42                 field=mysql_fetch_field(result);//取下一列的表头
     43         }
     44         printf("<br>");
     45         //按行获取表的具体内容
     46         int i=0;
     47         for (;i<rows;i++){
     48                 MYSQL_ROW  row=mysql_fetch_row(result);//每调用一次就取一行
     49                 int j=0;
     50                 for (;j<fields;++j){
     51                         printf("%s\t",row[j]);
     52                 }
     53                 printf ("<br>");
     54         }
     55         printf("</html>\n");
     56         //(f)断开连接
     57         mysql_close(connect_fd);
     58         //3。把获取的数据组织成html 返回给客户端
     59         return ;
     60 }
    

    基于HTTP服务器实现的业务:

    (1)用户通过浏览器网页(静态页面)提供给服务器一个日期和一个天数(N)。

    (2)服务器会对日期和天数进行计算(动态页面),计算出这个日期N天后的日期。

    (3)通过浏览器返回,以便用户更合理的安排时间。

    日期类我写在了另一篇博客https://blog.csdn.net/a15929748502/article/details/81369350欢迎查阅

    下面是我的测试;

    及结果:

     

    展开全文
  • 如何搭建HTTP服务、网站?...使用C语言服务器 1.1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <sys/socket.h> #i...

    如何搭建HTTP服务、网站?——最全的搭建HTTP服务、网站的教程:第一篇
    MacOS如何搭建网站:
    使用C语言服务器
    1.1
    注:请勿盲目拷贝(Copy)代码!
    部分请修改!!!
    大家可以使用便捷路径提供服务哦:
    当前目录:.
    当前目录下的某个文件夹:./(文件夹名称)/
    上级目录:…/
    上级目录的上级目录:…/…/

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #define PORT /*侦听端口,请修改!若无法在80端口提供服务,请先终止apache服务器!*/		//监听的端口号
    #define MAX_CONNECT 128 	//最大连接数
    #define BUFFER_SIZE 1024*32 	//32k缓冲区满足绝大多数网页请求
    #define NAME_BUFFER_SIZE 1024	//路径缓冲 
    #define HTTP_END "\r\n\r\n"		//http协议头以两个回车换行结尾
    #define BASE_DIR "/*网站路径(请修改!!!)*/"	//服务器根目录设为当前程序运行路径
    #define DEFAULT_PAGE "/index.html"	//默认页面
    #define HTTP_OK "HTTP/1.1 200 OK\r\n\r\n"	//http正常回应
    
    
    static void *doAccept(void * parm);           
    
    //做协议解析 
    static void *doProc(void *data);
    
    int writeFile(int fd,char *filePath);
    
    int main()
    {
    	pthread_t thread;
    	printf("start!\n");
    	printf("按回车键退出!\n");
    	bzero(&thread,sizeof(thread));
    	pthread_create(&thread,NULL,doAccept,NULL);
    	getchar();
    	printf("end!\n");
    	return 0;
    }
    
    static void *doAccept(void * parm)
    {
    	int socketfd,connectfd;
    	pthread_t thread;
    	struct sockaddr_in serverAddr;
    	pthread_t pthread;
    	signal(SIGCHLD,SIG_IGN);
    	signal(SIGPIPE,SIG_IGN);
    	socketfd = socket(AF_INET,SOCK_STREAM,0);
    	if(socketfd == -1)
    	{
    		printf("创建套接字失败!\n");
    	}
    	else
    	{
    		printf("创建套接字成功!\n");
    		bzero(&serverAddr,sizeof(serverAddr));
    		serverAddr.sin_family=AF_INET;
    		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    		serverAddr.sin_port = htons(PORT);
    		if( -1==bind(socketfd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr)) )
    		{
    			//FIXME 绑定端口失败时发生段错误! 
    			printf("绑定端口失败!\n");
    		}
    		else
    		{
    			printf("绑定端口成功!\n");
    			if(-1==listen(socketfd,MAX_CONNECT))
    			{
    				printf("创建监听失败!\n");
    			}
    			else
    			{
    				printf("创建监听成功!\n");
    				printf("在浏览器中输入http://127.0.0.1:%d来访问本服务器\n",PORT);
    				while(1)
    				{
    					//sleep(1);
    					socklen_t socketLen=0;
    					connectfd = accept(socketfd,(struct sockaddr*)&serverAddr,&socketLen);
    					if(connectfd<=0)
    					{
    						printf("接受连接失败!\n");
    					}
    					else
    					{
    						printf("接受连接成功!\n");
    						bzero(&thread,sizeof(thread));
    						pthread_create(&thread,NULL,doProc,(void*)connectfd);
    					}
    				}
    			}
    		}
    		
    	}
    	return NULL;
    }
    
    //做协议解析 
    static void *doProc(void *data)
    {
    	int connectfd = (int)data;
    	char *buffer = (char *)malloc(BUFFER_SIZE+1);
    	if(buffer==NULL)
    		printf("malloc return NULL!");
    	int readLen=read(connectfd,buffer,BUFFER_SIZE);
    	if(readLen<=0)
    		goto END;
    	buffer[readLen]=0;
    	//printf("%s",buffer);
    	int endLen = strlen(HTTP_END);
    	if( (readLen>=endLen) && (strcmp(buffer+readLen-endLen,HTTP_END)==0) )
    	{//是http协议
    		printf("%s",buffer);
    		char *startPos=strstr(buffer,"GET ");
    		
    		if(startPos!=NULL)
    		{
    			startPos+=4;
    			char *endPos=strstr(startPos," ");
    			if(endPos!=NULL&&(endPos-startPos)<NAME_BUFFER_SIZE)
    			{
    				char *nameBuffer=malloc(NAME_BUFFER_SIZE+1);
    				if(nameBuffer==NULL)
    					printf("malloc return NULL!");
    				nameBuffer[0]=0;
    				strcat(nameBuffer,BASE_DIR);
    				strncat(nameBuffer,startPos,endPos-startPos);
    				if(nameBuffer[strlen(nameBuffer)-1]=='/')
    				{
    					printf("is_dir,use default page.\n");
    					strcat(nameBuffer,DEFAULT_PAGE);
    				}
    				/*
    				//判断路径是否为文件夹,貌似不安全 
    				struct stat st;
    				stat(nameBuffer,&st);
    				if (S_ISDIR(st.st_mode))
    				{
    					printf("is_dir,use default page.\n");
    					strcat(nameBuffer,DEFAULT_PAGE);
    				}*/
    				printf("GET:%s\n",nameBuffer);
    				writeFile(connectfd,nameBuffer);
    				free(nameBuffer);
    			}
    		}
    	}
    END:
    	//printf("end doProc\n");
    	free(buffer);
    	close(connectfd);
    	return NULL;
    }
    
    int writeFile(int fd,char *filePath)
    {
    	//printf("write file:%s\n",filePath);
    	int fileHandler=open(filePath,0,O_RDONLY);
    	if(fileHandler<=0)
    		return -1;
    	char *buffer=(char *)malloc(BUFFER_SIZE+1);
    	if(buffer==NULL)
    		printf("malloc return NULL!");
    	write(fd,HTTP_OK,strlen(HTTP_OK));
    	int readLen=0;
    	while( 0<(readLen=read(fileHandler,buffer,BUFFER_SIZE)) )
    	{
    		write(fd,buffer,readLen);
    	}
    	
    	free(buffer);
    	close(fileHandler);
    	return 0;
    }
    

    使用apache服务器核心
    1.1

    打开apache服务器
    如何打开:

    	sudo apachectl start
    

    如何关闭:

    	sudo apachectl stop
    

    如何重启:

    sudo apachectl restart
    

    1.2
    测试访问:
    1.localhost

    2.127.0.0.1

    3.0.0.0.0
    如果访问提示It’s Works!
    则第一步成功了。
    2.1
    放入一个个网页文件:
    apache服务器的默认目录是/Library/WebServer/Documents
    大家按下command、Shift和+
    输入/Library/WebServer/Documents
    再回车即可打开/Library/WebServer/Documents
    注意:若您需要向/Library/WebServer/Documents放入网页,
    请打开终端,输入sudo chmod 777 /Library/WebServer/Documents
    或者在放入网页时输入密码。
    好了,本文结束,希望能帮到您。
    下期预告:配置httpd.conf修改错误代码,如404、403等,并且修改网站路径。

    展开全文
  • 【说明】转自:kikilizhm  http://blog.csdn.net/kikilizhm/article/details/7840719#comments ...自己的编程爱好也是从...不过离开大学之后很久没有碰过WEB了,最近看到嵌入式中的涉及到的web服务器,了解到了CG
  • 要想做好一个程序员,基本的服务器构架的能力还是需要有的。除了在本地开发,我们还要学会如何去部署我们开发好的系统。由于Linux的开源免费且功能强大,性能卓越。很多企业都会选择将服务部署在Linux系统上,所以...
  • 搭建简易的c语言CGI和Apache服务器的开发环境 http://www.cnblogs.com/tt-0411/archive/2011/11/21/2257203.html python配置apache的web服务器方法(python的CGI配置) ...
  • 搭建Nginx服务器

    2017-11-28 16:02:47
    背景:最近学习了如何搭建Nginx服务器,学习贵在总结,总结... 是一个使用c语言开发的高性能的http服务器及反向代理服务器。  Nginx是一款高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由
  • 要使用MPMoviePlayerController控件,必须采用Http请求的形式去请求数据,而TS流的来源是通过HTTP请求拿到的,需要再把这个拿到的TS流发送给IOS播放器,所以试图自己搭建一个HTTP服务器,为此学习了Mongoose的源码,...
  • 背景:最近学习了如何搭建Nginx服务器,... 是一个使用c语言开发的高性能的http服务器及反向代理服务器。  Nginx是一款高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计
  • 搭建nginx服务器

    千次阅读 2017-10-11 20:16:37
    这里使用的是百度云的服务器,CentOS7系统的nginx:Nginx代码完全用 C语言从头写成Nginx (engine x) 是一个高性能的HTTP和反向代理服务器下载nginxNginx官网:http://nginx.org/en/下载nginx:...下命令:wget ...
  • Linux上搭建Nginx服务器

    千次阅读 2017-02-25 16:46:16
    1.将nginx的压缩包nginx-1.8.0.tar.gz上传到Linux服务器2.由于nginx是C语言开发的并且我们这里是通过编译nginx的源码来安装nginx,所以Linux上要安装C语言的编译环境gcc, 如果已经安装此步可以省略,否则执行命令:yum...
  • 1.将nginx的压缩包nginx-1.8.0.tar.gz上传到Linux服务器 2.由于nginx是C语言开发的并且我们这里是通过编译nginx的源码来安装nginx,所以Linux上要安装C语言的编译环境gcc, 如果已经安装此步可以省略,否则执行命令: ...
  • 4、搭建web服务器nginx

    2020-10-25 11:50:14
    Nginx(发音同engine x)是一个异步的web服务器,主要提供Web服务、反向代理、负载均衡和HTTP缓存功能。由Igor Sysoev创建于2004年,使用C语言开发。 运行nginx容器 拉取:docker pull nginx:1.17.9 运行:docker ...
  • C++利用mongoose搭建web服务器

    千次阅读 2020-04-16 23:04:14
    Mongoose是c语言写成的网络库。它为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞api。其具有以下特性: 跨平台:可在linux/unix macos QNX eCos Windows Android Iphone FreeRtos上运行; ...
  • 具体搭建步骤如下:第一步:下载Nginx源码包,地址:http://nginx.org/  (我这里下载的是1.8.0版本的)第二步:将源码包上传至Linux服务器第三步:由于Nginx是用C语言开发的,编译依赖gcc环境,如果没有,需要安装...
  • 环境准备 操作系统CentOS-6.5 Nginx版本nginx-1.16.1 ...yum -y install gcc (编译C语言) yum -y install pcre-devel(是perl库包含perl兼容的正则表达式库,nginx的http模块使用pcre来解析正则表达式)...
  • 起因 想把手机(ios)中的部分文本传输给电脑(win),又不想登陆社交软件,想找一个跨越操作系统的文本...使用http协议:在电脑上搭建web服务器,写带文本框的页面,手机打开并用post方法提交文本,电脑这边consol...
  • 作为长期使用C语言开发网络游戏服务器的程序员,云风是有理由写这样一篇文字,不过还是感觉谈的不够深入,C语言在业界使用的现状没有怎么描写,有些意犹未尽。在这里想比较系统的谈谈个人对C语言学习方式方法的理解...
  • 非常感谢 http://oldcat1981.blog.51cto.com/10670523/1766810 ... FastDFS是一款类Google FS的开源分布式文件系统,它用纯C语言实现,支持Linux、FreeBSD、AIX等UNIX系统。它只能通过 专有API对文件进...
  • C语言学习

    千次阅读 2012-08-23 00:40:34
    作为长期使用C语言开发网络游戏服务器的程序员,云风是有理由写这样一篇文字,不过还是感觉谈的不够深入,C语言在业界使用的现状没有怎么描写,有些意犹未尽。在这里想比较系统的谈谈个人对C语言学习方式方法的理解...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 133
精华内容 53
关键字:

c语言搭建http服务器

c语言 订阅