今天我就要把我的最新研究成果展示看看,而不玩ARMBIAN了,因为刷了两台S905L3的发现一点挑战都没有

从0.2写WEB服务难啊,你需要懂HTTP协议和SOCKET!不过有经验我们很快就可以搭建一个

0.HTTP

为了准确了解HTTP服务,我写了个简单的代理脚本,源码放这了:

<?php
$s = socket_create(AF_INET6,SOCK_STREAM,SOL_TCP);
socket_bind($s,'::',81);
socket_listen($s,4);
socket_set_block($s);
$r = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
if(!socket_connect($r,'localhost',80))
throw new Error('Connect Error.'); while(true){
$_ = socket_accept($s);
socket_getpeername($_,$addr);
echo 'New Client connected IP:'.$addr;
while(true){
echo $__=socket_read($_,1*1024*1024);
if(false == $__) die('连接断开!');
echo PHP_EOL;
socket_write($r,$__);
$___ = socket_read($r,1*1024*1024);
socket_write($_,$___);
echo $___;
echo PHP_EOL;
}
}
?>

输入php test.php,然后访问IP:81,就会看见终端出现:

为什么明明就一次循环输出了多次数据?哈哈,因为请求和响应标头里有一个神奇的KEEP-ALIVE,自行百度吧

这样,我们就可以发现,客户端第一行包括了路径、请求方式,接下来就是各种参数

服务端响应第一部分响应头,包括状态码、服务程序名称、响应时间、响应文件类型等

第二部分与第一部分的分割线一个换行\r\n,接下来就是主要的数据了。

就这么简单!不要怕,知道了这些,我们可以轻轻松松写一个WEB SERVER

1.了解流程

socket其实很简单!看看大神的文章马上就懂了:https://zhuanlan.zhihu.com/p/260139078

首先我们需要启动一个TCP监听,我们无需管底层的解析,socket已经为我们解决了这些

我们首先读取客户端发来哪些数据,再决定发回哪些数据,最后有一端关闭连接就算完成了

2.小试牛刀

这样,我们就可以写一个简单的程序来小试牛刀了:

$http_port = 88;
$s = socket_create(AF_INET6,SOCK_STREAM,SOL_TCP); // 创建一个socket实例
socket_set_block($s); // 阻塞模式,如socket_accept()会等待有客户端连接才下一步,一般可以省很多事
socket_bind($s,'::',$http_port) or die('io.http : 无法绑定HTTP服务.'); // 绑定端口
socket_listen($s,4) or die('io.http : 监听失败!'); // 开始监听,最大队列为4

有了监听事情好了大半,接下来有连接了会放在队列里,等待我们接受(accept)它们。

如何接受这些请求呢?$c = socket_accept($s);就行了

$c很重要!我们可以通过socket函数执行很多操作

读取对方IP到变量$ip:socket_getpeername($c,$ip);

读取对方访问路径:list($proto,$path,$version) = explode(' ',socket_read($c , 1*1024*1024,PHP_NORMAL_READ));

注释:

$proto :协议,如GET POST PUT DELETE......

$path :请求的目录,如 /test

version :HTTP版本号,直接忽略即可

PHP_NORMAL_READ:读一行,这对我们来说正好可以满足需求(重要的信息都在第一行)

写东西缓冲区 socket_write($socket,'');

注意 只是写到了缓冲区,我们无法保证此时用户接收到了数据,贸然socket_close()会出错:连接已重置

比如写响应头:

