PHP Conference Japan 2024

Socket 函數

目錄

新增註解

使用者提供的註解 19 個註解

15
paul dot hodel at gmail dot com
13 年前
經過許多不眠之夜,我用 PHP 寫出了最簡單的多客戶端伺服器,而且真的有效。Ctrl+C 和 Ctrl+V...用作命令列來測試它。好好享受吧。

<?php

ini_set
('error_reporting', E_ALL ^ E_NOTICE);
ini_set('display_errors', 1);

// 設定執行時間限制為無限期執行
set_time_limit (0);

// 設定要監聽的 IP 和 Port
$address = '10.203.9.67';
$port = 6901;

// 建立 TCP Stream socket
$sock = socket_create(AF_INET, SOCK_STREAM, 0);

// 將 socket 綁定到 IP/Port
socket_bind($sock, $address, $port) or die('無法綁定到位址');

// 開始監聽連線
socket_listen($sock);

// 設定為非阻擋 socket 類型
socket_set_nonblock($sock);

// 持續迴圈
while (true)
{
unset(
$read);

$j = 0;

if (
count($client))
{
foreach (
$client AS $k => $v)
{
$read[$j] = $v;

$j++;
}
}

$client = $read;

if (
$newsock = @socket_accept($sock))
{
if (
is_resource($newsock))
{
socket_write($newsock, "$j>", 2).chr(0);

echo
"新客戶端連線 $j";

$client[$j] = $newsock;

$j++;
}
}

if (
count($client))
{
foreach (
$client AS $k => $v)
{
if (@
socket_recv($v, $string, 1024, MSG_DONTWAIT) === 0)
{
unset(
$client[$k]);

socket_close($v);
}
else
{
if (
$string)
{
echo
"$k: $string\n";
}
}
}
}

//echo ".";

sleep(1);
}

// 關閉主 socket
socket_close($sock);
?>
1
philip at birk-jensen dot dk
19 年前
我一直使用 Khaless [at] bigpond [dot] com 寫的 ICMP 檢查碼計算函數。但是當資料長度為奇數時,它會失敗,所以我自己寫了一個,如果資料長度為奇數,它會加入一個 0。
<?php
function icmpChecksum($data)
{
// 如果是「奇數長度」,則在資料末尾加入一個 0
if (strlen($data)%2)
$data .= "\x00";

// 讓 PHP 完成所有繁瑣的工作
$bit = unpack('n*', $data);
$sum = array_sum($bit);

// 從 Khaless [at] bigpond [dot] com 偷來的
// 原始 ping 程式碼:
// sum = (sum >> 16) + (sum & 0xffff); /* 將高 16 位元加到低 16 位元 */
// sum += (sum >> 16); /* 加入進位 */
// 這也運作良好,但對我來說,Khaless 的方法似乎適用於大型資料。
while ($sum>>16)
$sum = ($sum >> 16) + ($sum & 0xffff);

return
pack('n*', ~$sum);
}
?>
0
White-Gandalf
17 年前
目前 (2007-09),我沒有在 PECL 中找到這個擴充功能,而是在常用的 php 擴充功能目錄中找到。它需要包含在 php-ini 中

extension = php_sockets.dll

(或 ".so" - 根據您的系統而定)。
-1
davidccook+php at gmail dot com
16 年前
我打算透過 socket 發送整數值,但令我驚訝的是,PHP 只支援發送字串。
我得出結論,唯一的方法是建立一個字串,該字串將評估為與我想發送的整數相同的位元組值。所以(經過多次嘗試)我建立了一對函數:一個用於建立此「字串」,另一個用於將接收到的值轉換回整數。

<?php
//將整數轉換為「位元組陣列」(字串),預設為 4 個「位元組」(字元)
function int2string($int, $numbytes=4)
{
$str = "";
for (
$i=0; $i < $numbytes; $i++) {
$str .= chr($int % 256);
$int = $int / 256;
}
return
$str;
}

