如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻

這篇“如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻”文章吧。

成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),施秉企業(yè)網(wǎng)站建設(shè),施秉品牌網(wǎng)站建設(shè),網(wǎng)站定制,施秉網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,施秉網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

第一步,首先能夠簡(jiǎn)簡(jiǎn)單單的過濾IP

使用PHP監(jiān)聽端口并且轉(zhuǎn)發(fā)數(shù)據(jù)的框架很多,對(duì)此我選擇workerman,原因有3

  • 運(yùn)行簡(jiǎn)單穩(wěn)定

  • 方法接口簡(jiǎn)單

  • 內(nèi)置進(jìn)程守護(hù)

如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻

workerman的使用方法非常簡(jiǎn)單,只要10行代碼,就實(shí)現(xiàn)了IP轉(zhuǎn)發(fā)+白名單過濾:

$worker = new Worker('tcp:0.0.0.0:' . Config::get('door.port_in'));
// 監(jiān)聽一個(gè)端口
$worker->count = 2;
// 設(shè)置多進(jìn)程
$worker->onConnect = function (TcpConnection $connection) {
    // 獲取IP白名單
    $list_ip = AppIp::where('status', 0)->cache(3)->column('ip');
    $remote_ip = $connection->getRemoteIp();
    // 攔截IP
    if (!in_array($remote_ip, $list_ip)) {
        $connection->close();
    }
    // 放行連接,連接內(nèi)部目標(biāo)端口
    $to_connection = new AsyncTcpConnection('tcp:127.0.0.1:' . Config::get('door.port_to'));
    // 互相轉(zhuǎn)發(fā)流量
    $connection->pipe($to_connection);
    $to_connection->pipe($connection);
    $to_connection->connect();
}

正如上面代碼所示,只有簡(jiǎn)單幾行,便實(shí)現(xiàn)了IP監(jiān)聽和轉(zhuǎn)發(fā),其中IP白名單通過數(shù)據(jù)庫查詢,并且緩存。

第二步,與ThinkPHP命令行整合在一起

為了項(xiàng)目開發(fā)方便,我都會(huì)使用ThinkPHP框架進(jìn)行開發(fā),它夠簡(jiǎn)單,功能也比較齊全。

如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻

最終實(shí)現(xiàn)的命令行效果如下:

運(yùn)行命令
php think door start
php think door start --mode d  // 守護(hù)進(jìn)程重啟
重啟
php think door restart
停止
php think door stop

workerman的命令參數(shù)與thinkphp并不兼容,但是實(shí)現(xiàn)這樣的效果并不難,實(shí)際上很簡(jiǎn)單,代碼如下:

<?php

declare(strict_types=1);

namespace app\common\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class Door extends Command
{
    protected function configure()
    {
        // 指令配置
        $this->setName('door')
            // 設(shè)置think的命令參數(shù)
            ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
            ->addOption('mode', 'm', Option::VALUE_OPTIONAL, 'Run the workerman server in daemon mode.')
            ->setDescription('the door command');
    }
    protected function execute(Input $input, Output $output)
    {
        // 指令輸出
        $output->writeln('door');
        $action = $input->getArgument('action');
        $mode = $input->getOption('mode');
        // 重新構(gòu)造命令行參數(shù),以便兼容workerman的命令
        global $argv;
        $argv = [];
        array_unshift($argv, 'think', $action);
        if ($mode == 'd') {
            $argv[] = '-d';
        } else if ($mode == 'g') {
            $argv[] = '-g';
        }
        // ...workerman的代碼
    }
}

在上面的代碼中,主要做了兩件事:

  • 實(shí)現(xiàn)

    ThinkPHP

    的命令設(shè)置

  • 將命令參數(shù)重新構(gòu)造為

    workerman

    兼容的方式

第三步,實(shí)現(xiàn)管理面板

使用PHP實(shí)現(xiàn)一個(gè)管理面板太簡(jiǎn)單了,PHP到處都是這樣的后臺(tái)框架,這里我選擇ulthon_admin,這是我自己開發(fā)維護(hù)的,它基于ThinkPHP6,很簡(jiǎn)單,為定制而生,不搞所謂的“插件”和“市場(chǎng)”生態(tài),能夠自動(dòng)生成CURD代碼,并且內(nèi)置幾了幾個(gè)有趣的皮膚。

最終效果如下:

如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻

第四步,進(jìn)階,更好的性能和流量統(tǒng)計(jì)

