如何用PHP来实现一个动态Web服务器


Posted in PHP onJuly 29, 2015

要是现实一个 web 服务器,那么就需要大概了解 web 服务器的运行原理。先从静态的文本服务器开始,以访问 web 服务器的1.html为例

1.客户端通过发送一个 http 请求到服务器,如果服务器监听的端口号是9002,那么在本机自身测试访问的地址就是http://localhost:9002/1.html。

2.服务器监听着9002端口,那么在收到请求了请求之后,就能从 http head 头中获取到请求里需要访问的 uri 资源在web 目录中的位置。

3.服务器读取需要访问的资源文件,然后填充到 http 的实体中返回给客户端。

示意图如下:

如何用PHP来实现一个动态Web服务器

<?php
class web_config {
  // 监听的端口号
  const PORT = 9003;
  // 项目根目录
  const WEB_ROOT = "/Users/zhoumengkang/Documents/html";
}


class server {
  private $ip;
  private $port;
  public function __construct($ip, $port) {
    $this->ip = $ip;
    $this->port = $port;
    $this->await();
  }
  private function await() {
    $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if ($sock < 0) {
      echo "Error:" . socket_strerror(socket_last_error()) . "\n";
    }
    $ret = socket_bind($sock, $this->ip, $this->port);
    if (!$ret) {
      echo "BIND FAILED:" . socket_strerror(socket_last_error()) . "\n";
      exit;
    }
    echo "OK\n";
    $ret = socket_listen($sock);
    if ($ret < 0) {
      echo "LISTEN FAILED:" . socket_strerror(socket_last_error()) . "\n";
    }
    do {
      $new_sock = null;
      try {
        $new_sock = socket_accept($sock);
      } catch (Exception $e) {
        echo $e->getMessage();
        echo "ACCEPT FAILED:" . socket_strerror(socket_last_error()) . "\n";
      }
      try {
        $request_string = socket_read($new_sock, 1024);
        $response = $this->output($request_string);
        socket_write($new_sock, $response);
        socket_close($new_sock);
      } catch (Exception $e) {
        echo $e->getMessage();
        echo "READ FAILED:" . socket_strerror(socket_last_error()) . "\n";
      }
    } while (TRUE);
  }
  /**
   * @param $request_string
   * @return string
   */
  private function output($request_string){
    // 静态 GET /1.html HTTP/1.1 ...
    $request_array = explode(" ",$request_string);
    if(count($request_array) < 2){
      return $this->not_found();
    }
    $uri = $request_array[1];
    $filename = web_config::WEB_ROOT . $uri;
    echo "request:".$filename."\n";
    // 静态文件的处理
    if (file_exists($filename)) {
      return $this->add_header(file_get_contents($filename));
    } else {
      return $this->not_found();
    }
  }
  /**
   * 404 返回
   * @return string
   */
  private function not_found(){
    $content = "

<h1>File Not Found </h1>

";
    return "HTTP/1.1 404 File Not Found\r\nContent-Type: text/html\r\nContent-Length: ".strlen($content)."\r\n\r\n".$content;
  }
  /**
   * 加上头信息
   * @param $string
   * @return string
   */
  private function add_header($string){
    return "HTTP/1.1 200 OK\r\nContent-Length: ".strlen($string)."\r\nServer: mengkang\r\n\r\n".$string;
  }
}
$server = new server("127.0.0.1", web_config::PORT);

如上代码所述,只要在终端执行该文件,那么一个静态的 web 服务器就启动啦。

下图为我访问我 web 目录下的1.jpg文件的截图

如何用PHP来实现一个动态Web服务器

简单的静态 web 服务器已经完成了,下面的问题就是怎么让其支持动态内容的输出了。是不是只需要在 web 服务器内部执行完某个程序之后,把得到的结果返回给客户端就行呢?但是这样 web 服务器的代码就和业务代码耦合在一起了,怎么解决一个 web 服务器,可以运用在各个业务场景下呢?

CGI 的出现解决了这一问题。那么 CGI 是什么呢?下面这段话复制的:

CGI是外部应用程序(CGI程序)与Web服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的规程。CGI规范允许Web服务器执行外部程序,并将它们的输出发送给Web浏览器,CGI将Web的一组简单的静态超媒体文档变成一个完整的新的交互式媒体。

好晕,举个具体的例子,比如我们在使用的 PHP 的全局变量$_SERVER['QUERY_STRING']就是 Web 服务器通过 CGI 协议之上,传递过来的。例如在 Nginx 中,也许你记得这样的 fastcgi 配置

fastcgi_param  QUERY_STRING       $query_string;

没错 nginx 把自己的全局变量$query_string传递给了 fastcgi_param 的环境变量中。

