在工業物聯網、智能硬體、POS 機對接等場景中,PHP 作為後端服務常需與設備進行 Socket 級通訊。本文系統整理三種主流通訊模式的完整封裝代碼:TCP 阻塞/非阻塞連接、UDP 無連接收發、RS232 串口讀寫,並附帶 16 進制指令轉換工具函數,可直接用於生產環境。
適用於下發控制指令、心跳包等場景。採用非阻塞連接 + 超時輪詢,避免設備離線導致 PHP 進程卡死。
/**
* TCP 發送命令(無返回值)
* @param int $port 目標端口
* @param string $address 目標 IP
* @param string $cmd 發送內容(原始字節/字符串)
*/
function tcp_send_only($port, $address, $cmd) {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { die("Create socket failed: " . socket_strerror(socket_last_error())); }
$timeout = 2; // 秒
$start = time();
socket_set_nonblock($socket);
// 非阻塞連接輪詢
while (!@socket_connect($socket, $address, $port)) {
$err = socket_last_error($socket);
if ($err === 10056 || $err === 115) { // 已連接 / EINPROGRESS
break;
}
if (time() - $start >= $timeout) {
socket_close($socket);
throw new Exception("TCP 連接超時: $address:$port");
}
usleep(250000); // 250ms
}
socket_set_block($socket);
socket_write($socket, $cmd, strlen($cmd));
socket_close($socket);
}
適用於查詢設備狀態、讀取傳感器數據等雙向通訊場景。
/**
* TCP 發送命令並讀取響應
* @return string|false 設備返回數據,失敗拋異常
*/
function tcp_send_receive($port, $address, $cmd) {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { throw new Exception("Socket create failed"); }
$timeout = 2;
$start = time();
socket_set_nonblock($socket);
while (!@socket_connect($socket, $address, $port)) {
$err = socket_last_error($socket);
if ($err === 10056 || $err === 115) { break; }
if (time() - $start >= $timeout) {
socket_close($socket);
throw new Exception("TCP 連接超時");
}
usleep(250000);
}
socket_set_block($socket);
socket_write($socket, $cmd, strlen($cmd));
// 讀取響應(根據協議調整長度/模式)
$buffer = socket_read($socket, 1024, PHP_NORMAL_READ);
socket_close($socket);
if ($buffer === false || $buffer === '') {
throw new Exception("設備無響應或讀取失敗");
}
return $buffer;
}
UDP 無需建立連接,延遲極低,但不保證送達與順序。適用於局域網設備發現、實時遙測等場景。
/**
* UDP 發送命令並可選接收響應
* @param bool $need_response 是否等待響應
*/
function udp_communicate($port, $address, $cmd, $need_response = true) {
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if (!$socket) { throw new Exception("UDP socket create failed"); }
// 設置接收超時 2 秒
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
// 發送
$sent = @socket_sendto($socket, $cmd, strlen($cmd), 0, $address, $port);
if ($sent === false) {
socket_close($socket);
throw new Exception("UDP 發送失敗");
}
if (!$need_response) {
socket_close($socket);
return true;
}
// 接收
$buffer = '';
$from = '';
$port_recv = 0;
$result = @socket_recvfrom($socket, $buffer, 1024, MSG_WAITALL, $from, $port_recv);
socket_close($socket);
if ($result === false || $buffer === '') {
throw new Exception("UDP 無響應或接收失敗");
}
return $buffer;
}
PHP 在 Windows 下可通過 fopen('COM1:', 'w+') 直接讀寫串口,配合 mode 命令設置波特率、校驗位等參數。
/**
* RS232 串口發送命令
* @param string $com 串口號,如 'COM1'、'COM3'
* @param int $baudrate 波特率,如 9600、115200
* @param string $cmd 發送的原始字節字符串
*/
function rs232_send($com, $baudrate, $cmd) {
@set_time_limit(10);
// 設置串口參數:波特率、無校驗、8數據位、1停止位、關閉硬件流控
$mode_cmd = "mode {$com} BAUD={$baudrate} PARITY=n DATA=8 STOP=1 odsr=off";
exec($mode_cmd, $output, $return_var);
if ($return_var !== 0) {
throw new Exception("串口參數設置失敗: $mode_cmd");
}
// 打開串口(需管理員權限)
$fp = @fopen($com, 'w+');
if (!$fp) {
throw new Exception("無法打開串口 $com,請檢查權限或是否被佔用");
}
// 發送數據
fwrite($fp, $cmd);
fclose($fp);
}
設備協議常用 16 進制指令(如 "AA 55 01 02 FF"),需轉為二進制字符串發送。
/**
* 16 進制字符串轉二進制字節字符串
* @param string $hex_str 格式:"AA 55 01 02 FF" 或 "AA550102FF"
* @return string 二進制字符串
*/
function hex_to_binary($hex_str) {
// 移除空格
$hex_str = str_replace(' ', '', $hex_str);
// 校驗長度為偶數
if (strlen($hex_str) % 2 !== 0) {
throw new Exception("16進制字符串長度必須為偶數");
}
$binary = '';
for ($i = 0; $i < strlen($hex_str); $i += 2) {
$binary .= chr(hexdec(substr($hex_str, $i, 2)));
}
return $binary;
}
// 使用示例
$cmd_hex = "AA 55 01 02 FF";
$cmd_bin = hex_to_binary($cmd_hex); // 返回 5 字節二進制字符串
tcp_send_receive(502, '192.168.1.100', $cmd_bin);
dialout 組;Windows 下 Apache/PHP 需以管理員身份運行。將上述函數封裝為 DeviceCommunicator 類,配合依賴注入容器,即可在 Laravel/ThinkPHP/Symfony 等框架中優雅地調用:$device->tcpSendReceive($cmd)。掌握這三種通訊模式,讓 PHP 輕松駕馭硬體世界。