//將「位元組陣列」(字串)轉換為整數
function string2int($str)
{
$numbytes = strlen($str);
$int = 0;
for (
$i=0; $i < $numbytes; $i++) {
$int += ord($str[$i]) * pow(2, $i * 8);
}
return
$int;
}

//範例
echo int2string(16705, 2); // 16 位元整數轉換為兩個位元組:65, 65;反過來就是 'AA'
echo string2int('AA'); //反向轉換
?>
-1
aeolianmeson at NOSPAM dot blitzeclipse dot com
18 年前
有一本關於這個函式庫很棒的書,叫做《TCP/IP Sockets in C》(ISBN 1558608265),內容涵蓋了所有的來龍去脈、怪癖和其他所有的事情。當然,它是用 C 語言寫的,但是幾乎沒有任何嚴重的程式碼差異,它很容易就可以用 PHP 來撰寫。

Dustin
-1
noSanity
20 年前
我長期以來一直在尋找一個不使用 EXEC() 或 SYSTEM() 的 ping 腳本。到目前為止,我一無所獲,所以我決定自己寫一個,這至少可以說是一項任務。

首先,我要感謝 Khaless 的校驗和函式,將它從 C 語言轉換過來本身就是一項艱鉅的任務。

以下是我寫的類別
<?php

class Net_Ping
{
var
$icmp_socket;
var
$request;
var
$request_len;
var
$reply;
var
$errstr;
var
$time;
var
$timer_start_time;
function
Net_Ping()
{
$this->icmp_socket = socket_create(AF_INET, SOCK_RAW, 1);
socket_set_block($this->icmp_socket);
}

function
ip_checksum($data)
{
for(
$i=0;$i<strlen($data);$i += 2)
{
if(
$data[$i+1]) $bits = unpack('n*',$data[$i].$data[$i+1]);
else
$bits = unpack('C*',$data[$i]);
$sum += $bits[1];
}

while (
$sum>>16) $sum = ($sum & 0xffff) + ($sum >> 16);
$checksum = pack('n1',~$sum);
return
$checksum;
}

function
start_time()
{
$this->timer_start_time = microtime();
}

function
get_time($acc=2)
{
// format start time
$start_time = explode (" ", $this->timer_start_time);
$start_time = $start_time[1] + $start_time[0];
// get and format end time
$end_time = explode (" ", microtime());
$end_time = $end_time[1] + $end_time[0];
return
number_format ($end_time - $start_time, $acc);
}

function
Build_Packet()
{
$data = "abcdefghijklmnopqrstuvwabcdefghi"; // the actual test data
$type = "\x08"; // 8 echo message; 0 echo reply message
$code = "\x00"; // always 0 for this program
$chksm = "\x00\x00"; // generate checksum for icmp request
$id = "\x00\x00"; // we will have to work with this later
$sqn = "\x00\x00"; // we will have to work with this later

// now we need to change the checksum to the real checksum
$chksm = $this->ip_checksum($type.$code.$chksm.$id.$sqn.$data);

// now lets build the actual icmp packet
$this->request = $type.$code.$chksm.$id.$sqn.$data;
$this->request_len = strlen($this->request);
}

function
Ping($dst_addr,$timeout=5,$percision=3)
{
// lets catch dumb people
if ((int)$timeout <= 0) $timeout=5;
if ((int)
$percision <= 0) $percision=3;

// set the timeout
socket_set_option($this->icmp_socket,
SOL_SOCKET, // socket level
SO_RCVTIMEO, // timeout option
array(
"sec"=>$timeout, // Timeout in seconds
"usec"=>0 // I assume timeout in microseconds
)
);

if (
$dst_addr)
{
if (@
socket_connect($this->icmp_socket, $dst_addr, NULL))
{

} else {
$this->errstr = "無法連線至 $dst_addr";
return
FALSE;
}
$this->Build_Packet();
$this->start_time();
socket_write($this->icmp_socket, $this->request, $this->request_len);
if (@
socket_recv($this->icmp_socket, &$this->reply, 256, 0))
{
$this->time = $this->get_time($percision);
return
$this->time;
} else {
$this->errstr = "逾時";
return
FALSE;
}
} else {
$this->errstr = "未指定目標位址";
return
FALSE;
}
}
}

