Workerman TCP 服务器的流程和概念

运行:
先运行php 1.php start
在运行php 2.php start

1.php

<?php
/**
 * 简单的 TCP 服务器示例
 *
 * 演示TCP四个核心特点:
 * 1. ✓ 面向连接 - 必须先建立连接才能通信
 * 2. ✓ 可靠传输 - 数据按顺序到达,不会丢失
 * 3. ✓ 流式数据 - 支持持续的数据流传输
 * 4. ✓ 全双工通信 - 服务器和客户端可以同时收发
 *
 * 启动: php tcp-simple-demo.php start
 * 测试: telnet 127.0.0.1 8888
 *       或者: php tcp-simple-client.php
 */

require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;
use Workerman\Timer;

// 创建TCP服务器
$tcp = new Worker('tcp://0.0.0.0:8888');
$tcp->name = 'SimpleTCP';
$tcp->count = 1;

// ========================================
// 特点1: 面向连接 (Connection-Oriented)
// ========================================
// TCP需要先建立连接,连接建立后才能通信

$tcp->onConnect = function($connection) {
    echo "\n【1.面向连接】新连接建立\n";
    echo "  客户端IP: {$connection->getRemoteIp()}\n";
    echo "  客户端端口: {$connection->getRemotePort()}\n";

    // 给连接分配ID
    $connection->id = uniqid('tcp_');

    // 发送欢迎消息
    $connection->send("✓ 连接建立成功!你的ID是: {$connection->id}\n");
    $connection->send("可用命令: echo 消息 | time | quit\n\n");
};

// ========================================
// 特点2: 可靠传输 (Reliable Delivery)
// ========================================
// TCP保证数据按顺序到达,不会丢失

$tcp->onMessage = function($connection, $data) {
    $data = trim($data);
    echo "\n【2.可靠传输】收到数据: {$data}\n";

    if (strpos($data, 'echo') === 0) {
        // 原样返回,证明数据完整传输
        $msg = substr($data, 5);
        $connection->send("服务器回显: {$msg}\n");
        echo "  → 数据已原样返回,证明传输可靠\n";

    } elseif ($data === 'time') {
        $time = date('Y-m-d H:i:s');
        $connection->send("服务器时间: {$time}\n");

    } elseif ($data === 'quit') {
        $connection->send("再见!\n");
        $connection->close();
    } else {
        $connection->send("未知命令: {$data}\n");
    }
};

// ========================================
// 特点3: 流式数据 (Stream-Based)
// ========================================
// TCP支持持续的数据流传输,不是一次性的

$tcp->onWorkerStart = function($worker) {
    echo "\n";
    echo "════════════════════════════════════════\n";
    echo "  🚀 简单TCP服务器启动\n";
    echo "════════════════════════════════════════\n";
    echo "监听: tcp://0.0.0.0:8888\n";
    echo "测试: telnet 127.0.0.1 8888\n";
    echo "════════════════════════════════════════\n\n";

    // 每3秒向所有连接推送数据(演示流式传输)
    Timer::add(3, function() use ($worker) {
        if (empty($worker->connections)) {
            return;
        }

        echo "\n【3.流式数据】定时推送数据流\n";
        $time = date('H:i:s');
        foreach ($worker->connections as $conn) {
            $conn->send("📊 [{$time}] 服务器心跳\n");
        }
        echo "  → 持续推送数据,不需要客户端请求\n";
    });
};

// ========================================
// 特点4: 全双工通信 (Full-Duplex)
// ========================================
// 服务器和客户端可以同时发送和接收数据

$tcp->onClose = function($connection) {
    echo "\n【4.全双工通信】连接关闭\n";
    echo "  连接ID: {$connection->id}\n";
    echo "  → 在连接期间,双方可以随时收发数据\n";
};

// 启动服务器
Worker::runAll();

2.php