我們的IP攔截客戶端需要運(yùn)行在服務(wù)器上,并且直接連接數(shù)據(jù)庫,如果每次收到請(qǐng)求都要查詢數(shù)據(jù)庫,那么很有可能導(dǎo)致連接不通暢,尤其是客戶端和數(shù)據(jù)庫本身位置較遠(yuǎn)的時(shí)候。在第一步的代碼中,我們只是簡(jiǎn)單的使用了查詢緩存,但是還不夠,還可以優(yōu)化。并且我們可以在管理面板的截圖中看到,我們是可以統(tǒng)計(jì)流量和攔截次數(shù)的,現(xiàn)在我們要實(shí)現(xiàn)這些功能:

流量統(tǒng)計(jì)

首先我們將第一個(gè)步中,流量轉(zhuǎn)發(fā)部分的代碼改造成如下的樣子:

<?php
// 向TO發(fā)起連接
$to_connection = new AsyncTcpConnection('tcp://127.0.0.1:' . Config::get('door.port_to'));
$to_connection->onMessage = function ($source, $data) use ($connection, $remote_ip) {
    // 接收到來自TO的數(shù)據(jù),返回的數(shù)據(jù)
    $connection->send($data);
    // 將流量統(tǒng)計(jì)存儲(chǔ)到內(nèi)存里
    Cache::inc(md5($remote_ip) . '-to', strlen($data));
};
// 流程和流量控制
$to_connection->onClose = function ($source) use ($connection) {
    $connection->close();
};
$connection->onBufferFull = function ($dest) use ($to_connection) {
    $to_connection->pauseRecv();
};
$connection->onBufferDrain = function ($dest) use ($to_connection) {
    $to_connection->resumeRecv();
};
$connection->onMessage = function ($source, $data) use ($to_connection, $remote_ip) {
    // 接收來自IN的數(shù)據(jù),請(qǐng)求的數(shù)據(jù)
    $to_connection->send($data);
    // 將流量統(tǒng)計(jì)存儲(chǔ)到內(nèi)存里
    Cache::inc(md5($remote_ip) . '-in', strlen($data));
};
// 流程和流量控制
$connection->onClose = function ($source) use ($to_connection) {
    $to_connection->close();
};
$to_connection->onBufferFull = function ($dest) use ($connection) {
    $connection->pauseRecv();
};
$to_connection->onBufferDrain = function ($dest) use ($connection) {
    $connection->resumeRecv();
};

在第一部的代碼中,只用兩行便實(shí)現(xiàn)了這些代碼:

// 放行連接,連接內(nèi)部目標(biāo)端口
$to_connection = new AsyncTcpConnection('tcp:127.0.0.1:' . Config::get('door.port_to'));
// 互相轉(zhuǎn)發(fā)流量
$connection->pipe($to_connection);
$to_connection->pipe($connection);

這里使用的是workerman內(nèi)置的流量轉(zhuǎn)發(fā),它很好用,但是這里我們要統(tǒng)計(jì)流量,所以我們手動(dòng)轉(zhuǎn)發(fā)流量。

這里我們將統(tǒng)計(jì)的數(shù)據(jù)存儲(chǔ)到緩存里,而不是直接連接數(shù)據(jù)庫更新,這是為了更好的連接性能。我們會(huì)另外開啟一個(gè)進(jìn)程將這些改動(dòng)更新到數(shù)據(jù)庫。后面會(huì)介紹到。

攔截統(tǒng)計(jì)

我們將第一步中的加載IP白名單的邏輯改成下面這樣:

<?php
$worker->onConnect = function (TcpConnection $connection) {
    $disable_cache_key = 'disable_ip_list';
    $list_ip = Cache::get($disable_cache_key);
    if (empty($list_ip)) {
        $connection->close();
    }
    $remote_ip = $connection->getRemoteIp();
    if (!in_array($remote_ip, $list_ip)) {
        AppIpReject::initRecord($remote_ip);
        $connection->close();
    }
};

在這里我們不連接數(shù)據(jù)庫查詢,而是直接從本地緩存讀取白名單,這樣會(huì)有更好的性能。我們會(huì)在另一個(gè)進(jìn)程中更新這份白名單。

另外我們可以看到,攔截的IP調(diào)用了一個(gè)靜態(tài)方法,這里的功能很簡(jiǎn)單,判斷數(shù)據(jù)庫中該IP是否存在,如果不存在則新增,如果存在,則更新攔截次數(shù)+·1。這里就不多介紹了。這里也沒有必要做什么性能優(yōu)化,反正本來就是攔截的IP,優(yōu)化個(gè)毛。

