首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 與 Java Socket 通訊實作:TCP 連線雙向資料傳輸範例

     編輯:PHP 綜合知識

架構概覽

  • Java 端:ServerSocket 監聽埠口,接受連線,讀取一行文字,原樣回應
  • PHP 端:socket_create 連線,發送資料,讀取回應,關閉連線
  • 協定:純文字、換行符結尾 (\n)、請求-回應模式

Java 伺服器端完整程式碼

import java.io.*;
import java.net.*;

public class EchoServer {
    private static final int PORT = 5678;

    public static void main(String[] args) {
        System.out.println("Server starting on port " + PORT + "...");

        try (ServerSocket server = new ServerSocket(PORT)) {
            server.setReuseAddress(true);
            System.out.println("Server started. Waiting for connections...");

            while (true) {
                try (Socket client = server.accept();
                     BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
                     PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"), true)) {

                    String clientIp = client.getInetAddress().getHostAddress();
                    System.out.println("Client connected: " + clientIp);

                    String line;
                    while ((line = in.readLine()) != null) {
                        System.out.println("Received from " + clientIp + ": " + line);
                        // 業務邏輯處理
                        String response = processRequest(line);
                        out.println(response); // 自動 flush
                    }
                    System.out.println("Client disconnected: " + clientIp);
                } catch (IOException e) {
                    System.err.println("Client handling error: " + e.getMessage());
                }
            }
        } catch (IOException e) {
            System.err.println("Server fatal error: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static String processRequest(String input) {
        // 這裡實作實際業務邏輯
        // 範例:直接回聲
        return "ECHO: " + input;
    }
}

關鍵點說明

  • try-with-resources:Java 7+ 自動關閉資源,防洩漏
  • setReuseAddress(true):避免重啟時 Address already in use
  • PrintWriter(..., true):啟用自動 flush,println() 即時發送
  • readLine():阻塞讀取至換行符,適配 PHP PHP_NORMAL_READ
  • 編碼統一指定 UTF-8,避免中文亂碼

PHP 客戶端完整程式碼

<?php
/**
 * Socket 客戶端封裝類
 */
class SocketClient {
    private $socket;
    private $host;
    private $port;
    private float $timeout;

    public function __construct(string $host = '127.0.0.1', int $port = 5678, float $timeout = 5.0) {
        $this->host = $host;
        $this->port = $port;
        $this->timeout = $timeout;
    }

    public function connect(): bool {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($this->socket === false) {
            throw new RuntimeException('socket_create failed: ' . socket_strerror(socket_last_error()));
        }

        // 設定連線與收發逾時
        socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => (int)$this->timeout, 'usec' => 0]);
        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => (int)$this->timeout, 'usec' => 0]);

        $result = @socket_connect($this->socket, $this->host, $this->port);
        if ($result === false) {
            $err = socket_last_error($this->socket);
            socket_close($this->socket);
            throw new RuntimeException("socket_connect failed ({$err}): " . socket_strerror($err));
        }
        return true;
    }

    public function send(string $data): bool {
        // 確保以 \n 結尾
        if (!str_ends_with($data, "\n")) $data .= "\n";
        $bytes = socket_write($this->socket, $data, strlen($data));
        return $bytes !== false && $bytes === strlen($data);
    }

    public function receive(int $maxLength = 8192): ?string {
        // PHP_NORMAL_READ: 讀取至 \n 或 \r\n
        $data = socket_read($this->socket, $maxLength, PHP_NORMAL_READ);
        if ($data === false) {
            throw new RuntimeException('socket_read failed: ' . socket_strerror(socket_last_error($this->socket)));
        }
        return $data !== '' ? rtrim($data, "\r\n") : null;
    }

    public function close(): void {
        if (is_resource($this->socket)) {
            socket_close($this->socket);
            $this->socket = null;
        }
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用範例
try {
    $client = new SocketClient('127.0.0.1', 5678);
    $client->connect();

    $request = 'Hello from PHP ' . date('H:i:s');
    echo "Sending: {$request}\n";
    $client->send($request);

    $response = $client->receive();
    echo "Received: {$response}\n";

    $client->close();
} catch (Throwable $e) {
    echo "Error: {$e->getMessage()}\n";
    exit(1);
}

進階:支援 JSON 協定的雙向通訊

<?php
class JsonSocketClient extends SocketClient {
    public function sendRequest(string $action, array $payload = []): ?array {
        $request = [
            'action' => $action,
            'payload' => $payload,
            'timestamp' => microtime(true),
            'request_id' => bin2hex(random_bytes(8)),
        ];
        $json = json_encode($request, JSON_UNESCAPED_UNICODE);
        if (!$this->send($json)) return null;

        $response = $this->receive();
        if ($response === null) return null;

        $data = json_decode($response, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log('Invalid JSON response: ' . $response);
            return null;
        }
        return $data;
    }
}

// 使用
$client = new JsonSocketClient('127.0.0.1', 5678);
$client->connect();

$result = $client->sendRequest('calculate', ['a' => 10, 'b' => 5, 'op' => 'add']);
if ($result && ($result['status'] ?? '') === 'ok') {
    echo "Result: {$result['data']['result']}\n"; // 15
}
$client->close();

Java 端對應 JSON 處理

// 使用 Jackson 或 Gson 解析
// 依賴: com.fasterxml.jackson.core:jackson-databind
private static String processRequest(String input) {
    try {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode req = mapper.readTree(input);
        String action = req.get("action").asText();
        JsonNode payload = req.get("payload");

        ObjectNode resp = mapper.createObjectNode();
        resp.put("request_id", req.get("request_id").asText());
        resp.put("timestamp", System.currentTimeMillis() / 1000.0);

        switch (action) {
            case "calculate" -> {
                double a = payload.get("a").asDouble();
                double b = payload.get("b").asDouble();
                String op = payload.get("op").asText();
                double result = switch (op) {
                    case "add" -> a + b;
                    case "sub" -> a - b;
                    case "mul" -> a * b;
                    case "div" -> b != 0 ? a / b : Double.NaN;
                    default -> Double.NaN;
                };
                resp.put("status", "ok");
                resp.putObject("data").put("result", result);
            }
            default -> {
                resp.put("status", "error");
                resp.put("message", "Unknown action: " + action);
            }
        }
        return mapper.writeValueAsString(resp);
    } catch (Exception e) {
        ObjectNode err = mapper.createObjectNode();
        err.put("status", "error");
        err.put("message", e.getMessage());
        return mapper.writeValueAsString(err);
    }
}

部署與除錯清單

項目檢查重點
防火牆伺服器開放埠口 (5678),雲端安全群組同步設定
綁定位址Java new ServerSocket(port) 預設綁定 0.0.0.0,內網可用;若需限制本機改 new ServerSocket(port, 50, InetAddress.getByName("127.0.0.1"))
編碼一致雙端均指定 UTF-8,含 JSON 序列化
逾時設定PHP socket_set_option、Java socket.setSoTimeout()
程序守護Java 用 systemd/supervisor 管理,自動重啟、日誌輪替
連線池高頻呼叫建議 PHP 端用持久連線或連線池 (swoole/Workerman)

替代方案比較

  • HTTP/REST + JSON:通用性最好、除錯容易、支援負載平衡、但有 HTTP 標頭開銷
  • gRPC:高效能、強型別契約、支援流式、但需 Protobuf、學習曲線較陡
  • Message Queue (RabbitMQ/Kafka):非同步、解耦、削峰填谷、但引入基建複雜度
  • Raw Socket (本文):極低延遲、雙向即時、適合長連線、但需自行處理協定、心跳、重連
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved