佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > PHP 實(shí)現(xiàn)base64編碼文件上傳出現(xiàn)問(wèn)題詳解

PHP 實(shí)現(xiàn)base64編碼文件上傳出現(xiàn)問(wèn)題詳解

熱門標(biāo)簽:Mysql連接數(shù)設(shè)置 銀行業(yè)務(wù) 服務(wù)器配置 團(tuán)購(gòu)網(wǎng)站 阿里云 電子圍欄 Linux服務(wù)器 科大訊飛語(yǔ)音識(shí)別系統(tǒng)

一、場(chǎng)景

領(lǐng)導(dǎo):小A同學(xué),我們要做一個(gè)樣本上傳進(jìn)行分析的功能,你看下是否使用base64編碼加進(jìn)去,這樣客戶端的同學(xué)就不需要用form-data方式來(lái)上傳了,直接使用json格式就可以上報(bào),可以讓格式上報(bào)統(tǒng)一。

小A:好的,領(lǐng)導(dǎo),馬上搞定!

咋看上面的對(duì)話沒(méi)啥問(wèn)題,很多公司團(tuán)隊(duì)內(nèi)部為了一些標(biāo)準(zhǔn)化的問(wèn)題,都會(huì)進(jìn)行一些技術(shù)選型問(wèn)題,但是噩夢(mèng)也就從這個(gè)對(duì)話開(kāi)始,功能實(shí)現(xiàn)當(dāng)然都是很簡(jiǎn)單的,先來(lái)看簡(jiǎn)單流程圖:

本身的流程是一個(gè)很簡(jiǎn)單的文件轉(zhuǎn)換成base64上傳,再服務(wù)端decode保存,在開(kāi)發(fā)聯(lián)調(diào)過(guò)程中沒(méi)有問(wèn)題,非常完美的走下去了。

二、問(wèn)題來(lái)了

突然有一天終端同學(xué)誤操作將一個(gè)37M文件上傳,nginx與php-fpm文件上傳限制均為(60M),但是在界面出現(xiàn)500錯(cuò)誤,進(jìn)入docker 日志查看有一條數(shù)據(jù):

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

玩php的基本都知道這是啥意思,就是代碼運(yùn)行過(guò)程中使用內(nèi)存超過(guò) 我們php.ini設(shè)置的memory_limit 的值,然后就屁顛屁顛進(jìn)入php.ini找參數(shù)配置,很快找到:

memory_limit=128M

然后就轉(zhuǎn)念一想,不應(yīng)該出現(xiàn)這個(gè)問(wèn)題,我們知道,php的內(nèi)部變量使用cow(寫時(shí)復(fù)制)機(jī)制來(lái)實(shí)現(xiàn),那么內(nèi)存申請(qǐng)只有在變量賦值變更才會(huì)進(jìn)行

三、測(cè)驗(yàn)

接下來(lái)我們單獨(dú)寫一個(gè)程序來(lái)進(jìn)行測(cè)試,將一個(gè)4.89M文件進(jìn)行base64_encode 編碼 與base64_decode解碼,查看各自占用內(nèi)存以及過(guò)程中占用峰值內(nèi)存

?php
$mid = memory_get_usage();
$apk_content = file_get_contents(__DIR__ . '/4bc1c8a05b8505662be778b6dad23b55.apk');
var_dump('文件加載到內(nèi)存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M');
var_dump('過(guò)程中峰值使用的內(nèi)存:' . 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占用內(nèi)存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M');
var_dump('過(guò)程中峰值使用的內(nèi)存:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'M');

unset($mid);
$mid = memory_get_usage();
base64_decode($base64_encode);
var_dump('base64_decode占用內(nèi)存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M');
var_dump('過(guò)程中峰值使用的內(nèi)存:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'M');
unset($mid);

執(zhí)行結(jié)果:

string(29) "文件加載到內(nèi)存:4.89M"
string(38) "過(guò)程中峰值使用的內(nèi)存:5.25M"
string(33) "base64_encode占用內(nèi)存:1.63M"
string(39) "過(guò)程中峰值使用的內(nèi)存:11.76M"
string(30) "base64_decode占用內(nèi)存:0M"
string(38) "過(guò)程中峰值使用的內(nèi)存:13.4M"

通過(guò)上面結(jié)果可以看出

  • 加載文件使用內(nèi)存沒(méi)有太大問(wèn)題,加載過(guò)程使用的峰值在5.25M,高出整體文件大小不多,這在文件加載過(guò)程有一些臨時(shí)申請(qǐng)內(nèi)存的問(wèn)題
  • base64_encode占用內(nèi)存,這個(gè)在使用的時(shí)候,就已經(jīng)將內(nèi)存差不多進(jìn)行一個(gè)double,而這基本上也是在內(nèi)核解析過(guò)程中,進(jìn)行了內(nèi)存申請(qǐng),可以理解,文件本身占用內(nèi)存+base64_encode 解析后的內(nèi)存,兩份內(nèi)存同時(shí)存在的
  • base64_decode操作,這個(gè)操作就是解密了,解密過(guò)程中,這里直接就占用了3倍多的內(nèi)存操作,問(wèn)題就出在這里,在場(chǎng)景中出現(xiàn)的問(wèn)題是一個(gè)37M的文件,為什么就把單個(gè)fpm的128M內(nèi)存占滿了呢

四、源碼解析

base64_encode源碼解析

首先找到對(duì)應(yīng)的c文件 base64.c,找到里面php_base64_encode函數(shù)

PHPAPI zend_string *php_base64_encode(const unsigned char *str, size_t length) /* {{{ */
{
	const unsigned char *current = str;
	unsigned char *p;
	zend_string *result;

	result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);
	p = (unsigned char *)ZSTR_VAL(result);
    ...
}

我們先來(lái)分析這段代碼,因?yàn)檫@里涉及到內(nèi)存的問(wèn)題,那么我們就看

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

這啥意思呢?

申請(qǐng)內(nèi)存,最終調(diào)用的函數(shù)是:

safe_emalloc(size_t nmemb, size_t size, size_t offset)

在wiki上解釋是:

void *safe_emalloc(size_t nmemb, size_t size, size_t offset)分配緩沖區(qū)來(lái)存放每塊大小為 size 字節(jié)的 nmemb 塊,并附加 offset 字節(jié)。類似于 emalloc(nmemb * size + offset),但增加了針對(duì)溢出的特殊保護(hù)。

那么我可以簡(jiǎn)單的認(rèn)為,就是在encode過(guò)程中,重新申請(qǐng)了內(nèi)存,申請(qǐng)的內(nèi)存大小是文件本身的 4/3 大小,加上原來(lái)的文件本身大小,那么峰值大小可以理解為

峰值內(nèi)存= 7/3 *4.89 = 11.41

那么與我們實(shí)驗(yàn)過(guò)程中峰值大小基本是相符。

base64_decode操作

同樣我們進(jìn)行源碼分析

PHPAPI zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict) /* {{{ */
{
	const unsigned char *current = str;
	int ch, i = 0, j = 0, padding = 0;
	zend_string *result;

	result = zend_string_alloc(length, 0);
	...
}