socket_write($c,"HTTP/1.1 404 Not Found
Date: Tue, 27 Dec 2022 06:45:30 GMT
Content-Type: text/html; charset=UTF-8
\r\n");

接着写数据,比如Hello world!:socket_write($c,"<p>Hello world!</p>";

最后可选平滑关闭连接socket_shutdown($c);或者强制关闭连接socket_close($c);

结束!我们放到一起看看效果:

OKOK!可以看见我们已经成功了!但是只能接收一次数据实在是太难受了,我们接下来改进一下:

3.改进

3.1 HEADER

说响应头简单倒真的很简单,就是KEY: VALUE\n的结构,那我们可以包装一下传入数组输出heaer

HTTP状态码我们不要了,反正浏览器也不会看,它懂这些我们写了也没有用

function create_header(array $header,int $status = 200){
$tmp = "HTTP/1.1 $status";
foreach(array_merge([
'Server'=>'ioPan', // 服务程序
'Content-Type'=>'text/html ;charset=UTF-8',// 默认响应HTML
'Date'=>gmdate('D, d M Y H:i:s T')// 响应时间
],$header) as $n=>$v){
$tmp .= "\r\n$n: $v"; // 对应key:value\n
}
return $tmp."\r\n\r\n"; // header结束了需要两个换行
}

3.2 循环结构

只要让socket_accept()循环不就可以一直为我们工作了吗?好,我们用while循环下去:

while(true){
$_ = socket_accept($s);
socket_getpeername($_,$ip);
list($proto,$path,$version) = explode(' ',socket_read($_ , 1*1024*1024,PHP_NORMAL_READ));
echo "io.http : 新的客户端连接 IP[$ip];请求路径:[$path]\n";
// 干些活
socket_write($_,create_header([],200));
socket_write($_,'<p>Hello world!</p>');
}

我就偷懒不演示了,可以发现好几个客户端同时访问也几乎没什么问题。

3.3 本地文件

这个最简单,只要判断本地有文件且可读,返回状态码200,调用fread()读取输出文件

如果请求的地址文件不存在或不可读,返回状态码404,展示404.html

如果是个目录返回403。当然如果你愿意可以再加Range(断点续传)的支持

if(is_dir($path) or !is_readable($path)) socket_write($_,create_header([],304));
else socket_write($_,create_header([
'Content-Length'=>filesize($path),
'Content-Type'=>'application/octet-stream'
],200)) and socket_write($_,file_get_contents($path));

4.showTime

把上述代码组装起来,我们就成功写了个高性能的WEB SERVER,不过只有单进程,一次只能接受一个请求。

改进:使用端口复用(socket_set_option($s,SOL_SOCKET, SO_REUSEADDR ,true);)

并且使用POSIX多进程pcntl_fork()),但是由于Windows不支持POSIX,我就不演示了

[PHP]用socket写一个简单的WEB服务器的更多相关文章

  1. 如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  2. nodejs写一个简单的Web服务器

    目录文件如 httpFile.js如下: const httpd = require("http"); const fs = require("fs"); // ...

  3. 一个简单的web服务器

    写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...

  4. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  5. 动手写一个简单的Web框架(模板渲染)

    动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...

  6. 动手写一个简单的Web框架(Werkzeug路由问题)

    动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...

  7. 动手写一个简单的Web框架(HelloWorld的实现)

    动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...

  8. Tomcat剖析(一):一个简单的Web服务器

    Tomcat剖析(一):一个简单的Web服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器 ...

  9. 自己模拟的一个简单的web服务器

    首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器 ...

  10. 自己动手模拟开发一个简单的Web服务器

    开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...

随机推荐

  1. 基于FPGA的4x4矩阵键盘驱动设计---第一版

    欢迎各位朋友关注"郝旭帅电子设计团队",本篇为各位朋友介绍基于FPGA的4x4矩阵键盘驱动设计---第一版 功能说明: 1. 驱动4x4矩阵键盘:按下任意一个按键,解析出对应按键信 ...

  2. 将大量文件的拓展名中大写字母改为小写:Python实现

      本文介绍基于Python语言,基于一个大文件夹,遍历其中的多个子文件夹,对于每一个子文件夹中的大量文件,批量将其文件的名称或后缀名中的字母由大写修改为小写的方法.   本文期望实现的需求为:现有一 ...

  3. quartzui 的界面管理

    基于Quartz.NET3.0的定时任务Web可视化管理.docker打包开箱即用.内置SQLite持久化.语言无关.业务代码零污染.支持 RESTful风格接口.傻瓜式配置 quartzuiquar ...

  4. 阿里巴巴 MySQL 数据库之索引规约 (二)

    索引规约 强制部分 [强制] 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引. 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的:另外 ...

  5. Android 12(S) Binder(二)

    前面一节学习了ServiceManager这个特殊service的工作过程,这一节来看看普通service的工作过程. 就用media.extractor这个service来当例子! 1.服务的注册及 ...

  6. webview2 示例 Samples Selenium

    https://github.com/MicrosoftEdge/WebView2Samples https://learn.microsoft.com/zh-cn/microsoft-edge/we ...

  7. IPsecVPN 服务器一键安装脚本

    IPsec VPN 服务器一键安装脚本 使用 Linux 脚本一键快速搭建自己的 IPsec VPN 服务器.支持 IPsec/L2TP, Cisco IPsec 和 IKEv2 协议.你只需提供自己 ...

  8. RTMP推流FLV插入自定义SEI数据总结

    一.需求 在RTMP推送的流中添加一个接口,可以添加自定义的数据(一段字节数组). 经过分析,在H264的流中可以通过SEI添加自定义数据,下面是实施的总结 二.实施 1)准备工具 RTMP推流客户端 ...

  9. webpack 路径别名与导入后缀省略

    // 解析模块的规则 resolve: { // 配置解析模块路径别名:优点简写路径,缺点路径没有提示 alias: { // 定义一个@变量,可在import引入时使用 '@': path.reso ...

  10. 通过JS来触发<a>链接来实现图片下载

    function downloadImg(){ var url = '实际情况的图片URL'; // 获取图片地址 var a = document.createElement('a'); // 创建 ...