在PHP 中,使用fsockopen()函數來實現FTP 文件上傳的過程相對來說比較低級,涉及到對FTP 協議的手動操作。雖然PHP 提供了ftp_*系列的函數來簡化FTP 上傳的過程,但是有時候你可能會遇到特殊需求,或者為了更細粒度地控制上傳流程,選擇直接通過fsockopen()來實現。本文將詳細介紹如何通過fsockopen()實現FTP 文件上傳,並指出一些需要特別注意的關鍵點。
FTP(File Transfer Protocol)是一種常用的網絡協議,用於在客戶端與服務器之間傳輸文件。使用fsockopen()函數,可以在PHP 中打開一個到FTP 服務器的套接字連接,從而與FTP 服務器進行數據交互,執行上傳、下載等操作。
建立連接<br> 首先,我們需要使用fsockopen()打開到FTP 服務器的連接通常,FTP 服務器的默認端口是21。
<span><span><span class="hljs-variable">$ftp_server</span></span><span> = </span><span><span class="hljs-string">'ftp.example.com'</span></span><span>;
</span><span><span class="hljs-variable">$ftp_port</span></span><span> = </span><span><span class="hljs-number">21</span></span><span>;
</span><span><span class="hljs-variable">$ftp_user</span></span><span> = </span><span><span class="hljs-string">'username'</span></span><span>;
</span><span><span class="hljs-variable">$ftp_pass</span></span><span> = </span><span><span class="hljs-string">'password'</span></span><span>;
</span><span><span class="hljs-comment">// 創建到FTP服務器的連接</span></span><span>
</span><span><span class="hljs-variable">$ftp_socket</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fsockopen</span></span><span>(</span><span><span class="hljs-variable">$ftp_server</span></span><span>, </span><span><span class="hljs-variable">$ftp_port</span></span><span>, </span><span><span class="hljs-variable">$errno</span></span><span>, </span><span><span class="hljs-variable">$errstr</span></span><span>, </span><span><span class="hljs-number">30</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$ftp_socket</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"FTP連接失敗: <span class="hljs-subst">$errstr</span></span></span><span> (</span><span><span class="hljs-subst">$errno</span></span><span>)");
}
</span></span>
接收服務器響應
FTP 通常會在連接建立時發送一個歡迎消息。我們需要讀取並檢查服務器的響應,確保成功連接。
<span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'220'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"連接失敗: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
發送用戶名和密碼<br> 在FTP 協議中,登錄過程是通過發送用戶名(USER)和密碼(PASS)命令來完成的我們可以使用fputs()函數發送這些命令。
<span><span><span class="hljs-comment">// 發送用戶名</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"USER <span class="hljs-subst">$ftp_user</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'331'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"用戶名錯誤: <span class="hljs-subst">$response</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 發送密碼</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"PASS <span class="hljs-subst">$ftp_pass</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'230'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"密碼錯誤: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
設置被動模式(可選)
在某些網絡環境中,使用被動模式(PASV)更為穩定。被動模式會在FTP 服務器端開啟一個新的端口,客戶端連接該端口進行數據傳輸。
<span><span><span class="hljs-comment">// 進入被動模式</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"PASV\r\n"</span></span><span>);
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'227'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"進入被動模式失败: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
服務器返回的響應中會包含一個數據端口的信息,您需要解析這個響應來獲得數據端口。
上傳文件<br> 要上傳文件,需要先發送STOR命令指定文件的上傳位置,然後將文件內容逐塊傳輸給服務器
<span><span><span class="hljs-variable">$local_file</span></span><span> = </span><span><span class="hljs-string">'local_file.txt'</span></span><span>;
</span><span><span class="hljs-variable">$remote_file</span></span><span> = </span><span><span class="hljs-string">'remote_file.txt'</span></span><span>;
</span><span><span class="hljs-comment">// 打開本地文件</span></span><span>
</span><span><span class="hljs-variable">$file</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fopen</span></span><span>(</span><span><span class="hljs-variable">$local_file</span></span><span>, </span><span><span class="hljs-string">'rb'</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$file</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"无法打開本地文件: <span class="hljs-subst">$local_file</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 發送 STOR 命令開始上傳</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"STOR <span class="hljs-subst">$remote_file</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'150'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"開始上傳失敗: <span class="hljs-subst">$response</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 逐塊上傳文件內容</span></span><span>
</span><span><span class="hljs-keyword">while</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">feof</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>)) {
</span><span><span class="hljs-variable">$data</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fread</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-variable">$data</span></span><span>);
}
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>);
</span></span>
關閉連接<br> 上傳完文件後,關閉FTP 連接
<span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"QUIT\r\n"</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>);
</span></span>
錯誤處理
FTP 協議涉及多個命令和響應,所以錯誤處理非常重要。每個FTP 命令的響應都包含一個三位數的狀態碼(如220 , 230 , 331等),通過檢查這些狀態碼,可以確定操作是否成功。
被動模式與主動模式<br> 在某些網絡環境下,使用被動模式可以避免NAT(網絡地址轉換)設備和防火牆的阻塞如果默認模式不適合,記得切換到被動模式。
二進制與ASCII模式<br> 上傳文件時,需要確保文件傳輸模式的正確設置對於二進製文件(如圖片、視頻、壓縮文件等),應該使用二進制模式(即TYPE I )。對於文本文件,使用ASCII 模式(即TYPE A )。
緩衝區大小<br> 在文件上傳過程中,讀取和寫入操作時,可以通過調整緩衝區大小來提高上傳性能fread()和fputs()的緩衝區大小會影響數據傳輸的速度。
防止資源洩露<br> 在通過fsockopen()創建套接字連接後,務必記得在上傳完成後關閉連接,避免資源洩漏
通過fsockopen()實現FTP 文件上傳雖然相對底層,但它給予開發者更多控製文件傳輸的能力。通過手動實現FTP 協議的每個步驟,我們可以在復雜的場景下進行定制化操作。然而,使用時需要注意協議的細節、錯誤處理、傳輸模式等問題,確保上傳過程的順利進行。對於常規的文件上傳需求,可以使用PHP 內置的FTP 函數來簡化操作,而通過fsockopen()進行定制化的上傳操作則適合需要更細緻控制的場景。
相關標籤:
FTP