$ping = new Net_Ping;
$ping->ping("www.google.ca");

if (
$ping->time)
echo
"時間: ".$ping->time;
else
echo
$ping->errstr;

?>

希望這能幫您省去一些麻煩。

noSanity
-1
Khaless [at] bigpond [dot] com
20 年前
我花了一段時間嘗試使用 SOCK_RAW 發送 ICMP 請求封包,這樣我才能進行 ping。然而,這導致我需要一個用 PHP 函數寫成的網際網路校驗和,由於 PHP 處理變數類型的方式,這有點困難。無論如何,為了讓其他人省去麻煩,這是我想出的,這會回傳 $data 的校驗和

<?PHP
// 計算 $data 的 Internet 檢查和
// 會回傳 $data 的 16 位元 Internet 檢查和
function inetChecksum($data)
{
// 32 位元累加器,一次處理 16 位元,最後加上奇數位元
for($i=0;$i<strlen($data);$i += 2)
{
if(
$data[$i+1]) $bits = unpack('n*',$data[$i].$data[$i+1]);
else
$bits = unpack('C*',$data[$i]);
$sum += $bits[1];
}

// 將 32 位元總和摺疊成 16 位元
while ($sum>>16) $sum = ($sum & 0xffff) + ($sum >> 16);
$checksum = pack('n1',~$sum);
return
$checksum;
}
?>

有了這個,我就可以建立一個正確的 PING 請求。
-2
roberto at spadim dot com dot br
17 年前
網路喚醒 (Wake on Lan),在沒有設定的情況下正常運作,以及一些功能

<?php
function wake_on_lan($mac,$addr=false,$port=7) {
// 用法
// $addr:
// 您將發送並廣播到此位址。
// 通常您需要使用 255.255.255.255 位址,所以我將其設為預設值。 因此您不需要
// 對此進行任何處理。
// 由於 255.255.255.255 有權限被拒絕的問題,您可以使用 addr=false 從 ifconfig 命令取得所有廣播位址
// addr 可以是具有廣播 IP 值的陣列
// $mac:
// 您將喚醒此啟用 WOL 的電腦,您需要在此處新增 MAC 位址。
// Mac 也可以是陣列
//
// 回傳
// TRUE: 當 socket 成功建立並且訊息已傳送時。
// FALSE: 發生錯誤
//
// 範例 1
// 當訊息已傳送時,您會看到訊息「完成...」。
// if ( wake_on_lan('00:00:00:00:00:00'))
// echo '完成...';
// else
// echo '傳送時發生錯誤';
//
if ($addr===false){
exec("ifconfig | grep Bcast | cut -d \":\" -f 3 | cut -d \" \" -f 1",$addr);
$addr=array_flip(array_flip($addr));
}
if(
is_array($addr)){
$last_ret=false;
for (
$i=0;$i<count($ret);$i++)
if (
$ret[$i]!==false)
$last_ret=wake_on_lan($mac,$ret[$i],$port);
return(
$last_ret);
}
if (
is_array($mac)){
$ret=array();
foreach(
$mac as $k=>v)
$ret[$k]=wake_on_lan($v,$addr,$port);
return(
$ret);
}
//檢查是否為真實 MAC 位址,並將其分割成陣列
$mac=strtoupper($mac);
if (!
preg_match("/([A-F0-9]{1,2}[-:]){5}[A-F0-9]{1,2}/",$mac,$maccheck))
return
false;
$addr_byte = preg_split("/[-:]/",$maccheck[0]);

//建立硬體位址
$hw_addr = '';
for (
$a=0; $a < 6; $a++)//將 mac 位址從十六進位變更為十進位
$hw_addr .= chr(hexdec($addr_byte[$a]));

//建立封包資料
$msg = str_repeat(chr(255),6);
for (
$a = 1; $a <= 16; $a++)
$msg .= $hw_addr;
//傳送資料
if (function_exists('socket_create')){
//socket_create 存在
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); //可以建立 socket
if ($sock){
$sock_data = socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1); //設定
if ($sock_data){
$sock_data = socket_sendto($sock, $msg, strlen($msg), 0, $addr,$port); //傳送資料
if ($sock_data){
socket_close($sock); //關閉 socket
unset($sock);
return(
true);
}
}
}
@
socket_close($sock);
unset(
$sock);
}
$sock=fsockopen("udp://" . $addr, $port);
if(
$sock){
$ret=fwrite($sock,$msg);
fclose($sock);
}
if(
$ret)
return(
true);
return(
false);
}
?>
-2
bmatheny at mobocracy dot net
19 年前
多播伺服器可能會寫得很糟糕,如下所示

