运行:
先运行php 1.php start
在运行php 2.php start
<?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();
<?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();
TCP(传输控制协议)是一种可靠的通信方式。
TCP(像打电话)
✓ 先接通(握手)
✓ 实时对话(双向通信)
✓ 确认对方收到(可靠)
✓ 挂断电话(关闭连接)
UDP(像发短信)
✗ 直接发送(无连接)
✗ 不确认是否收到
✓ 速度快但可能丢失
客户端 服务器
| |
|----① SYN(你好)----------->|
| |
|<---② SYN-ACK(你好,收到)---|
| |
|----③ ACK(好的,开始吧)---->|
| |
|======= 连接建立 =============|
┌─────────────────────────────────────────────────────────────┐
│ 【服务器启动】 │
│ │
│ 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 回调 │
│ - 四次挥手(自动完成) │
└─────────────────────────────────────────────────────────────┘
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
// 设置启动多少个进程
$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
// 当 Worker 进程启动时触发(只执行一次)
$tcp_worker->onWorkerStart = function($worker) {
echo "服务器启动成功!\n";
echo "监听地址: tcp://0.0.0.0:8080\n";
// 这里可以做初始化工作:
// - 连接数据库
// - 加载配置
// - 初始化缓存
};
// 每当有新客户端连接时触发
$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() // 关闭连接
// 每当收到客户端消息时触发
$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"
// 每当客户端断开连接时触发
$tcp_worker->onClose = function($connection) {
echo "客户端断开连接!\n";
echo "连接ID: {$connection->id}\n";
// 这里可以做清理工作:
// - 清除该用户的缓存
// - 通知其他用户该用户已离线
// - 记录日志
};
// 启动所有 Worker(进入事件循环)
Worker::runAll();
// 这个函数会阻塞在这里,不会往下执行
// 除非服务器被停止
// 创建 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 → 连接远程服务器(需要域名解析)
// 设置阻塞/非阻塞模式
stream_set_blocking($socket, true); // 阻塞模式:等待数据到达
stream_set_blocking($socket, false); // 非阻塞模式:立即返回
// 设置超时时间
stream_set_timeout($socket, 3); // 3秒超时
阻塞 vs 非阻塞:
阻塞模式(blocking)
┌─────────────────────┐
│ fgets($socket) │
│ ↓ │
│ 等待... │ 程序会在这里等待,直到收到数据
│ 等待... │
│ 收到数据! │
│ ↓ │
│ 返回数据 │
└─────────────────────┘
非阻塞模式(non-blocking)
┌─────────────────────┐
│ fgets($socket) │
│ ↓ │
│ 立即返回(可能为空)│ 立即返回,不等待
└─────────────────────┘
需要循环检查是否有数据
// 按行读取(读到换行符 \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";
}
}
// 发送数据
$result = fwrite($socket, "Hello World\n");
// 检查是否成功
if ($result === false) {
echo "发送失败!\n";
} else {
echo "发送成功,共 {$result} 字节\n";
}
// 关闭 Socket 连接
fclose($socket);