下面我们也以CGI的QUERY_STRING作为桥梁,将客户端请求的 uri 中的信息传递到 cgi 程序中去。通过putenv的方式把QUERY_STRING存入该请求的环境变量中。

我们约定 Web 服务器中访问的资源是.cgi后缀则表示是动态访问,这一点有点儿类似于 nginx 里配置 location 来寻找 php 脚本程序一样。都是一种检查是否应该请求 cgi 程序的规则。为了和 Web 服务器区别开来,我用 C 写了一个查询用户信息的 cgi 程序,根据用户 id 查询用户资料。

大致的访问逻辑如下图

如何用PHP来实现一个动态Web服务器

演示代码地址:https://github.com/zhoumengkang/php/tree/master/php-webserver/dynamic

如果要运行该 demo 需要做如下操作

1.修改config.php里的项目根目录WEB_ROOT

2.编译cgi-demo\user.c,编译命令gcc -o user.cgi user.c,然后将user.cgi文件放入你配置的项目根目录下面

3.在终端执行php start.php ,这样该 web 服务器就启动了

4.通过 http://localhost:9003/user.cgi?id=1 就可以访问看到如下效果了

如何用PHP来实现一个动态Web服务器

其实只是在静态服务器的基础上做了一些 cgi 的判断是请求的转发处理,把github 上的三个文件的代码合并到一个文件里方便大家观看

<?php
class web_config {

  // 监听的端口号
  const PORT = 9003;

  // 项目根目录
  const WEB_ROOT = "/Users/zhoumengkang/Documents/html";

  // 系统支持的 cgi 程序的文件扩展名
  const CGI_EXTENSION = "cgi";

}

class server {
  private $ip;
  private $port;
  public function __construct($ip, $port) {
    $this->ip = $ip;
    $this->port = $port;
    $this->await();
  }

  private function await() {
    $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if ($sock < 0) {
      echo "Error:" . socket_strerror(socket_last_error()) . "\n";
    }

    $ret = socket_bind($sock, $this->ip, $this->port);
    if (!$ret) {
      echo "BIND FAILED:" . socket_strerror(socket_last_error()) . "\n";
      exit;
    }
    echo "OK\n";

    $ret = socket_listen($sock);
    if ($ret < 0) {
      echo "LISTEN FAILED:" . socket_strerror(socket_last_error()) . "\n";
    }

    do {
      $new_sock = null;
      try {
        $new_sock = socket_accept($sock);
      } catch (Exception $e) {
        echo $e->getMessage();
        echo "ACCEPT FAILED:" . socket_strerror(socket_last_error()) . "\n";
      }
      try {
        $request_string = socket_read($new_sock, 1024);
        $response = $this->output($request_string);
        socket_write($new_sock, $response);
        socket_close($new_sock);
      } catch (Exception $e) {
        echo $e->getMessage();
        echo "READ FAILED:" . socket_strerror(socket_last_error()) . "\n";
      }
    } while (TRUE);
  }
  /**
   * @param $request_string
   * @return string
   */
  private function output($request_string){
    // 静态 GET /1.html HTTP/1.1 ...
    // 动态 GET /user.cgi?id=1 HTTP/1.1 ...
    $request_array = explode(" ",$request_string);
    if(count($request_array) < 2){
      return "";
    }
    $uri = $request_array[1];
    echo "request:".web_config::WEB_ROOT . $uri."\n";
    $query_string = null;
    if ($uri == "/favicon.ico") {
      return "";
    }
    if (strpos($uri,"?")) {
      $uriArr = explode("?", $uri);
      $uri = $uriArr[0];
      $query_string = isset($uriArr[1]) ? $uriArr[1] : null;
    }
    $filename = web_config::WEB_ROOT . $uri;
    if ($this->cgi_check($uri)) {

      $this->set_env($query_string);
      $handle = popen(web_config::WEB_ROOT.$uri, "r");
      $read = stream_get_contents($handle);
      pclose($handle);
      return $this->add_header($read);
    }
    // 静态文件的处理
    if (file_exists($filename)) {
      return $this->add_header(file_get_contents($filename));
    } else {
      return $this->not_found();
    }
  }
  /**
   * 设置环境变量 给 cgi 程序使用
   * @param $query_string
   * @return bool
   */
  private function set_env($query_string){
    if($query_string == null){
      return false;
    }
    if (strpos($query_string, "=")) {
      putenv("QUERY_STRING=".$query_string);
    }
  }
  /**
   * 判断请求的 uri 是否是合法的 cgi 资源
   * @param $uri
   * @return bool
   */
  private function cgi_check($uri){
    $info = pathinfo($uri);
    $extension = isset($info["extension"]) ? $info["extension"] : null;
    if( $extension && in_array($extension,explode(",",web_config::CGI_EXTENSION))){
      return true;
    }
    return false;
  }
  /**
   * 404 返回
   * @return string
   */
  private function not_found(){
    $content = "<h1>File Not Found </h1>";
    return "HTTP/1.1 404 File Not Found\r\nContent-Type: text/html\r\nContent-Length: ".strlen($content)."\r\n\r\n".$content;
  }
  /**
   * 加上头信息
   * @param $string
   * @return string
   */
  private function add_header($string){
    return "HTTP/1.1 200 OK\r\nContent-Length: ".strlen($string)."\r\nServer: mengkang\r\n\r\n".$string;
  }
}

