PHP 實現base64編碼檔案上傳出現問題詳解

2022-09-26 02:39:12 字數 3402 閱讀 1656

一、場景

領導:小a同學,我們要做乙個樣本上傳進行分析的功能,你看下是否使用base64編碼加進去,這樣客戶端的同學就不需要用form-data方式來上傳了,直接使用json格式就可以上報,可以讓格式上報統一。

小a:好的,領導,馬上搞定!

咋看上面的對話沒啥問題,很多公司團隊內部為了一些標準化的問題,都會進行一些技術選型問題,但是噩夢也就從這個對話開始,功能實現當然都是很簡單的,先來看簡單流程圖:

本身的流程是乙個很簡單的檔案轉換成base64上傳,再服務端decode儲存,在開發聯調過程中沒有問題,非常完美的走下去了。

二、問題來了

突然有一天終端同學誤操作將乙個37m檔案上傳,nginx與php-fpm檔案上傳限制均為(60m),但是在介面出現程式設計客棧500錯誤,進入docker 日誌檢視有一條資料:

allowed memory size of 8388608 bytes exhausted (tried to allocate 1298358 bytes)

玩php的基本都知道這是啥意思,就是**執行過程中使用記憶體超過 我們phercytlp.ini設定的memory_limit 的值,然後就屁顛屁顛進入php.ini找引數配置,很快找到:

memory_limit=128m

然後就轉念一想,不應該出現這個問題,我們知道,php的內部變數使用cow(寫時複製)機制來實現,那麼記憶體申請只有在變數賦值變更才會進行

三、測驗

接下來我們單獨寫乙個程式來進行測試,將乙個4.89m檔案進行base64_encode 編碼 與base64_decode解碼,檢視各自占用記憶體以及過程中占用峰值記憶體

<?php $mid = memory_get_usage();

$apk_content = file_get_contents(__dir__ . '/4bc1c8a05b8505662be778b6dad23b55.apk');

var_dump('檔案載入到記憶體:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'm');

var_dump('過程中峰值使用的記憶體:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'm');

unset($mid);

$mid = memory_get_usage();

$base64_encode = base64_encode($apk_content);unset($apk_content);

var_dump('base64_encode占用記憶體:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'm');

var_dump('過程中峰值使用的記憶體:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'm');

unset($mid);

$mid = memory_get_usage();

base64_decode($base64_encode);

var_dump('base64_decode占用記憶體:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'm');

var_dump('過程中峰值使用的記憶體:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'm');

unset($mid);

執行結果:

string(29) "檔案載入到記憶體:4.89m"

string(38) "過程中峰值使用的記憶體:5.25m"

string(33) "base64_encode占用記憶體:1.63m"

string(39) "過程中峰值使用的記憶體:11.76m"

string(30) "base64_decode占用記憶體:0m"

string(38) "過程中峰值使用的記憶體:13.4m"

通過上面結果可以看出

四、原始碼解析

base64_encode原始碼解析

首先找到對應的c檔案 base64.c,找到裡面php_base64_encode函式

phpapi zend_string *php_base64_encode(const unsigned char *str, size_t length) /*

我們先來分www.cppcns.com析這段**,因為這裡涉及到記憶體的問題,那麼我們就看

result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);

這啥意思呢?

申請記憶體,最終呼叫的函式是:

safe_emalloc(size_t nmemb, size_t size, size_t offset)

在wiki上解釋是:

void *safe_emalloc(size_t nmemb, size_t size, size_t offset)分配緩衝區來存放每塊大小為 size 位元組的 nmemb 塊,並附加 offset 位元組。類似於 emalloc(nmemb * size + offset),但增加了針對溢位的特殊保護。

那麼我可以簡單的認為,就是在encode過程中,重新申請了記憶體,申請的記憶體大小是檔案本身的 4/3 大小,加上原來的檔案本身大小,那麼峰值大小可以理解為

峰值記憶體= 7/3 *4.89 = 11.41

那麼與我們實驗過程中峰值大小基本是相符。

base64_decode操作

同樣我們進行原始碼分析

phpapi zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict) /*

這裡使用的zend_string_alloc來進行申請記憶體,那麼底層使用的函式就是emalloc函式,來看下wiki的解釋

void *emalloc(size_t size)分配 size 位元組的記憶體。

這個就比較好理解了,傳入引數記憶體再進行乙個double拷貝就可以,

那麼我們進行乙個decode的記憶體峰值的計算:

峰值記憶體=(4/3+4/3) *4.89 =13.04

基本與我們測試的結果相差不多,因為精度關係,我們進行四捨五入的計算,測試**是精準計算,所以會有小數點偏差。

五、總結

那這就可以理解為什麼乙個為什麼在我們乙個37m的檔案,不能再128m記憶體進行base64_encode與base64程式設計客棧_decode操作,當然這裡有一些臨時變數沒有及時釋放記憶體的情況,但是通過原始碼分析可以知道,要做一次這樣場景來進行檔案上傳,單純檔案的記憶體損耗是2.6倍左右,所以為了節省記憶體,我們不要再用這個方式來進行操作了,很費記憶體的

本文標題: php 實現base64編碼檔案上傳出現問題詳解

本文位址:

php實現base64編碼

工作需要,想弄乙個加密的串,就想仿照base64的編碼方式,寫乙個加密的方法,所以就有了下面這個用php實現的base64的 base64 編碼 解碼 author liruixing class base64 bin bin ord str if i 1 3 0 if len 3 1 else i...

base64編碼實現

package com.ls.hfvj 思路 base64只有64個字元,因此只需要6個二進位制位來表示 實現 每3個位元組為一組湊4個base64字元。多餘乙個位元組補4個0bit位 共12位 湊成2個base64字元 多餘兩個位元組補2個bit位 共18位 湊成3個base64字元。為了知道新增...

base64編碼 動畫演示 Base 64 編碼

base64 是一種十分流行的編碼方式,僅僅使用 64 個字元加等號 就可以以文字的形式表示所有的二進位制資料了,因為它能夠把二進位制格式通過編碼轉換成可見字元,所有我們就可以通過的把二進位制格式比如,檔案等通過 base64 編碼然後通過文字的形式共享出去,是不是很神奇呀。把輸入的資料轉換成 二進...