单进程服务器简陋版:

<?php
/**
* Single http server.
*
* Access http://127.0.0.1:8081
*
* @license Apache-2.0
* @author farwish
*/ $s_socket_uri = 'tcp://0.0.0.0:8081';
$s_socket = stream_socket_server($s_socket_uri, $errno, $errstr) OR
trigger_error("Failed to create socket: $s_socket_uri, Err($errno) $errstr", E_USER_ERROR); while(1)
{
while($connection = @stream_socket_accept($s_socket, 60, $peer))
{
echo "Connected with $peer. Request info...\n"; $client_request = "";
// Read until double \r
while( !preg_match('/\r?\n\r?\n/', $client_request) )
{
$client_request .= fread($connection, 1024);
} if (!$client_request)
{
trigger_error("Client request is empty!");
}
$headers = "HTTP/1.1 200 OK\r\n"
."Server: nginx\r\n"
."Content-Type: text/html; charset=utf-8\r\n"
."\r\n";
$body = "<h1>hello world</h1><br><br>";
if ((int) fwrite($connection, $headers . $body) < 1) {
trigger_error("Write to socket failed!");
}
fclose($connection);
}
}

HTTP 底层基于 TCP,所以 socket 地址指定为 tcp 协议没有问题;stream_socket_server 功能相当于执行了 socket => bind => listen,stream_socket_accept 阻塞等待 client 连接,并设置了超时时间,默认的 timeout 时间使用在 php.ini 中设置。

注意这里的错误抑制符@,抑制 accept 超时情况产生的 PHP Warning,如果用到 stream_select 也需要加错误抑制符 @ 来避免中断信号引起的 PHP Warning。

HTTP 响应报文格式包含 响应状态码、响应首部、响应主体(如果有的话),响应首部每行以 \r\n 结尾,响应头部结束单独一行 \r\n 结尾,后面就是响应主体了,响应头部加响应主体以 fwrite 写入 socket 连接,fclose 关闭连接。

注意:上面的简陋版既没有设置 socket 上下文选项,也没有使用 I/O 复用,更不是多进程的,只是作为请求响应的演示。

较为严谨的HTTP协议处理版:

        $method             = '';
$url = '';
$protocol_version = ''; $request_header = [];
$content_type = 'text/html; charset=utf-8';
$content_length = 0;
$request_body = '';
$end_of_header = false; // @see http://php.net/manual/en/function.fread.php
$buffer = fread($connection, 8192); if (false !== $buffer) { // Http request format check.
if (false !== strstr($buffer, "\r\n")) {
$list = explode("\r\n", $buffer);
} if ($list) {
foreach ($list as $line) {
if ($end_of_header) {
if (strlen($line) === $content_length) {
$request_body = $line;
} else {
throw new \Exception("Content-Length {$content_length} not match request body length " . strlen($line) . "\n");
}
break;
} if ( empty($line) ) {
$end_of_header = true;
} else {
// Header.
//
if (false === strstr($line, ': ')) {
$array = explode(' ', $line); // Request line.
if (count($array) === 3) {
$method = $array[0];
$url = $array[1];
$protocol_version = $array[2];
}
} else {
$array = explode(': ', $line); // Request header.
list ($key, $value) = $array;
$request_header[$key] = $value; if ( strtolower($key) === strtolower('Content-type') ) {
$content_type = $value;
} // Have request body.
if ($key === 'Content-Length') {
$content_length = $value;
}
}
}
}
}
} // No request body, show buffer from read.
$response_body = $request_body ?: $buffer;
$response_header = "HTTP/1.1 200 OK\r\n";
$response_header .= "Content-type: {$content_type}\r\n";
if (empty($content_length) && (strlen($response_body) > 0)) {
$response_header .= "Content-Length: " . strlen($response_body) . "\r\n";
}
foreach ($request_header as $k => $v) {
$response_header .= "{$k}: {$v}\r\n";
}
$response_header .= "\r\n";
fwrite($connection, $response_header . $response_body);
fclose($connection);

以上程序属于 accept 之后的处理步骤,外层逻辑这里已省略,也适用在多进程服务器中子进程的处理部分。

这里还是用 fread 统一读取数据,设置读取的长度 8192(fread 的默认也是8192),$buffer 含有头部信息和数据,按 \r\n 分解成数组元素再处理,处理方式按照 HTTP 请求报文格式。

首先是请求行,如  GET /index HTTP/1.1    三部分以 “空格” 隔开,行尾以 \r\n 结束。

其次是报文首部,如 Content-Type: text/html  键值中间以 “冒号” 加 “空格” 隔开,行尾以 \r\n 结束。

请求报文头部以一行 \r\n 结束。

最后是请求主体(如果有的话),如果报文首部中有 Content-Length 值,就说明有请求主体。

响应数据按照 响应头部 加 响应主体,写入 socket 连接,最后关闭连接。

HTTP协议格式示意:

小结:

完整步骤 1.创建服务端套接字 2.接受请求 3.解释请求行确定请求的特定文件 4.从文件系统中获取请求文件 5.创建被请求的文件组成的HTTP响应报文,发送给客户端 6.如果请求的文件不存在,服务端返回 404 Not Found 报文。

