我们将从最基础的 TCP Socket 服务器开始,逐步深入,并加入现代的最佳实践,如面向对象和 Swoole 这样的协程扩展。

第一部分:基础 TCP Socket 服务器
这是最经典、最核心的 Socket 编程模型,它遵循“创建 -> 绑定 -> 监听 -> 接受 -> 通信 -> 关闭”的流程。
核心流程与函数
一个简单的 TCP 服务器生命周期如下:
socket_create(): 创建一个 Socket 套接字。socket_bind(): 将 Socket 绑定到指定的 IP 地址和端口。socket_listen(): 开始监听来自客户端的连接请求。socket_accept(): 阻塞等待,接受一个客户端连接,成功后返回一个新的 Socket 资源,用于与该客户端通信。socket_read()/socket_write(): 通过accept返回的新 Socket 资源,读取客户端发来的数据和向客户端写入数据。socket_close(): 关闭 Socket 连接。
完整代码示例
这是一个功能完整的单线程、阻塞式 TCP 服务器,它能处理一个客户端,当客户端断开后,它会继续等待下一个客户端。
文件名:server.php

<?php
// +----------------------------------------------------------------------
// | PHP Socket Server
// +----------------------------------------------------------------------
// 设置错误报告和显示所有错误
error_reporting(E_ALL);
ini_set('display_errors', 1);
// --- 1. 创建 Socket ---
// AF_INET: IPv4 协议
// SOCK_STREAM: TCP 类型
// SOL_TCP: TCP 协议 (等同于 getprotobyname('tcp'))
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($serverSocket === false) {
die("socket_create() failed: " . socket_strerror(socket_last_error()) . "\n");
}
// --- 2. 绑定 Socket ---
$address = '0.0.0.0'; // 0.0.0.0 表示监听所有可用的网络接口
$port = 9999;
$result = socket_bind($serverSocket, $address, $port);
if ($result === false) {
die("socket_bind() failed: " . socket_strerror(socket_last_error($serverSocket)) . "\n");
}
// --- 3. 监听 Socket ---
$result = socket_listen($serverSocket, 5); // 5 是 backlog,表示等待连接的最大队列长度
if ($result === false) {
die("socket_listen() failed: " . socket_strerror(socket_last_error($serverSocket)) . "\n");
}
echo "Server is running at tcp://{$address}:{$port}\n";
echo "Waiting for a client to connect...\n";
// --- 4. 接受客户端连接 ---
// 这是一个阻塞函数,会一直等待直到有客户端连接
$clientSocket = socket_accept($serverSocket);
if ($clientSocket === false) {
die("socket_accept() failed: " . socket_strerror(socket_last_error($serverSocket)) . "\n");
}
echo "Client connected!\n";
// --- 5. 与客户端通信 ---
while (true) {
// 读取客户端数据
// 1024 是读取的最大字节数
$data = socket_read($clientSocket, 1024);
// 如果读取失败或客户端关闭了连接
if ($data === false) {
echo "socket_read() failed: " . socket_strerror(socket_last_error($clientSocket)) . "\n";
break;
}
// 如果读取到空字符串,表示客户端断开了连接
if ($data === '') {
echo "Client disconnected.\n";
break;
}
// 将数据转为字符串,并去除末尾的空白字符(如 \n, \r)
$data = trim($data);
echo "Received from client: {$data}\n";
// 处理数据并返回响应
$response = "Server got your message: " . $data . "\n";
socket_write($clientSocket, $response, strlen($response));
}
// --- 6. 关闭连接 ---
socket_close($clientSocket);
socket_close($serverSocket);
echo "Server shutdown.\n";
?>
如何运行和测试
-
保存代码: 将上面的代码保存为
server.php。 -
打开终端: 进入
server.php所在的目录。 -
启动服务器: 运行命令
php server.php。$ php server.php Server is running at tcp://0.0.0.0:9999 Waiting for a client to connect...
服务器会阻塞在
socket_accept,等待客户端连接。
(图片来源网络,侵删) -
测试客户端: 打开另一个终端窗口,你可以使用
telnet或nc(netcat) 作为客户端。- 使用
telnet:$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. hello server <-- 你输入的内容 Server got your message: hello server <-- 服务器返回的响应 ^] <-- 输入 Ctrl+] 进入 telnet 命令模式 telnet> quit <-- 输入 quit 退出 Connection closed.
- 使用
nc:$ nc 127.0.0.1 9999 hello from nc Server got your message: hello from nc
- 使用
-
观察服务器输出: 当你在客户端输入并发送消息后,服务器的终端会显示接收到的内容。
第二部分:进阶 - 面向对象与多客户端处理
上面的例子只能同时处理一个客户端,当它在与一个客户端通信时,其他客户端必须等待,为了处理多个客户端,我们需要引入多进程或多线程,在 PHP 中,最常见和稳定的方式是使用多进程。
面向对象封装
我们把服务器逻辑封装成一个类,这样更易于管理和扩展。
文件名:TcpServer.php
<?php
class TcpServer
{
private $serverSocket;
private $address;
private $port;
public function __construct(string $address = '0.0.0.0', int $port = 9999)
{
$this->address = $address;
$this->port = $port;
$this->serverSocket = null;
}
public function start()
{
$this->serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->serverSocket) {
throw new RuntimeException("socket_create failed: " . socket_strerror(socket_last_error()));
}
socket_set_option($this->serverSocket, SOL_SOCKET, SO_REUSEADDR, 1); // 地址复用
if (!socket_bind($this->serverSocket, $this->address, $this->port)) {
throw new RuntimeException("socket_bind failed: " . socket_strerror(socket_last_error($this->serverSocket)));
}
if (!socket_listen($this->serverSocket, 5)) {
throw new RuntimeException("socket_listen failed: " . socket_strerror(socket_last_error($this->serverSocket)));
}
echo "Server started at tcp://{$this->address}:{$this->port}\n";
while (true) {
// 阻塞等待客户端连接
$clientSocket = socket_accept($this->serverSocket);
if ($clientSocket === false) {
echo "socket_accept failed: " . socket_strerror(socket_last_error($this->serverSocket)) . "\n";
continue;
}
echo "New client connected.\n";
// 为每个客户端连接创建一个子进程来处理
$pid = pcntl_fork();
if ($pid == -1) {
// 创建进程失败
die("Could not fork process\n");
} else if ($pid) {
// 父进程
// 关闭父进程中与客户端通信的 socket,只保留监听 socket
socket_close($clientSocket);
// 父进程继续循环,接受下一个连接
continue;
} else {
// 子进程
// 关闭子进程中用于监听的 server socket
socket_close($this->serverSocket);
$this->handleClient($clientSocket);
exit(0); // 子进程处理完毕后退出
}
}
}
private function handleClient($clientSocket)
{
while (true) {
$data = socket_read($clientSocket, 1024);
if ($data === false) {
echo "socket_read failed: " . socket_strerror(socket_last_error($clientSocket)) . "\n";
break;
}
if ($data === '') {
echo "Client disconnected.\n";
break;
}
$data = trim($data);
echo "Received from client: {$data}\n";
$response = "Server (PID: " . getmypid() . ") got your message: " . $data . "\n";
socket_write($clientSocket, $response, strlen($response));
}
socket_close($clientSocket);
}
public function __destruct()
{
if ($this->serverSocket) {
socket_close($this->serverSocket);
}
}
}
// 使用示例
try {
$server = new TcpServer('0.0.0.0', 9999);
$server->start();
} catch (RuntimeException $e) {
echo $e->getMessage() . "\n";
}
多进程模型说明
pcntl_fork(): 这个函数会创建一个子进程,它会复制父进程的整个内存空间。- 父进程:
pcntl_fork()返回子进程的 PID (Process ID),父进程的职责是继续循环,调用socket_accept()接受新的连接,然后将每个新连接交给一个子进程去处理,父进程自己不参与具体的通信。 - 子进程:
pcntl_fork()返回0,子进程的唯一任务就是处理分配给它的那个客户端连接,处理完成后,调用exit(0)终止自己,避免变成僵尸进程。 socket_close()的作用: 在父进程中,关闭$clientSocket是非常关键的一步,因为子进程复制了父进程的文件描述符,如果不关闭,会导致文件描述符泄漏,同样,在子进程中关闭$serverSocket也是必要的,因为子进程不需要监听新连接。
第三部分:现代选择 - Swoole 扩展
传统的 PHP Socket 编程是阻塞式的,并发能力非常弱,要实现高并发,需要依赖多进程/多线程,这会增加编程的复杂性。
Swoole 是一个高性能的 PHP 协程框架,它提供了强大的异步网络编程能力,可以轻松实现成千上万的并发连接。
安装 Swoole
你需要安装 Swoole 扩展。
# 使用 pecl 安装 (推荐) pecl install swoole # 或者从源码编译安装 git clone https://github.com/swoole/swoole-src.git cd swoole-src phpize ./configure make && make install # 然后在 php.ini 中添加 extension=swoole
Swoole TCP 服务器示例
Swoole 的 API 非常简洁,并且是基于事件驱动的,性能极高。
文件名:swoole_server.php
<?php
// +----------------------------------------------------------------------
// | Swoole TCP Server
// +----------------------------------------------------------------------
use Swoole\Server;
// 创建一个 TCP 服务器对象
// SWOOLE_PROCESS 是默认模式,也是多进程模式
$server = new Server('0.0.0.0', 9999);
// 设置运行参数
$server->set([
'worker_num' => 4, // 设置启动的 Worker 进程数,CPU 核心数的 1-4 倍
'daemonize' => false, // 是否以守护进程方式运行
'log_file' => '/tmp/swoole.log', // 指定日志文件
]);
// 监听连接进入事件
$server->on('Connect', function ($server, $fd) {
echo "Client #{$fd} connected.\n";
});
// 监听数据接收事件
$server->on('Receive', function ($server, $fd, $reactorId, $data) {
echo "Received from #{$fd}: {$data}\n";
// 向客户端发送数据
$server->send($fd, "Server (Swoole) got your message: {$data}\n");
});
// 监听连接关闭事件
$server->on('Close', function ($server, $fd) {
echo "Client #{$fd} closed.\n";
});
// 启动服务器
echo "Swoole TCP Server started at tcp://0.0.0.0:9999\n";
$server->start();
Swoole 的优势
- 高性能: 基于事件驱动和协程,单进程即可处理海量并发连接。
- 异步非阻塞: I/O 操作(如网络请求、文件读写)不会阻塞整个进程,其他协程可以继续执行。
- 丰富的协议支持: 内置了对 HTTP, WebSocket, HTTP2, Redis, MySQL 等协议的支持。
- 现代化的 API: 回调函数的写法清晰明了,易于理解和管理。
总结与对比
| 特性 | 原生 PHP Socket | Swoole |
|---|---|---|
| 模型 | 阻塞式、多进程 | 异步非阻塞、事件驱动、协程 |
| 并发能力 | 低 (每个连接需要一个进程) | 极高 (单进程可处理数万连接) |
| 易用性 | 较低,需要处理进程管理、信号等 | 高,API 封装良好,开箱即用 |
| 适用场景 | 学习 Socket 原理、简单任务、无高并发需求 | 高性能实时应用、WebSocket 服务、RPC 服务、微服务 |
| 依赖 | 仅 PHP 核心功能 | 需要安装 Swoole 扩展 |
如何选择?
- 如果你是初学者,想了解 Socket 的底层原理,从原生 PHP Socket 开始是最好的选择。
- 如果你要构建一个生产环境、需要高并发、高性能的服务,毫无疑问应该选择 Swoole,它能让你用 PHP 写出媲美 Go 和 Node.js 的网络服务。
