socket_create

(PHP 4 >= 4.1.0, PHP 5, PHP 7, PHP 8)

socket_create创建套接字(通信端点)

描述

socket_create(int $domain, int $type, int $protocol): Socket|false

创建并返回一个 Socket 实例,也称为通信端点。典型的网络连接由 2 个套接字组成,一个充当客户端的角色,另一个充当服务器的角色。

参数

domain

domain 参数指定套接字要使用的协议族。

可用地址/协议族
描述
AF_INET 基于 IPv4 的互联网协议。TCP 和 UDP 是此协议族中的常见协议。
AF_INET6 基于 IPv6 的互联网协议。TCP 和 UDP 是此协议族中的常见协议。
AF_UNIX 本地通信协议族。高效率和低开销使其成为 IPC(进程间通信)的绝佳形式。
type

type 参数选择套接字要使用的通信类型。

可用套接字类型
类型 描述
SOCK_STREAM 提供排序的、可靠的、全双工的、基于连接的字节流。可能支持带外数据传输机制。TCP 协议基于这种套接字类型。
SOCK_DGRAM 支持数据报(无连接的、不可靠的、固定最大长度的消息)。UDP 协议基于这种套接字类型。
SOCK_SEQPACKET 为固定最大长度的数据报提供排序的、可靠的、双向的基于连接的数据传输路径;消费者需要在每次读取调用时读取整个数据包。
SOCK_RAW 提供原始网络协议访问。这种特殊类型的套接字可用于手动构建任何类型的协议。这种套接字类型的常见用途是执行 ICMP 请求(如 ping)。
SOCK_RDM 提供一个可靠的数据报层,不保证排序。这很可能未在您的操作系统上实现。
protocol

protocol 参数设置在指定的 domain 内的特定协议,在返回的套接字上进行通信时使用。可以使用 getprotobyname() 按名称检索正确的值。如果所需的协议是 TCP 或 UDP,也可以使用相应的常量 SOL_TCPSOL_UDP

常见协议
名称 描述
icmp 互联网控制消息协议主要由网关和主机用于报告数据报通信中的错误。“ping”命令(存在于大多数现代操作系统中)是 ICMP 的一个示例应用程序。
udp 用户数据报协议是一种无连接的、不可靠的协议,具有固定记录长度。由于这些方面,UDP 需要最少的协议开销。
tcp 传输控制协议是一种可靠的、基于连接的、面向流的、全双工协议。TCP 保证所有数据包将按发送顺序接收。如果在通信过程中有任何数据包丢失,TCP 将自动重新传输数据包,直到目标主机确认该数据包。出于可靠性和性能原因,TCP 本身决定底层数据报通信层的适当八位字节边界。因此,TCP 应用程序必须允许部分记录传输的可能性。

返回值

socket_create() 在成功时返回一个 Socket 实例,在失败时返回 false。可以通过调用 socket_last_error() 检索实际的错误代码。此错误代码可以传递给 socket_strerror() 以获取错误的文本说明。

错误/异常

如果给出了无效的 domaintypesocket_create() 会分别默认为 AF_INETSOCK_STREAM,并另外发出 E_WARNING 消息。

变更日志

版本 描述
8.0.0 成功时,此函数现在返回一个 Socket 实例;以前,返回的是一个 resource

参见

添加说明

用户贡献说明 12 条说明

kyle gibson
18 年前
花了我大约 20 分钟才弄清楚为 AF_UNIX 套接字提供正确参数的方法。任何其他操作都会导致 PHP 警告“type”不受支持。我希望这可以为其他人节省时间。

<?php
$socket
= socket_create(AF_UNIX, SOCK_STREAM, 0);
// code
?>
rhollencamp at gmail dot com
14 年前
请注意,如果您使用 AF_UNIX 创建套接字,将在文件系统中创建一个文件。此文件不会在您调用 socket_close 时删除 - 您应该在关闭套接字后取消链接该文件。
ab1965 at yandex dot ru
12 年前
我花了一些时间才明白一个 PHP 进程如何通过 unix udp 套接字与另一个 PHP 进程通信。下面给出了“服务器”和“客户端”代码的示例。假设服务器在客户端启动之前运行。

“服务器”代码
<?php
如果 (!extension_loaded('sockets')) {
die(
'sockets 扩展未加载.');
}
// 创建 Unix UDP 套接字
$socket = socket_create(AF_UNIX, SOCK_DGRAM, 0);
如果 (!
$socket)
die(
'无法创建 AF_UNIX 套接字');

// 在 recv_from 和 send_to 中将使用相同的套接字
$server_side_sock = dirname(__FILE__)."/server.sock";
如果 (!
socket_bind($socket, $server_side_sock))
die(
"无法绑定到 $server_side_sock");

