首页 > 编程语言 >[PHP]用socket写一个简单的WEB服务器

[PHP]用socket写一个简单的WEB服务器

时间:2022-12-28 10:45:45浏览次数:63  
标签:WEB HTTP socket write header path PHP 我们

今天我就要把我的最新研究成果展示看看,而不玩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,我就不演示了

1

标签:WEB,HTTP,socket,write,header,path,PHP,我们
From: https://www.cnblogs.com/imzlh/p/17009589.html

相关文章

  • sb+websocket实例
    1、pom.xml<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-star......
  • 【译】2022 年回顾:Web 性能有哪些新变化?
    原文地址:https://www.debugbear.com/blog/2022-in-web-performance若对文中提到的一些性能参数不太熟悉,可以参考我之前的一篇博文《性能参数和优化手段》。......
  • webpack-dev-server
    Tip如果你碰到了问题,请将路由导航至 /webpack-dev-server 将会为你展示服务文件的位置。例如: http://localhost:9000/webpack-dev-server。Tip如果你需要要手动重新......
  • WebUI自动化必备技能-HTML和css知识详解
    每天进步一点点,关注我们哦,每天分享测试技术文章本文章出自【码同学软件测试】码同学公众号:自动化软件测试,领取资料可加:magetest码同学抖音号:小码哥聊软件测试 学习w......
  • JavaWeb
    JavaWeb1、基本概念1.1前言静态webhtmlcss动态web技术栈:Servlet/JSP、ASP、php动态web资源开发技术统称为JavaWeb1.2web应用程序:可以提供浏览......
  • 运维实践-使用WebP Server Go无缝转换图片为Google的webp格式让你网站访问加载速度飞
    关注「WeiyiGeek」公众号本章目录:0x00快速入门WebP介绍Webp-Server介绍0x01安装实践二进制安装部署Docker安装部署0x02博客网站图片资源访问优化实践0x00快速入门WebP......
  • php redis之高性能扫描和批量操作
    前提:redis的扫描方法,使用scan,而不是使用keys* 因为keys*会全部key扫描一次,key数量很多时,容易造成阻塞太久甚至down机。 scan原理:指定每次遍历的key数目和查找规......
  • Webpack 使用
    简介Webpack主要有五个核心属性1.Entry入口(Entry)指示webpack以哪个文件为入口起点开始打包,分析构建内部依赖图2.Output输出(Output)指......
  • Nginx实现websocket代理的方式
    一个简单的实现,后续再补充。其中80端口是提供正常web访问的端口,9000是提供socket服务的端口。实际部署时出于安全考虑,可以将代理端口与后端服务器提供的端口设置为不同的值......
  • 在Netty底层监控消息发送到Socket的时间
    1、调用writeAndFlush方法之后获取ChannelFuture;2、新增消息发送ChannelFutureListener,监听消息发送结果,如果消息写入网络Socket成功,则Netty会回调C......