$bc_string = "Hello World!";
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
$opt_ret = socket_set_option($sock, 1, 6, TRUE);
$send_ret = socket_sendto($sock, $bc_string, strlen($bc_string), 0, '230.0.0.1', 4446);

需要檢查回傳類型,但這確實允許您從 php 程式碼進行多播。
-2
saryon at unfix dot org
22 年前
我在 zend php 上找到這個非常有用的連結
郵件列表

http://www.zend.com/lists/php-dev/200205/msg00286.html

它是關於能夠使用多個連線
在 php socket 伺服器中,而無需
使用那些似乎是每個人都
非常喜歡的執行緒。
運作良好 :)
(附註:我沒有製作它,所以...不要對我說謝謝;)
感謝他)
-4
david dot schueler at tel-bilig dot de
16 年前
注意! 如果您嘗試使用此程式碼傳送廣播訊息,您_可能_會在 socket_connect 收到「權限被拒絕」錯誤,即使您是以 root 身分在 linux 電腦上執行此程式碼也一樣。
<?php
$sock
= socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_connect($sock,"255.255.255.255", 10000);
socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1);
$buf = "Hello World!";
socket_write($sock,$buf,strlen($buf));
socket_close($sock);
?>
唯一的解決方法是取得介面的廣播位址,並使用 for 迴圈遍歷所有 IP。
-3
firefly2442 at hotmail dot com
16 年前
這是一個簡單的腳本,用於在伺服器和客戶端之間來回傳送訊息。目前,程式碼相當粗糙,因為一旦進入 while 迴圈,它就不會停止,但可以修改和修正。請享用。

<?php
//伺服器端
error_reporting(E_ALL);
$address = "127.0.0.1";
$port = "10000";


/* 在 AF_INET 網路家族中建立一個 socket,使用 SOCK_STREAM 作為 TCP 連線 */
$mysock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($mysock, $address, $port);

socket_listen($mysock, 5);

$client = socket_accept($mysock);

echo
"伺服器已啟動,正在接受連線...\n";


$i = 0;
while (
true == true)
{
$i++;
echo
"正在傳送 $i 給客戶端。\n";
socket_write($client, $i, strlen($i));

$input = socket_read($client, 2048);
echo
"來自客戶端的響應是: $input\n";
sleep(5);
}

echo
"正在關閉 sockets...";
socket_close($client);

socket_close($mysock);

?>

<?php
//客戶端
error_reporting(E_ALL);

$address = "127.0.0.1";
$port = 10000;

/* 建立 TCP/IP socket。 */
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (
$socket === false) {
echo
"socket_create() 失敗,原因:" . socket_strerror(socket_last_error()) . "\n";
} else {
echo
"socket 成功建立。\n";
}

echo
"嘗試連線到 '$address' 的 '$port' 埠...";
$result = socket_connect($socket, $address, $port);
if (
$result === false) {
echo
"socket_connect() 失敗。\n原因:($result) " . socket_strerror(socket_last_error($socket)) . "\n";
} else {
echo
"成功連線到 $address。\n";
}