<?php
/**
 * 简单的 TCP 客户端示例
 *
 * 用于测试 tcp-simple-demo.php 服务器
 * 启动: php tcp-simple-client.php
 */

require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;
use Workerman\Connection\AsyncTcpConnection;

// 创建一个虚拟Worker用于事件循环
$worker = new Worker();

$worker->onWorkerStart = function() {
    echo "\n";
    echo "════════════════════════════════════════\n";
    echo "  🔌 TCP客户端连接中...\n";
    echo "════════════════════════════════════════\n\n";

    // 创建TCP连接
    $conn = new AsyncTcpConnection('tcp://127.0.0.1:8888');

    // 连接成功
    $conn->onConnect = function($conn) {
        echo "✅ 连接成功!\n\n";
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
        echo "演示说明:\n";
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
        echo "1️⃣  面向连接:你已经建立了TCP连接\n";
        echo "2️⃣  可靠传输:发送 'echo 你好' 测试\n";
        echo "3️⃣  流式数据:服务器每3秒推送心跳\n";
        echo "4️⃣  全双工通信:可以随时收发消息\n";
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";

        // 自动发送测试消息
        echo ">> 发送测试消息...\n\n";
        $conn->send("echo Hello TCP!\n");

        // 5秒后发送time命令
        \Workerman\Timer::add(5, function() use ($conn) {
            echo ">> 发送 time 命令...\n\n";
            $conn->send("time\n");
        }, [], false);

        // 10秒后断开
        \Workerman\Timer::add(10, function() use ($conn) {
            echo ">> 10秒演示完成,断开连接...\n\n";
            $conn->send("quit\n");
        }, [], false);
    };

    // 收到服务器消息
    $conn->onMessage = function($conn, $data) {
        echo "📩 收到: {$data}";
    };

    // 连接关闭
    $conn->onClose = function($conn) {
        echo "\n════════════════════════════════════════\n";
        echo "❌ 连接已关闭\n";
        echo "════════════════════════════════════════\n";
        Worker::stopAll();
    };

    // 连接错误
    $conn->onError = function($conn, $code, $msg) {
        echo "⚠️  错误: {$msg}\n";
    };

    // 发起连接
    $conn->connect();
};

// 运行
Worker::runAll();

2. 什么是 TCP?

基本概念

TCP(传输控制协议)是一种可靠的通信方式。

类比:打电话 vs 发短信

TCP(像打电话)
✓ 先接通(握手)
✓ 实时对话(双向通信)
✓ 确认对方收到(可靠)
✓ 挂断电话(关闭连接)

UDP(像发短信)
✗ 直接发送(无连接)
✗ 不确认是否收到
✓ 速度快但可能丢失

TCP 三次握手(建立连接)

客户端                        服务器
  |                             |
  |----① SYN(你好)----------->|
  |                             |
  |<---② SYN-ACK(你好,收到)---|
  |                             |
  |----③ ACK(好的,开始吧)---->|
  |                             |
  |======= 连接建立 =============|

3. 整体工作流程

完整流程图

┌─────────────────────────────────────────────────────────────┐
│                      【服务器启动】                          │
│                                                              │
│  1. 创建 Worker 对象                                         │
│     new Worker('tcp://0.0.0.0:8080')                        │
│                                                              │
│  2. 设置回调函数                                             │
│     - onWorkerStart  (Worker启动时)                         │
│     - onConnect      (客户端连接时)                         │
│     - onMessage      (收到消息时)                           │
│     - onClose        (连接断开时)                           │
│                                                              │
│  3. 启动监听                                                 │
│     Worker::runAll()                                        │
│                                                              │
│  4. 进入事件循环(永不停止,除非手动关闭)                    │
│     ┌──────────────────────────────────────┐                │
│     │  等待事件... → 处理事件 → 等待事件... │                │
│     └──────────────────────────────────────┘                │
└─────────────────────────────────────────────────────────────┘

                            ↓ ↑
                        客户端连接