当 (
1) // 服务器永不退出
{
// 接收查询
如果 (!socket_set_block($socket))
die(
'无法为套接字设置阻塞模式');
$buf = '';
$from = '';
echo
"准备接收...\n";
// 将阻塞以等待客户端查询
$bytes_received = socket_recvfrom($socket, $buf, 65536, 0, $from);
如果 (
$bytes_received == -1)
die(
'从套接字接收时出错');
echo
"收到 $buf 来自 $from\n";

$buf .= "->响应"; // 在此处处理客户端查询

// 发送响应
如果 (!socket_set_nonblock($socket))
die(
'无法为套接字设置非阻塞模式');
// 客户端套接字文件名从客户端请求中已知: $from
$len = strlen($buf);
$bytes_sent = socket_sendto($socket, $buf, $len, 0, $from);
如果 (
$bytes_sent == -1)
die(
'发送到套接字时出错');
否则如果 (
$bytes_sent != $len)
die(
$bytes_sent . ' 字节已发送,而不是预期的 ' . $len . ' 字节');
echo
"请求已处理\n";
}
?>

'客户端' 代码
<?php
如果 (!extension_loaded('sockets')) {
die(
'sockets 扩展未加载.');
}
// 创建 Unix UDP 套接字
$socket = socket_create(AF_UNIX, SOCK_DGRAM, 0);
如果 (!
$socket)
die(
'无法创建 AF_UNIX 套接字');

// 同一个套接字稍后将在 recv_from 中使用
// 如果你只希望发送而从不接收,则不需要绑定
$client_side_sock = dirname(__FILE__)."/client.sock";
如果 (!
socket_bind($socket, $client_side_sock))
die(
"无法绑定到 $client_side_sock");

// 使用套接字发送数据
如果 (!socket_set_nonblock($socket))
die(
'无法为套接字设置非阻塞模式');
// 服务器套接字文件名是先验已知的
$server_side_sock = dirname(__FILE__)."/server.sock";
$msg = "消息";
$len = strlen($msg);
// 在此,'服务器' 进程必须运行并绑定以从 serv.sock 接收
$bytes_sent = socket_sendto($socket, $msg, $len, 0, $server_side_sock);
如果 (
$bytes_sent == -1)
die(
'发送到套接字时出错');
否则如果 (
$bytes_sent != $len)
die(
$bytes_sent . ' 字节已发送,而不是预期的 ' . $len . ' 字节');

// 使用套接字接收数据
如果 (!socket_set_block($socket))
die(
'无法为套接字设置阻塞模式');
$buf = '';
$from = '';
// 将阻塞以等待服务器响应
$bytes_received = socket_recvfrom($socket, $buf, 65536, 0, $from);
如果 (
$bytes_received == -1)
die(
'从套接字接收时出错');
echo
"收到 $buf 来自 $from\n";

// 关闭套接字并删除自己的 .sock 文件
socket_close($socket);
unlink($client_side_sock);
echo
"客户端退出\n";
?>
geoff at spacevs dot com
13 年前
这是一个 PHP 的 ping 函数,不使用 exec/system/passthrough/etc... 在尝试连接到主机之前测试主机是否在线非常有用。超时以秒为单位。

<?PHP
函数 ping($host, $timeout = 1) {
/* 带有预先计算的校验和的 ICMP ping 数据包 */
$package = "\x08\x00\x7d\x4b\x00\x00\x00\x00PingHost";
$socket = socket_create(AF_INET, SOCK_RAW, 1);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, 数组('sec' => $timeout, 'usec' => 0));
socket_connect($socket, $host, null);

$ts = microtime(true);
socket_send($socket, $package, strLen($package), 0);
如果 (
socket_read($socket, 255))
$result = microtime(true) - $ts;
否则
$result = false;
socket_close($socket);

返回
$result;
}
?>
Jean Charles MAMMANA
16 年前
我已经使用 socket_create() 和 SOCK_RAW 编写了 ping() 函数。
(在 Unix 系统上,您需要具有 root 权限才能执行此函数)

<?php

/// ping.inc.php 开始 ///

$g_icmp_error = "无错误";