$i = 0;
while (
true == true)
{
$i++;
echo
"正在傳送 $i 給伺服器。\n";
socket_write($socket, $i, strlen($i));

$input = socket_read($socket, 2048);
echo
"來自伺服器的響應是: $input\n";
sleep(5);
}

echo
"正在關閉 socket...";
socket_close($socket);
?>
-2
aidan at php dot net
20 年前
hexdump() 是一個很棒的函式,用於「傾印」來自伺服器的封包或二進制輸出。請參閱以下連結以取得更多資訊。

http://aidanlister.com/repos/v/function.hexdump.php
-2
talmage at usi-rpg dot com
21 年前
我花了過去兩天時間,為了弄清楚如何使用上述範例防止殭屍進程而抓破頭,我剛好在另一種語言的手冊中找到這個,覺得有必要移植到這裡。

--開始複製--
van[at]webfreshener[dot]com
2002 年 10 月 11 日 02:53

Forking 您的 PHP 守護進程會在退出時導致它變成殭屍。

...或者我在
FreeBSD (PHP4.2.x)
Debian (PHP4.3.0-dev)
Darwin (PHP4.3.0-dev)

這已使用上面的範例程式碼和其他為評估而建立的腳本進行測試。

似乎在您的設定中加入 <b>--enable-sigchild</b> 將會解決這個問題。

希望這可以讓您免於一些抓頭髮的痛苦:]

--結束複製--

感謝 vam@wenfreshener.com !!!!
-2
daniel[at]lorch.cc
22 年前
「Beej 的網路程式設計指南」是一本絕對出色且容易理解的 Socket 程式設計教學。它是為 C 開發人員編寫的,但由於 PHP 中的 Socket 函式(幾乎)是類似的,這應該不是問題。

http://www.ecst.csuchico.edu/~beej/guide/net/
-2
judeman at yahoo dot com
23 年前
在嘗試使用 Socket 進行 UDP 廣播數小時後,我認為對於任何想要做類似事情的人來說,都需要一點幫助,因為它使用了許多「未記錄」的函式。以下是我如何做的

<?php
// 這裡是 socket 的基本開啟方式。AF_INET 指定網際網路網域。SOCK_DGRAM
// 指定資料包 socket 類型,0 指定我想使用預設協定(在此
// 情況下是 UDP)
$sock = socket(AF_INET, SOCK_DGRAM, 0);

// 如果指派給 socket 的檔案控制代碼小於 0,則開啟 socket 失敗
if($sock < 0)
{
echo
"socket() 失敗,錯誤:" . strerror($sock) . "\n";
}

// 這裡是我設定 socket 選項的地方,這對於允許廣播至關重要。先前的評論(截至
// 2001 年 6 月 4 日)解釋了參數是什麼。對於我的目的(UDP 廣播),我需要將
// socket 層級的廣播選項設定為 true。在 C 中,這使用 SOL_SOCKET 作為層級參數
// (2) 和 SO_BROADCAST 作為類型參數 (3) 完成。這些可能存在於 PHP 中,但我無法參考它們
// 因此我使用了在 C 中參考這些變數時傳回的值(分別為 1 和 6)。此
// 函式基本上只是 C 函式的包裝函式,因此請查看 C 文件以取得更多資訊
$opt_ret = setsockopt($sock, 1, 6, TRUE);

// 如果傳回值小於 1,則在設定選項時發生錯誤
if($opt_ret < 0)
{
echo
"setsockopt() 失敗,錯誤:" . strerror($opt_ret) . "\n";
}

// 最後我準備好廣播一些內容。sendto 函式允許在沒有任何
// 連線的情況下進行此操作(對於廣播至關重要)。因此,此函式會將 $broadcast_string 的內容傳送至
// 一般廣播位址 (255.255.255.255) 的 4096 埠。0(參數 4)指定沒有特殊
// 選項,您可以使用 man sendto 閱讀有關這些選項的資訊
$send_ret = sendto($sock, $broadcast_string, strlen($broadcast_string), 0, '255.255.255.255', 4096);