┌─────────────────────────────────────────────────────────────┐
│                     【客户端连接过程】                        │
│                                                              │
│  1. 创建 Socket 连接                                         │
│     stream_socket_client('tcp://127.0.0.1:8080')           │
│                                                              │
│  2. 三次握手(自动完成)                                      │
│     建立 TCP 连接                                            │
│                                                              │
│  3. 服务器触发 onConnect 回调                                │
│     - 分配连接 ID                                            │
│     - 发送欢迎消息                                           │
│                                                              │
│  4. 客户端接收欢迎消息                                        │
│     fgets($socket) 读取数据                                 │
│                                                              │
│  5. 客户端发送消息                                           │
│     fwrite($socket, "Hello\n")                              │
│                                                              │
│  6. 服务器触发 onMessage 回调                                │
│     - 接收数据                                               │
│     - 处理业务逻辑                                           │
│     - 发送响应                                               │
│                                                              │
│  7. 客户端接收响应                                           │
│     fgets($socket) 读取服务器回复                            │
│                                                              │
│  8. 断开连接                                                 │
│     - 客户端发送 "quit" 或 fclose($socket)                   │
│     - 服务器触发 onClose 回调                                │
│     - 四次挥手(自动完成)                                    │
└─────────────────────────────────────────────────────────────┘

4. 服务器代码详解

4.1 创建服务器

use Workerman\Worker;

// 创建一个 TCP Worker
// tcp://0.0.0.0:8080 的意思:
//   - tcp://      → 使用 TCP 协议
//   - 0.0.0.0     → 监听所有网络接口(可以从任何IP访问)
//   - :8080       → 监听 8080 端口
$tcp_worker = new Worker('tcp://0.0.0.0:8080');

网络地址说明:

0.0.0.0:8080    → 监听所有IP(本机、局域网、外网都可访问)
127.0.0.1:8080  → 只监听本机(只能 localhost 访问)
192.168.1.100:8080 → 只监听特定IP

4.2 设置进程数

// 设置启动多少个进程
$tcp_worker->count = 1;  // 1个进程(单进程模式,适合学习)
// $tcp_worker->count = 4;  // 4个进程(多进程,适合生产环境)

进程的作用:

单进程(count=1)
┌────────────┐
│  Worker 1  │  处理所有连接
└────────────┘

多进程(count=4)
┌────────────┐
│  Worker 1  │  处理部分连接
├────────────┤
│  Worker 2  │  处理部分连接
├────────────┤
│  Worker 3  │  处理部分连接
├────────────┤
│  Worker 4  │  处理部分连接
└────────────┘
提高并发能力,充分利用多核 CPU

4.3 Worker 启动回调

// 当 Worker 进程启动时触发(只执行一次)
$tcp_worker->onWorkerStart = function($worker) {
    echo "服务器启动成功!\n";
    echo "监听地址: tcp://0.0.0.0:8080\n";
    // 这里可以做初始化工作:
    // - 连接数据库
    // - 加载配置
    // - 初始化缓存
};

4.4 客户端连接回调

// 每当有新客户端连接时触发
$tcp_worker->onConnect = function($connection) {
    // $connection 是连接对象,代表这个客户端

    echo "新客户端连接!\n";
    echo "连接ID: {$connection->id}\n";           // 每个连接的唯一ID
    echo "客户端IP: {$connection->getRemoteIp()}\n";  // 客户端的IP地址
    echo "客户端端口: {$connection->getRemotePort()}\n"; // 客户端的端口

    // 向客户端发送欢迎消息
    $connection->send("欢迎连接!\n");
};

connection 对象说明:

$connection->id              // 连接ID(数字,如:1, 2, 3...)
$connection->getRemoteIp()   // 客户端IP(如:127.0.0.1)
$connection->getRemotePort() // 客户端端口(如:54321)
$connection->send($data)     // 发送数据给客户端
$connection->close()         // 关闭连接