高性能處理緩存數(shù)據(jù)

上面我們介紹,我們會(huì)另外開啟一個(gè)進(jìn)程,維護(hù)IP白名單,并且將流量統(tǒng)計(jì)提交到數(shù)據(jù)庫。這就是這個(gè)進(jìn)程:

<?php
$worker_ip = new Worker();
$worker_ip->name = 'report';
$worker_ip->onWorkerStart = function () {
    Timer::add(5, function () {
        $disable_cache_key = 'disable_ip_list';
        $list_ip = AppIp::where('status', 1)->column('ip');
        Cache::set($disable_cache_key, $list_ip);
        foreach ($list_ip as  $ip) {
            $ip_md5 = md5($ip);
            $in_length = Cache::pull("$ip_md5-in");
            // 請(qǐng)求的數(shù)據(jù)
            $to_length = Cache::pull("$ip_md5-to");
            // 返回的數(shù)據(jù)
            if (!empty($in_length) || !empty($to_length)) {
                $model_ip = AppIp::where('ip', $ip)->find();
                $model_ip->in_buffer += $in_length;
                $model_ip->to_buffer += $to_length;
                $model_ip->save();
            }
        }
    });
};

他做的事情很簡(jiǎn)單,讀取緩存,更新數(shù)據(jù)到數(shù)據(jù)庫,并且更新IP白名單。這里不需要考慮它和數(shù)據(jù)庫之間的性能問題,這是額外的進(jìn)程,不影響端口的連接和轉(zhuǎn)發(fā)。

下一步,更好的性能設(shè)計(jì)

以上,只有幾行代碼,幾個(gè)小時(shí)(如果不含設(shè)計(jì)系統(tǒng)的時(shí)間,代碼量可能只有一兩個(gè)小時(shí)。還能再怎么優(yōu)化呢?實(shí)際上還是可以優(yōu)化的。

更好的內(nèi)存驅(qū)動(dòng)

這里使用的是ThinkPHP內(nèi)置的文件緩存,存儲(chǔ)到磁盤上,以上方法,在大量連接并發(fā)時(shí),肯定受制于磁盤的性能。所以自然而然,我們可以使用內(nèi)存緩存。

版權(quán)聲明:本文由phpreturn.com(PHP武器庫官網(wǎng))原創(chuàng)和首發(fā),所有權(quán)利歸phpreturnPHP武器庫)所有,本站允許任何形式的轉(zhuǎn)載/引用文章,但必須同時(shí)注明出處。

但是使用內(nèi)存緩存,redis可以嗎?并不好。這里是客戶端,它只是想簡(jiǎn)簡(jiǎn)單單實(shí)現(xiàn)一個(gè)攔截轉(zhuǎn)發(fā),還要再部署redis,不可取。

但實(shí)際上,workerman本身內(nèi)置了數(shù)據(jù)共享組件,這是一個(gè)很好的方案。相當(dāng)于一個(gè)極簡(jiǎn)的redis。完美符合我們的需求。但是我并沒有實(shí)現(xiàn)這個(gè)功能,目前的系統(tǒng)已經(jīng)符合我的場(chǎng)景。

更好的客戶端

目前攔截IP客戶端和管理面板集成在一起,使用相同的配置,面板基于ThinkPHP,客戶端只是ThinkPHP的一個(gè)命令。我之所以這樣做,是希望直接在Workerman中使用ThinkPHP的眾多特性(數(shù)據(jù)庫、緩存。

實(shí)際上,我們可以將客戶端的代碼,另外開一個(gè)項(xiàng)目,使客戶端和面板獨(dú)立開。在面板上實(shí)現(xiàn)通用得API。客戶端通過API操作數(shù)據(jù)。這樣客戶端就不需要連接數(shù)據(jù)庫。好處多多。

但是這樣也帶來的更多的工作量,這種情況下,我們自然而然的認(rèn)為客戶端的環(huán)境不安全,所以要做權(quán)限認(rèn)證,登錄認(rèn)證。接口開發(fā)也要寫更多的代碼。

以上就是關(guān)于“如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

分享名稱:如何用PHP實(shí)現(xiàn)一個(gè)IP防火墻
網(wǎng)站地址:http://muchs.cn/article0/ihedio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、網(wǎng)站制作、企業(yè)網(wǎng)站制作、網(wǎng)頁設(shè)計(jì)公司網(wǎng)站收錄、App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)