首發(fā)原文鏈接: Swoole與Go系列教程之HTTP服務(wù)的應(yīng)用
大家好,我是碼農(nóng)先森。
寫在前面
PHP 曾是Web開(kāi)發(fā)領(lǐng)域佼佼者,隨著業(yè)務(wù)壯大,異步和高并發(fā)方面不足顯現(xiàn)。Swoole 曾經(jīng)嘗試填補(bǔ)空白,但局限性也比較的明顯。Go 語(yǔ)言的崛起,簡(jiǎn)潔語(yǔ)法和并發(fā)優(yōu)勢(shì)吸引大廠使用,吸引了大多數(shù)程序員的轉(zhuǎn)型。疫情、戰(zhàn)爭(zhēng)、大環(huán)境的惡化等因素加劇了互聯(lián)網(wǎng)行業(yè)內(nèi)卷,PHP 程序員陷入了困境,因此轉(zhuǎn)型 Go 語(yǔ)言是不二的選擇。我從 PHP 轉(zhuǎn)型Go,深知轉(zhuǎn)型之難。因此致力于幫助其他 PHP 程序員轉(zhuǎn)型,分享經(jīng)驗(yàn)。困境時(shí)需抱團(tuán)取暖,才能走過(guò)黎明前的黑暗。
HTTP 協(xié)議原理
HTTP 協(xié)議是一種用于傳輸超文本(如HTML)的應(yīng)用層協(xié)議。它是Web通信的基礎(chǔ),負(fù)責(zé)在客戶端和服務(wù)器之間傳遞請(qǐng)求和響應(yīng)。HTTP 使用 TCP 作為傳輸協(xié)議,通常使用 80 端口進(jìn)行通信。如下圖所示 HTTP 協(xié)議在 TCP/IP 網(wǎng)絡(luò)模型中是處于應(yīng)用層,是 TCP/IP 協(xié)議的一個(gè)子集。
HTTP 協(xié)議撐起了互聯(lián)網(wǎng)的大半江山,可以說(shuō)沒(méi)有 HTTP 協(xié)議就沒(méi)有當(dāng)下的互聯(lián)網(wǎng)。作為一名 Web 程序的開(kāi)發(fā)者,深入掌握和理解 HTTP 協(xié)議尤為重要。下面這張圖是表示了 HTTP 的請(qǐng)求報(bào)文、響應(yīng)報(bào)文格式,其實(shí)相對(duì)于其他的協(xié)議來(lái)說(shuō),HTTP 協(xié)議是比較簡(jiǎn)單了,同時(shí)也是最常用的。
原生 PHP 的實(shí)現(xiàn)
使用 PHP 的內(nèi)置 HTTP 模塊啟動(dòng)服務(wù)
php -S 內(nèi)置服務(wù)器實(shí)際上是基于CLI(Command Line Interface)SAPI。當(dāng)執(zhí)行php -S命令時(shí),PHP 會(huì)以命令行模式啟動(dòng)一個(gè)輕量級(jí)服務(wù)器,監(jiān)聽(tīng)指定的IP地址和端口。但是,這種內(nèi)置的服務(wù)器并不適合用于生產(chǎn)環(huán)境,它是為了便于開(kāi)發(fā)和測(cè)試而提供的工具。如果是在生產(chǎn)環(huán)境上,建議部署 PHP-FPM,再通過(guò) Nginx 做反向代理的方式實(shí)現(xiàn)。
1、代碼文件 index.php
<?php
// 設(shè)置HTTP響應(yīng)頭,告知瀏覽器返回的是文本內(nèi)容
header('Content-Type: text/plain');
// 輸出 "Hello, World!" 作為響應(yīng)
echo "Hello, World! For PHP Module";
2、啟動(dòng)
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、訪問(wèn)
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello, World! For PHP Module
使用 Socket 的方式啟動(dòng) HTTP 服務(wù)
Socket 是一種用于在網(wǎng)絡(luò)中進(jìn)行通信的編程接口。它允許程序在網(wǎng)絡(luò)上通過(guò) IP 地址和端口號(hào)與其他計(jì)算機(jī)建立連接,從而實(shí)現(xiàn)數(shù)據(jù)的傳輸和交換。 Socket 是一種低層次的網(wǎng)絡(luò)編程接口,支持 TCP、UDP、IPv4、IPv6 協(xié)議,不能直接支持應(yīng)用層協(xié)議,例如 HTTP 協(xié)議等。因此,這里通過(guò)使用 Socket 創(chuàng)建一個(gè) TCP 服務(wù)器來(lái)處理 HTTP 請(qǐng)求。
1、代碼文件 socket.php
<?php
$host = 'localhost'; ?// 如果需要,可以將此處更改為你服務(wù)器的IP地址
$port = 8080; ? ? ? ? // 服務(wù)器監(jiān)聽(tīng)的端口
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
? ? echo "無(wú)法創(chuàng)建Socket: " . socket_strerror(socket_last_error()) . PHP_EOL;
? ? exit(1);
}
socket_bind($socket, $host, $port) or die('無(wú)法綁定到指定地址');
socket_listen($socket);
echo "服務(wù)器監(jiān)聽(tīng)在 $host:$port..." . PHP_EOL;
while (true) {
? ? $client = socket_accept($socket);
? ??
? ? // 讀取客戶端的請(qǐng)求
? ? $request = socket_read($client, 4096);
? ? // 處理請(qǐng)求(為簡(jiǎn)單起見(jiàn),我們只處理GET請(qǐng)求)
? ? 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";
? ? }
? ? // 將響應(yīng)發(fā)送給客戶端
? ? socket_write($client, $response);
? ??
? ? // 關(guān)閉客戶端連接
? ? socket_close($client);
}
2、啟動(dòng)
MacBook-Pro:demo$ php socket.php?
服務(wù)器監(jiān)聽(tīng)在 localhost:8080...
3、訪問(wèn)
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello World! For PHP Socket
Swoole 擴(kuò)展的實(shí)現(xiàn)
Swoole 使用底層 C++ 實(shí)現(xiàn),充分利用了異步非阻塞的網(wǎng)絡(luò)模型,能夠處理大量并發(fā)連接,極大地提高了 HTTP 服務(wù)的性能。通過(guò)事件循環(huán)和異步處理,避免了 PHP 單進(jìn)程模型的瓶頸。
每一個(gè)用戶的 HTTP 請(qǐng)求,將由 Master 進(jìn)程分配到 Worker 進(jìn)程進(jìn)行處理,不阻塞主進(jìn)程的執(zhí)行;同時(shí),每個(gè) Worker 進(jìn)程內(nèi)部會(huì)將請(qǐng)求協(xié)程化,避免阻塞 worker 進(jìn)程,這種模式極大的提高了服務(wù)的處理能力,如下圖源代碼中對(duì)應(yīng)使用協(xié)程來(lái)實(shí)現(xiàn)發(fā)送數(shù)據(jù)。
1、代碼文件 swoole_http.php
<?php
// 創(chuàng)建HTTP服務(wù)器對(duì)象,監(jiān)聽(tīng)在0.0.0.0:8080端口
$http = new Swoole\Http\Server("0.0.0.0", 8080);
// 設(shè)置 Worker 數(shù)量
$http->set([
? ? 'worker_num' => 3
]);
// 在啟動(dòng)事件中執(zhí)行初始化設(shè)置
$http->on('start', function ($server) {
? ? echo "Swoole HTTP服務(wù)器已啟動(dòng)\n";
});
// 監(jiān)聽(tīng)HTTP請(qǐng)求事件
$http->on('request', function ($request, $response) {
? ? // 處理請(qǐng)求
? ? $response->status(200);
? ? $response->end("Hello World! For Swoole HTTP\r\n");
});
// 啟動(dòng)HTTP服務(wù)器
$http->start();
2、啟動(dòng)
MacBook-Pro:demo$ php swoole_http.php?
Swoole HTTP服務(wù)器已啟動(dòng)
3、訪問(wèn)
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello World! For Swoole HTTP
Go 語(yǔ)言的實(shí)現(xiàn)
Go 語(yǔ)言的輕量級(jí)線程Goroutine,能夠快速處理大量并發(fā)請(qǐng)求。Goroutine 的創(chuàng)建和銷毀非??焖?,在單個(gè)物理線程上可以同時(shí)運(yùn)行成千上萬(wàn)個(gè) Goroutine。并且可以高效的利用多核 CPU,充分的使用物理資源。http.ListenAndServe
啟動(dòng)并監(jiān)聽(tīng)一個(gè) HTTP 服務(wù),客戶端與服務(wù)端建立連接后,服務(wù)端會(huì)交由一個(gè) Goroutine 處理,下面這張圖是對(duì)應(yīng) Go Http 模塊源代碼的處理邏輯。
1、代碼文件 main.go
package main
import (
?? ?"fmt"
?? ?"net/http"
)
func main() {
?? ?// 處理請(qǐng)求
?? ?http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
?? ??? ?fmt.Fprintf(w, "Hello, World! For Go")
?? ?})
?? ?// 啟動(dòng)HTTP服務(wù),監(jiān)聽(tīng)在本地的8080端口
?? ?err := http.ListenAndServe(":8080", nil)
?? ?if err != nil {
?? ??? ?fmt.Println("Error starting server:", err)
?? ?}
}
2、啟動(dòng)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-843104.html
MacBook-Pro:demo$ go run main.go
3、訪問(wèn)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-843104.html
MacBook-Pro:demo$ curl http://127.0.0.1:8080
Hello, World! For Go
總結(jié)
- 在 PHP 語(yǔ)言層面啟動(dòng) HTTP 服務(wù)并不適合,通常需要結(jié)合 PHP-FPM、Nginx 等服務(wù)。
- Swoole 作為用 C++ 實(shí)現(xiàn)的擴(kuò)展,彌補(bǔ)了 PHP 在異步通信及并發(fā)層面的不足,但是,在單進(jìn)程的模式下無(wú)法高效的利用多核 CPU,不能充分的榨干物理資源。
- Go 語(yǔ)言是高并發(fā)領(lǐng)域的一支獨(dú)秀,天然支持高并發(fā),并且能夠充分的利用物理資源。
到了這里,關(guān)于01 | Swoole與Go系列教程之HTTP服務(wù)的應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!