无重逻辑(字符输出)的场景中,ab 100并发1000次访问压测,对比传统调优后的 Nginx + PHP,PHP 实现的多进程非阻塞 I/O HTTP Server 的 QPS 性能稍高,有一个理由可以解释:它不需要 Nginx 来转发请求。

PHP实现的 HTTP Server 有很多细节需要自身处理,不同因素会对处理请求的性能产生直接影响以及面临某些场景下才能产生的BUG,所以稳定性上需要经受更多考验。

Reference: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Overview

Link:http://www.cnblogs.com/farwish/p/8418969.html

[HTTP] PHP 实现 HTTP Server 原理的更多相关文章

  1. MXNet之ps-lite及parameter server原理

    MXNet之ps-lite及parameter server原理 ps-lite框架是DMLC组自行实现的parameter server通信框架,是DMLC其他项目的核心,例如其深度学习框架MXNE ...

  2. Spark job server原理初探

    Spark job server是一个基于Spark的服务系统,提供了管理SparkJob,context,jar的RestFul接口. 专注标注原文链接 http://www.cnblogs.com ...

  3. Tomcat Server 原理

    构成: 1.server代表整个catalina serverlet容器 2.service:由一个或多个connector以及一个共享的engine处理引擎组成 3.connector 在指定端口上 ...

  4. kubernetes核心原理之API Server原理分析

    kubernetes API Server的核心功能是提供了Kubernetes各类资源对象(Pod,RC,Service等)的增删改查及Watch等HTTP Rest接口,成为集群内各个功能模块之间 ...

  5. [原创]安全系列之端口敲门服务(Port Knocking for Ubuntu 14.04 Server)

    Port Knocking for Ubuntu 14.04 Server OS:ubuntu 14.04 server 原理简单分析: 端口敲门服务,即:knockd服务.该服务通过动态的添加ipt ...

  6. FQ原理

    笔者在nginx反向代理篇讲了正向代理和反向代理的区别,今天着重讲其中的FQ是实现原理. 一.普遍的两种方式 1.vpn vpn它将客户端的IP数据报经过加密和二次封装后转发出去,客户端通过vpn上网 ...

  7. kubernetes 核心原理

    3.1 K8s API Server 原理分析 K8s API server核心提供对各种资源对象的增.删.改.查以及Watch等HTTPRest接口,是集群内各个模块之间数据交互和通信的中心枢纽,是 ...

  8. 028.核心组件-API Server

    一 Kubernetes API Server原理 1.1 API Server功能 Kubernetes API Server的核心功能是提供Kubernetes各类资源对象(如Pod.RC.Ser ...

  9. 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...

随机推荐

  1. TLD算法概述--学习理解之(一)

    liuyihai@126.com http://www.cnblogs.com/liuyihai/ TLD(Tracking-Learning-Detection)是英国萨里大学的一个捷克籍博士生Zd ...

  2. 关于java解析xml文件出现的问题

    DOM解析xml文件 问题1:导入javax.xml.parsers.DocumentBuilderFactory出现问题,如图: 解决办法是:由于创建工程时有个默认的jre,重新创建工程改掉就解决了 ...

  3. Linux系统从安装开始

    已经很久很久没来得及写博客了,想想之前自己开始安装使用Linux系统的尝试,好像很简单!下面开始Linux系统的安装:这里推荐U盘安装 首先你必须下载一个U盘ISO镜像写入工具,本人使用USBWrit ...

  4. 2017多校第一套&&hdu6038 思维 数学

    链接  http://acm.hdu.edu.cn/showproblem.php?pid=6038 题意: 给你一个a序列,代表0到n-1的排列:一个b序列代表0到m-1的排列.问你可以找出多少种函 ...

  5. Kafka监控安装

    p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-s ...

  6. git工作流程一览

    Git是分布式版本控制系统,没有中央服务器,每个人的电脑就是一个完整的版本库,工作的时候不需要联网了,因为版本都在自己电脑上.协同的方法是这样的:比如说自己在电脑上改了文件A,其他人也在电脑上改了文件 ...

  7. angularJS 与angujs-sku实现购物车组合查询

    原网址:http://sentsin.com/web/1069.html   demo : https://codepen.io/hzxs1990225/pen/VYyOdW  修复版文件下载:htt ...

  8. 用adb录制手机屏幕视频

    adb shell screenrecord命令可以用来录制Android手机视频 screenrecord是一个shell命令,支持Android4.4(API level 19)以上,支持视频格式 ...

  9. solr单机版安装与基本部署

    安装solr准备工作: linux/tomcat/jdk solr单机版的安装 1.解压缩tomcat安装包 tar -zxf tomcat-xxx.tar.gz 2.创建文件夹:mkdir /usr ...

  10. ASP.NET core1.0 EF MYSQL搭建中碰到几个问题记录

    1.No executable found matching command "dotnet-ef"   看了网上各种办法都没用,最后选择"个人用户账户"创建项 ...