4.5 收到消息回调

// 每当收到客户端消息时触发
$tcp_worker->onMessage = function($connection, $data) {
    // $connection: 发送消息的客户端连接对象
    // $data: 客户端发送的数据(字符串)

    echo "收到消息: {$data}\n";

    // 处理特殊命令
    if (trim($data) === 'quit') {
        $connection->send("再见!\n");
        $connection->close();  // 关闭连接
        return;
    }

    // Echo 回显:把收到的消息原样返回
    $connection->send("服务器回显: " . $data);
};

数据处理流程:

客户端发送: "Hello\n"
     ↓
服务器接收: $data = "Hello\n"
     ↓
服务器处理: trim($data) = "Hello"
     ↓
服务器回复: "服务器回显: Hello\n"
     ↓
客户端接收: "服务器回显: Hello\n"

4.6 连接断开回调

// 每当客户端断开连接时触发
$tcp_worker->onClose = function($connection) {
    echo "客户端断开连接!\n";
    echo "连接ID: {$connection->id}\n";

    // 这里可以做清理工作:
    // - 清除该用户的缓存
    // - 通知其他用户该用户已离线
    // - 记录日志
};

4.7 启动服务器

// 启动所有 Worker(进入事件循环)
Worker::runAll();

// 这个函数会阻塞在这里,不会往下执行
// 除非服务器被停止

5. 客户端代码详解

5.1 创建连接

// 创建 TCP Socket 连接
$socket = stream_socket_client(
    "tcp://127.0.0.1:8080",  // 服务器地址
    $errno,                   // 错误代码(引用传递)
    $errstr,                  // 错误信息(引用传递)
    5                         // 超时时间(秒)
);

if (!$socket) {
    echo "连接失败: {$errstr} ({$errno})\n";
    exit(1);
}

连接地址说明:

tcp://127.0.0.1:8080    → 连接本机的 8080 端口
tcp://192.168.1.100:8080 → 连接局域网内的服务器
tcp://example.com:8080   → 连接远程服务器(需要域名解析)

5.2 设置 Socket 属性

// 设置阻塞/非阻塞模式
stream_set_blocking($socket, true);  // 阻塞模式:等待数据到达
stream_set_blocking($socket, false); // 非阻塞模式:立即返回

// 设置超时时间
stream_set_timeout($socket, 3);  // 3秒超时

阻塞 vs 非阻塞:

阻塞模式(blocking)
┌─────────────────────┐
│ fgets($socket)      │
│  ↓                  │
│ 等待...             │  程序会在这里等待,直到收到数据
│ 等待...             │
│ 收到数据!          │
│  ↓                  │
│ 返回数据            │
└─────────────────────┘

非阻塞模式(non-blocking)
┌─────────────────────┐
│ fgets($socket)      │
│  ↓                  │
│ 立即返回(可能为空)│  立即返回,不等待
└─────────────────────┘
需要循环检查是否有数据

5.3 接收数据

// 按行读取(读到换行符 \n 为止)
$data = fgets($socket, 8192);  // 最多读取 8192 字节

// 读取指定字节数
$data = fread($socket, 1024);  // 读取 1024 字节

// 检查是否成功
if ($data === false) {
    // 读取失败或超时
    $meta = stream_get_meta_data($socket);
    if ($meta['timed_out']) {
        echo "超时!\n";
    } elseif ($meta['eof']) {
        echo "连接已关闭!\n";
    }
}

5.4 发送数据

// 发送数据
$result = fwrite($socket, "Hello World\n");

// 检查是否成功
if ($result === false) {
    echo "发送失败!\n";
} else {
    echo "发送成功,共 {$result} 字节\n";
}

5.5 关闭连接

// 关闭 Socket 连接
fclose($socket);
19 0 0
0个评论

贵哥的编程之路(陈业贵)

1100
积分
0
获赞数
0
粉丝数
2025-07-11 加入
🔝