// 超时时间,单位毫秒
function ping($host, $timeout)
{
$port = 0;
$datasize = 64;
global
$g_icmp_error;
$g_icmp_error = "无错误";
$ident = array(ord('J'), ord('C'));
$seq = array(rand(0, 255), rand(0, 255));

$packet = '';
$packet .= chr(8); // 类型 = 8 : 请求
$packet .= chr(0); // 代码 = 0

$packet .= chr(0); // 校验和初始化
$packet .= chr(0); // 校验和初始化

$packet .= chr($ident[0]); // 标识符
$packet .= chr($ident[1]); // 标识符

$packet .= chr($seq[0]); // 序列号
$packet .= chr($seq[1]); // 序列号

for ($i = 0; $i < $datasize; $i++)
$packet .= chr(0);

$chk = icmpChecksum($packet);

$packet[2] = $chk[0]; // 校验和初始化
$packet[3] = $chk[1]; // 校验和初始化

$sock = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
$time_start = microtime();
socket_sendto($sock, $packet, strlen($packet), 0, $host, $port);


$read = array($sock);
$write = NULL;
$except = NULL;

$select = socket_select($read, $write, $except, 0, $timeout * 1000);
if (
$select === NULL)
{
$g_icmp_error = "选择错误";
socket_close($sock);
return -
1;
}
elseif (
$select === 0)
{
$g_icmp_error = "超时";
socket_close($sock);
return -
1;
}

$recv = '';
$time_stop = microtime();
socket_recvfrom($sock, $recv, 65535, 0, $host, $port);
$recv = unpack('C*', $recv);

if (
$recv[10] !== 1) // ICMP 协议 = 1
{
$g_icmp_error = "不是 ICMP 数据包";
socket_close($sock);
return -
1;
}

if (
$recv[21] !== 0) // ICMP 响应 = 0
{
$g_icmp_error = "不是 ICMP 响应";
socket_close($sock);
return -
1;
}

if (
$ident[0] !== $recv[25] || $ident[1] !== $recv[26])
{
$g_icmp_error = "标识号错误";
socket_close($sock);
return -
1;
}

if (
$seq[0] !== $recv[27] || $seq[1] !== $recv[28])
{
$g_icmp_error = "序列号错误";
socket_close($sock);
return -
1;
}

$ms = ($time_stop - $time_start) * 1000;

if (
$ms < 0)
{
$g_icmp_error = "响应时间过长";
$ms = -1;
}

socket_close($sock);

return
$ms;
}

function
icmpChecksum($data)
{
$bit = unpack('n*', $data);
$sum = array_sum($bit);

if (
strlen($data) % 2) {
$temp = unpack('C*', $data[strlen($data) - 1]);
$sum += $temp[1];
}

$sum = ($sum >> 16) + ($sum & 0xffff);
$sum += ($sum >> 16);

return
pack('n*', ~$sum);
}

function
getLastIcmpError()
{
global
$g_icmp_error;
return
$g_icmp_error;
}
/// ping.inc.php 结束 ///
?>
alexander dot krause at ed-solutions dot de
16 年前
在 UNIX 系统上,php 需要 /etc/protocols 文件来定义常量,例如 SOL_UDP 和 SOL_TCP。

我的嵌入式平台上缺少此文件。
jens at surefoot dot com
18 年前
请注意,原始套接字(如 ping 示例中使用的套接字)在 *nix 系统上仅限于 root 用户。由于 Web 服务器很少以 root 身份运行,因此它们在网页上无法正常工作。

在基于 Windows 的服务器上,它应该可以正常工作。
evan at coeus hyphen group dot com
22 年前
好的,我和 Richard 稍微讨论了一下(通过电子邮件)。我们一致认为 getprotobyname() 和使用常量在功能和速度上应该是一样的,使用哪一个纯粹是编码风格问题。就个人而言,我们都认为常量看起来更漂亮:)。

这八种不同的协议是 PHP 中实现的协议,而不是所有存在的协议(RFC 1340 有 98 种)。

我们唯一意见不一致的地方是使用 0 - Richard 说“根据官方 unix/bsd 套接字,0 没什么问题”。我认为,根据 RFC 1320,0 是一个保留数字,当使用时通常指的是 IP,而不是它的子协议(TCP、UDP 等)。
david at eder dot us
20 年前
似乎没有 UDP 客户端的示例。这是一个 tftp 客户端。我希望这能使某些人的生活更轻松。