這里使用的zend_string_alloc來(lái)進(jìn)行申請(qǐng)內(nèi)存,那么底層使用的函數(shù)就是emalloc函數(shù),來(lái)看下wiki的解釋

void *emalloc(size_t size)分配 size 字節(jié)的內(nèi)存。

這個(gè)就比較好理解了,傳入?yún)?shù)內(nèi)存再進(jìn)行一個(gè)double拷貝就可以,

那么我們進(jìn)行一個(gè)decode的內(nèi)存峰值的計(jì)算:

峰值內(nèi)存=(4/3+4/3) *4.89 =13.04

基本與我們測(cè)試的結(jié)果相差不多,因?yàn)榫汝P(guān)系,我們進(jìn)行四舍五入的計(jì)算,測(cè)試代碼是精準(zhǔn)計(jì)算,所以會(huì)有小數(shù)點(diǎn)偏差。

五、總結(jié)

那這就可以理解為什么一個(gè)為什么在我們一個(gè)37M的文件,不能再128M內(nèi)存進(jìn)行base64_encode與base64_decode操作,當(dāng)然這里有一些臨時(shí)變量沒(méi)有及時(shí)釋放內(nèi)存的情況,但是通過(guò)源碼分析可以知道,要做一次這樣場(chǎng)景來(lái)進(jìn)行文件上傳,單純文件的內(nèi)存損耗是2.6倍左右,所以為了節(jié)省內(nèi)存,我們不要再用這個(gè)方式來(lái)進(jìn)行操作了,很費(fèi)內(nèi)存的

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • PHP實(shí)現(xiàn)本地圖片轉(zhuǎn)base64格式并上傳
  • PHP保存Base64圖片base64_decode的問(wèn)題整理
  • php curl簡(jiǎn)單采集圖片生成base64編碼(并附curl函數(shù)參數(shù)說(shuō)明)
  • PHP實(shí)現(xiàn)將base64編碼字符串轉(zhuǎn)換成圖片示例
  • php讀取和保存base64編碼的圖片內(nèi)容
  • php實(shí)現(xiàn)base64圖片上傳方式實(shí)例代碼
  • php解析base64數(shù)據(jù)生成圖片的方法
  • php實(shí)現(xiàn)將base64格式圖片保存在指定目錄的方法
  • 利用PHP將圖片轉(zhuǎn)換成base64編碼的實(shí)現(xiàn)方法
  • php中base64_decode與base64_encode加密解密函數(shù)實(shí)例

標(biāo)簽:蚌埠 大理 廣元 衢州 棗莊 衡水 江蘇 萍鄉(xiāng)

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《PHP 實(shí)現(xiàn)base64編碼文件上傳出現(xiàn)問(wèn)題詳解》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    闸北区| 桃源县| 台州市| 长武县| 石景山区| 潢川县| 保山市| 遵义县| 宁化县| 通化县| 平定县| 桃园县| 娱乐| 舞钢市| 海安县| 长顺县| 抚顺市| 克拉玛依市| 会泽县| 常熟市| 辽中县| 南阳市| 连平县| 且末县| 额济纳旗| 满洲里市| 滁州市| 青阳县| 吕梁市| 黎城县| 同德县| 思南县| 嵊泗县| 灵石县| 雷山县| 肥东县| 鹤庆县| 兴和县| 永州市| 大荔县| 福海县|