大家好,我是码农先森。
写在前面
PHP 曾是Web开发领域佼佼者,随着业务壮大,异步和高并发方面不足显现。Swoole 曾经尝试填补空白,但局限性也比较的明显。Go 语言的崛起,简洁语法和并发优势吸引大厂使用,吸引了大多数程序员的转型。疫情、战争、大环境的恶化等因素加剧了互联网行业内卷,PHP 程序员陷入了困境,因此转型 Go 语言是不二的选择。我从 PHP 转型Go,深知转型之难。因此致力于帮助其他 PHP 程序员转型,分享经验。困境时需抱团取暖,才能走过黎明前的黑暗。
HTTP 协议原理
HTTP 协议是一种用于传输超文本(如HTML)的应用层协议。它是Web通信的基础,负责在客户端和服务器之间传递请求和响应。HTTP 使用 TCP 作为传输协议,通常使用 80 端口进行通信。如下图所示 HTTP 协议在 TCP/IP 网络模型中是处于应用层,是 TCP/IP 协议的一个子集。
HTTP 协议撑起了互联网的大半江山,可以说没有 HTTP 协议就没有当下的互联网。作为一名 Web 程序的开发者,深入掌握和理解 HTTP 协议尤为重要。下面这张图是表示了 HTTP 的请求报文、响应报文格式,其实相对于其他的协议来说,HTTP 协议是比较简单了,同时也是最常用的。
原生 PHP 的实现
使用 PHP 的内置 HTTP 模块启动服务
php -S 内置服务器实际上是基于CLI(Command Line Interface)SAPI。当执行php -S命令时,PHP 会以命令行模式启动一个轻量级服务器,监听指定的IP地址和端口。但是,这种内置的服务器并不适合用于生产环境,它是为了便于开发和测试而提供的工具。如果是在生产环境上,建议部署 PHP-FPM,再通过 Nginx 做反向代理的方式实现。
1、代码文件 index.php
<?php
// 设置HTTP响应头,告知浏览器返回的是文本内容
header('Content-Type: text/plain');
// 输出 "Hello, World!" 作为响应
echo "Hello, World! For PHP Module";
2、启动
MacBook-Pro:demo$ php -S 127.0.0.1:8080
[Sat Jul 22 10:30:42 2023] PHP 8.2.5 Development Server (http://127.0.0.1:8080) started
3、访问
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello, World! For PHP Module
使用 Socket 的方式启动 HTTP 服务
Socket 是一种用于在网络中进行通信的编程接口。它允许程序在网络上通过 IP 地址和端口号与其他计算机建立连接,从而实现数据的传输和交换。 Socket 是一种低层次的网络编程接口,支持 TCP、UDP、IPv4、IPv6 协议,不能直接支持应用层协议,例如 HTTP 协议等。因此,这里通过使用 Socket 创建一个 TCP 服务器来处理 HTTP 请求。
1、代码文件 socket.php
<?php
$host = 'localhost'; // 如果需要,可以将此处更改为你服务器的IP地址
$port = 8080; // 服务器监听的端口
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
echo "无法创建Socket: " . socket_strerror(socket_last_error()) . PHP_EOL;
exit(1);
}
socket_bind($socket, $host, $port) or die('无法绑定到指定地址');
socket_listen($socket);
echo "服务器监听在 $host:$port..." . PHP_EOL;
while (true) {
$client = socket_accept($socket);
// 读取客户端的请求
$request = socket_read($client, 4096);
// 处理请求(为简单起见,我们只处理GET请求)
if (preg_match("/GET (.*) HTTP\/1\.1/", $request, $matches)) {
$uri = $matches[1];
$response = "HTTP/1.1 200 SUCCESS\r\n\r\nHello World! For PHP Socket\r\n";
}
// 将响应发送给客户端
socket_write($client, $response);
// 关闭客户端连接
socket_close($client);
}
2、启动
MacBook-Pro:demo$ php socket.php
服务器监听在 localhost:8080...
3、访问
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello World! For PHP Socket
Swoole 扩展的实现
Swoole 使用底层 C++ 实现,充分利用了异步非阻塞的网络模型,能够处理大量并发连接,极大地提高了 HTTP 服务的性能。通过事件循环和异步处理,避免了 PHP 单进程模型的瓶颈。
每一个用户的 HTTP 请求,将由 Master 进程分配到 Worker 进程进行处理,不阻塞主进程的执行;同时,每个 Worker 进程内部会将请求协程化,避免阻塞 worker 进程,这种模式极大的提高了服务的处理能力,如下图源代码中对应使用协程来实现发送数据。
1、代码文件 swoole_http.php
<?php
// 创建HTTP服务器对象,监听在0.0.0.0:8080端口
$http = new Swoole\Http\Server("0.0.0.0", 8080);
// 设置 Worker 数量
$http->set([
'worker_num' => 3
]);
// 在启动事件中执行初始化设置
$http->on('start', function ($server) {
echo "Swoole HTTP服务器已启动\n";
});
// 监听HTTP请求事件
$http->on('request', function ($request, $response) {
// 处理请求
$response->status(200);
$response->end("Hello World! For Swoole HTTP\r\n");
});
// 启动HTTP服务器
$http->start();
2、启动
MacBook-Pro:demo$ php swoole_http.php
Swoole HTTP服务器已启动
3、访问
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello World! For Swoole HTTP
Go 语言的实现
Go 语言的轻量级线程Goroutine,能够快速处理大量并发请求。Goroutine 的创建和销毁非常快速,在单个物理线程上可以同时运行成千上万个 Goroutine。并且可以高效的利用多核 CPU,充分的使用物理资源。http.ListenAndServe
启动并监听一个 HTTP 服务,客户端与服务端建立连接后,服务端会交由一个 Goroutine 处理,下面这张图是对应 Go Http 模块源代码的处理逻辑。
1、代码文件 main.go
package main
import (
"fmt"
"net/http"
)
func main() {
// 处理请求
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World! For Go")
})
// 启动HTTP服务,监听在本地的8080端口
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting server:", err)
}
}
2、启动
MacBook-Pro:demo$ go run main.go
3、访问
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello, World! For Go
总结
- 在 PHP 语言层面启动 HTTP 服务并不适合,通常需要结合 PHP-FPM、Nginx 等服务。
- Swoole 作为用 C++ 实现的扩展,弥补了 PHP 在异步通信及并发层面的不足,但是,在单进程的模式下无法高效的利用多核 CPU,不能充分的榨干物理资源。
- Go 语言是高并发领域的一支独秀,天然支持高并发,并且能够充分的利用物理资源。
欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。