// 如果傳回值小於 0,則發生錯誤
if($send_ret < 0)
{
echo
"sendto() 失敗,錯誤:" . strerror($send_ret) . "<BR>\n"; }
// 完成後務必關閉您的 socket
close($sock);
-4
goldemish at tiscali dot it
18 年前
無需 socket 程式庫即可傳送用於廣域網路喚醒 (WOW) 或區域網路喚醒 (WOL) 的 Magic Packet 的函式。

<?
function WakeOnLan($ip, $mac, $port)
{
$packet = "";
for($i = 0; $i < 6; $i++) $packet .= chr(0xFF);
for($i = 0; $i < 6; $i++) $packet .= chr((int)substr($mac, $i, $i + 2));
$nic = fsockopen("udp://" . $ip, $port));
if($nic==false){
return false;
fclose($nic);
}
fwrite($nic, $packet);
fclose($nic);
return true;
}
?>
-5
murzik [at] pisem dot net
21 年前
>傳送 WakeOnLan (WOL, Magic Packet) 信號的函式

<?php
# 網路喚醒 (Wake on LAN) - (c) HotKey (at SPR dot AT), 由 Murzik <tomurzik@inbox.ru> 升級

flush();

function
WakeOnLan($addr, $mac)
{
$addr_byte = explode(':', $mac);
$hw_addr = '';

for (
$a=0; $a < 6; $a++) $hw_addr .= chr(hexdec($addr_byte[$a]));

$msg = chr(255).chr(255).chr(255).chr(255).chr(255).chr(255);

for (
$a = 1; $a <= 16; $a++) $msg .= $hw_addr;

// 使用 UDP 發送到廣播位址
// SQL_BROADCAST 選項沒有幫助!!
$s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if (
$s == false)
{
echo
"建立 Socket 錯誤!\n";
echo
"錯誤代碼是 '".socket_last_error($s)."' - " . socket_strerror(socket_last_error($s));
}
else
{
// 設定 Socket 的廣播選項:
$opt_ret = socket_set_option($s, 1, 6, TRUE);
if(
$opt_ret < 0)
{
echo
"setsockopt() 失敗, 錯誤: " . strerror($opt_ret) . "\n";
}
$e = socket_sendto($s, $msg, strlen($msg), 0, $addr, 2050);
socket_close($s);
echo
"魔術封包已傳送 (".$e.") 到 ".$addr.", MAC=".$mac;
}
}

#WakeOnLan('您的IP或網域名稱.dyndns.org', '您的:MAC:位址');
#WakeOnLan('192.168.0.2', '00:30:84:2A:90:42');
#WakeOnLan('192.168.1.2', '00:05:1C:10:04:05');

//如果您在區域網路中有交換器或其他路由設備,傳送到
// 本機IP沒有幫助!您需要像這樣傳送到廣播位址:
WakeOnLan('192.168.1.255', '00:05:1C:10:04:05');

?>
-4
f.moisant
18 年前
這個傳送魔術封包的函數真的有效!!!

/********/
<?php
function wake($ip, $mac, $port)
{
$nic = fsockopen("udp://" . $ip, $port);
if(
$nic)
{
$packet = "";
for(
$i = 0; $i < 6; $i++)
$packet .= chr(0xFF);
for(
$j = 0; $j < 16; $j++)
{
for(
$k = 0; $k < 6; $k++)
{
$str = substr($mac, $k * 2, 2);
$dec = hexdec($str);
$packet .= chr($dec);
}
}
$ret = fwrite($nic, $packet);
fclose($nic);
if(
$ret)
return
true;
}
return
false;
}
?>

/********/

執行範例:
wake('123.123.123.123', '112233445566', 9);
To Top