<?php
function tftp_fetch($host, $filename)
{
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

// 创建请求包
$packet = chr(0) . chr(1) . $filename . chr(0) . 'octet' . chr(0);
// UDP 是无连接的,所以我们直接发送。
socket_sendto($socket, $packet, strlen($packet), 0x100, $host, 69);

$buffer = '';
$port = '';
$ret = '';
do
{
// $buffer 和 $port 都返回确认信息的
// 516 = 4 字节的头部 + 512 字节的数据
socket_recvfrom($socket, $buffer, 516, 0, $host, $port);

// 将数据包中的块号添加到确认包中
$packet = chr(0) . chr(4) . substr($buffer, 2, 2);
// 发送确认
socket_sendto($socket, $packet, strlen($packet), 0, $host, $port);

// 将数据追加到返回值中
// 对于大文件,此函数应该接收文件句柄作为参数
$ret .= substr($buffer, 4);
}
while(
strlen($buffer) == 516); // 第一个非完整包是最后一个。
return $ret;
}
?>
Anonymous
18 年前
这是一个使用套接字而不是 exec() 的 ping 函数。注意:我无法使 socket_create() 在非 root 用户下从 CLI 运行。我已经计算了包的校验和以简化代码(消息是“ping”,但实际上并不重要)。

<?php

function ping($host) {
$package = "\x08\x00\x19\x2f\x00\x00\x00\x00\x70\x69\x6e\x67";

/* 创建套接字,最后一个 '1' 表示 ICMP */
$socket = socket_create(AF_INET, SOCK_RAW, 1);

/* 设置套接字接收超时为 1 秒 */
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 1, "usec" => 0));

/* 连接到套接字 */
socket_connect($socket, $host, null);

/* 记录开始时间 */
list($start_usec, $start_sec) = explode(" ", microtime());
$start_time = ((float) $start_usec + (float) $start_sec);

socket_send($socket, $package, strlen($package), 0);

if(@
socket_read($socket, 255)) {
list(
$end_usec, $end_sec) = explode(" ", microtime());
$end_time = ((float) $end_usec + (float) $end_sec);

$total_time = $end_time - $start_time;

return
$total_time;
} else {
return
false;
}

socket_close($socket);
}

?>
david at eder dot us
19 年前
有时当您运行 CLI 时,您需要知道您自己的 IP 地址。

<?php

$addr
= my_ip();
echo
"我的 IP 地址是 $addr\n";

function
my_ip($dest='64.0.0.0', $port=80)
{
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_connect($socket, $dest, $port);
socket_getsockname($socket, $addr, $port);
socket_close($socket);
return
$addr;
}
?>
Sakrizz
7 年前
这是一个使用 php 的 icmpv6 ping 的解决方案,如果有人在使用 php 的 icmpv6 时遇到问题,请将其放在这里。

<?php
$host
= "2a03:2880:f11b:83:face:b00c:0:25de";
$timeout = 100000;
$count = 3;
echo
"延迟: ". round(1000 * pingv6($host,$timeout,$count),5) ." 毫秒 \n";

function
pingv6($target,$timeout,$count) {
echo
"目标是ipv6地址, ". getprotobyname('ipv6-icmp'). " \n";
/* 创建套接字,最后一个 '1' 表示 ICMP */
$socket = socket_create(AF_INET6, SOCK_RAW, getprotobyname('ipv6-icmp'));
/* 设置套接字接收超时时间为 1 秒 */
$sec=intval($timeout/1000);
$usec=$timeout%1000*1000;
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$sec, "usec"=>$usec));
/* 套接字包参数 */
$type = "\x80";
$seqNumber = chr(floor($i/256)%256) . chr($i%256);
$checksum= "\x00\x00";
$code = "\x00";
$identifier = chr(rand(0,255)) . chr(rand(0,255));
$msg = "!\"#$%&'()*+,-./1234567";
$package = $type.$code.$checksum.$identifier.$seqNumber.$msg;
$checksum = icmpChecksum($package);
$package = $type.$code.$checksum.$identifier.$seqNumber.$msg;
/* 套接字连接 */
if(@socket_connect($socket, $target, null)){
for(
$i = 0; $i < $count; $i++){
list(
$start_usec, $start_sec) = explode(" ", microtime());
$start_time = ((float) $start_usec + (float) $start_sec);
$startTime = microtime(true);
socket_send($socket, $package, strLen($package), 0);
while (
$startTime + $timeout*1000 > microtime(true)){
if(
socket_read($socket, 255) !== false) {
list(
$end_usec, $end_sec) = explode(" ", microtime());
$end_time = ((float) $end_usec + (float) $end_sec);
$total_time = $end_time - $start_time;
echo
"往返时间 (".$i."): ". $total_time ."\n";
return
$total_time;
break;
}else{
return
"null";
echo
"超时 (".$i."), 未收到回显回复\n";
break;
}
}
usleep($interval*1000);
}
socket_close($socket);
}
}

function
icmpChecksum($data){
if (
strlen($data)%2) $data .= "\x00";
$bit = unpack('n*', $data);
$sum = array_sum($bit);
while (
$sum >> 16)
$sum = ($sum >> 16) + ($sum & 0xffff);
return
pack('n*', ~$sum);
}

?>
To Top