$server = new server("127.0.0.1", web_config::PORT);

以上就是PHP实现一个动态Web服务器的全部实现过程,希望对大家的学习有所帮助。

PHP 相关文章推荐
从Web查询数据库之PHP与MySQL篇
Sep 25 PHP
PHP开发过程中常用函数收藏
Dec 14 PHP
Linux下将excel数据导入到mssql数据库中的方法
Feb 08 PHP
codeigniter上传图片不能正确识别图片类型问题解决方法
Jul 25 PHP
PHP使用Session遇到的一个Permission denied Notice解决办法
Jul 30 PHP
smarty模板引擎之内建函数用法
Mar 30 PHP
PHP CURL 多线程操作代码实例
May 13 PHP
php编译安装php-amq扩展简明教程
Jun 25 PHP
PHP精确计算功能示例
Nov 29 PHP
Yii2框架实现数据库常用操作总结
Feb 08 PHP
PHP用正则匹配form表单中所有元素的类型和属性值实例代码
Feb 28 PHP
Yii2.0框架behaviors方法使用实例分析
Sep 30 PHP
php实现过滤字符串中的中文和数字实例
Jul 29 #PHP
php实现XSS安全过滤的方法
Jul 29 #PHP
php检查字符串中是否有外链的方法
Jul 29 #PHP
php数组比较实现查找连续数的方法
Jul 29 #PHP
PHP实现XML与数据格式进行转换类实例
Jul 29 #PHP
PHP获取某个月最大天数(最后一天)的方法
Jul 29 #PHP
discuz图片顺序混乱解决方案
Jul 29 #PHP
You might like
使用ThinkPHP自带的Http类下载远程图片到本地的实现代码
2011/08/02 PHP
linux中cd命令使用详解
2015/01/08 PHP
解决nginx不支持thinkphp中pathinfo的问题
2015/07/21 PHP
PHP使用缓存即时输出内容(output buffering)的方法
2015/08/03 PHP
详解JavaScript函数绑定
2013/08/18 Javascript
原生javascript图片自动或手动切换示例附演示源码
2013/09/04 Javascript
JS关闭窗口或JS关闭页面的几种代码分享
2013/10/25 Javascript
javascript实现的DES加密示例
2013/10/30 Javascript
简体中文转换繁体中文(实现代码)
2013/12/25 Javascript
ExtJS4如何给同一个formpanel不同的url
2014/05/02 Javascript
javascript搜索框点击文字消失失焦时文本出现
2014/09/18 Javascript
ExpressJS入门实例
2015/01/14 Javascript
js console.log打印对像与数组用法详解
2016/01/21 Javascript
jquery实现百叶窗效果
2017/01/12 Javascript
基于JavaScript实现新增内容滚动播放效果附完整代码
2017/08/24 Javascript
浅谈JavaScript的innerWidth与innerHeight
2017/10/12 Javascript
在vue中多次调用同一个定义全局变量的实例
2018/09/25 Javascript
Python GAE、Django导出Excel的方法
2008/11/24 Python
pycharm 使用心得(九)解决No Python interpreter selected的问题
2014/06/06 Python
Python封装shell命令实例分析
2015/05/05 Python
Python实现登录人人网并抓取新鲜事的方法
2015/05/11 Python
使用Python获取网段IP个数以及地址清单的方法
2018/11/01 Python
python 实现在无序数组中找到中位数方法
2020/03/03 Python
Python3操作读写CSV文件使用包过程解析
2020/04/10 Python
Django 5种类型Session使用方法解析
2020/04/29 Python
html5桌面通知(Web Notifications)实例解析
2014/07/07 HTML / CSS
创业培训计划书
2014/05/03 职场文书
关于护士节的演讲稿
2014/05/26 职场文书
如何写股份合作协议书
2014/09/11 职场文书
大学生考试作弊检讨书
2014/09/21 职场文书
践行三严三实心得体会
2014/10/13 职场文书
新教师个人总结
2015/02/06 职场文书
会计工作能力自我评价
2015/03/05 职场文书
学校通报表扬范文
2015/05/04 职场文书
幼儿园庆六一主持词
2015/06/30 职场文书
Alexa停服!网站排名将何去何从?目前还没有替代品。
2022/04/15 杂记