這些Socket函數直接跟著網際網路的協定發送訊息。相對於fopensock的流來講,他們操作在一個比較底層的級別。通常,他們都是對C 函數進行封裝,並且名稱都類似。如果你有使用C進行socket程式設計的經驗,那麼使用這些函數將是非常熟練的。我們這裡不討論特別詳細的socket程式。
使用這些函數能夠解決高層等級函數所無法解決的難題。使用這些函數能夠實現類似fopen的功能,你或許有很多方法來實作socket的功能,例如在PHP中使用CLI(Command-line Interface)來實現的Internet守護程式。
resource socket_accept(resource socket)
在你的腳本伺服器端中,使用socket_accept接受一個進入的連線。你必須先產生一個socket,綁定它到一個名字,並且設定它監聽一個連接埠。在區塊模式中,socket_accept將產生一個唯一接受後的連接。在非區塊模式中,它沒有建立連線則傳回false。另外,當你有了新的socket資源後就能夠進行讀寫操作。
下面我們將示範一個簡單的回顯伺服器端。它運行在CLI(命令列)下面,它在12345埠等待客戶端的連線。
socket_accept
<?php
set_time_limit(0);
//create the socket
if(($socket = socket_create(AF_INET, SOCK_STREAM, 0)) < 0){
print("Couldn't create socket: " . socket_strerror(socket_last_error()) . "n");
}
//bind it to the given address and port
if(($error = socket_bind($socket, gethostbyname($_SERVER['HOSTNAME']), 12345)) < 0){
print("Couldn't bind socket: " . socket_strerror(socket_last_error()) . "n");
}
if(($error = socket_listen($socket, 5)) < 0){
print("Couldn't list on socket: " .
socket_strerror(socket_last_error()) . "n");
}
while(TRUE){
//wait for connection
if(($accept = socket_accept($socket)) < 0){
print("Error while reading: " . socket_strerror($message) . "n");
break;
}
//send welcome message
socket_write($accept, "Connection acceptedn");
print(date('Ymd H:i:s') . " STATUS: Connection acceptedn");
ob_flush();
while(TRUE){
//read line from client
if(FALSE === ($line = socket_read($accept, 1024))){
print("Couldn't read from socket: " .
socket_strerror(socket_last_error()) . "n");
break 2;
}
if( !@socket_write($accept , "ECHO: $line")){
print(date('Ymd H:i:s') . " STATUS: Connection interruptedn");
break;
}
print(date('Ymd H:i:s') . " READ: $line");
ob_flush();
}
socket_close($accept);
}
?>
bool socket_bind(resource socket, string address, integer port)
這個socket_bind()把一個socket資源綁定在一個位址上。這個socket必須由socket_create()函數傳回的一個資源。這個位址必須是一個IP位址或是一個保存Unix socket的路徑。如果是運行在Internet上的socket,你還必須提供一個連接埠。
socket_clear_error(resource socket)
這個函數能夠清除制定socket的錯誤,如果沒有指定參數,那麼就會清除所有socket的錯誤。
socket_close(resource socket)
socket_close函數關閉一個socket並且清除該socket所佔用的記憶體資源。
boolean socket_connect(resource socket, string address, integer port)
這個函數建立一個客戶端到一個連接埠或socket的連線。你必須提供一個由socket_create產生的socket。這個address參數必須到一個socket的路徑或是IP位址。如果是後者,也必須跟一個數字的連接埠號碼。
以下範例示範了使用UDP協定的連接到遊戲伺服器然後取得資訊的過程。
socket_connect
<?php
//create UDP socket
if(($socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) < 0){
print("Couldn't create socket: " .
socket_strerror(socket_last_error()) . "n");
}
//timeout after 5 seconds
socket_set_option($socket, SOL_SOCKET,
SO_RCVTIMEO, array('sec'=>5,'usec'=>0));
//connect to the RtCW master server
if(!socket_connect($socket, 'wolfmaster.idsoftware.com', 27950)){
print("Couldn't connect: " .
socket_strerror(socket_last_error()) . "n");
}
//send request for servers
socket_write($socket, "xFFxFFxFFxFFgetserversx00");
//get servers
$server = array();
while(FALSE !== ($line = @socket_read($socket, 4096))){
//parse data
for($i=22; ($i+5) < strlen($line); $i += 7){
$ip = ord(substr($line, $i+1, 1)) . '.' .
ord(substr($line, $i+2, 1)) . '.' .
ord(substr($line, $i+3, 1)) . '.' .
ord(substr($line, $i+4, 1));
$port = (ord(substr($line, $i+5, 1)) * 256) +
ord(substr($line, $i+6, 1));
$server[] = array('ip'=>$ip, 'port'=>$port);
}
}
print("<h1>" . count($server) . " Servers</h1>n");
//loop over servers, getting status
foreach($server as $s){
print("<h1>{$s['ip']}:{$s['port']}</h1>n");
//connect to RtCW server
if(!socket_connect($socket, $s['ip'], $s['port'])){
print("<p>n" .
socket_strerror(socket_last_error()) .
"n</p>n");
continue;
}
//send request for status
socket_write($socket, "xFFxFFxFFxFFgetstatusx00");
//get status from server
if(FALSE === ($line = @socket_read($socket, 1024))){
print("<p>n" .
socket_strerror(socket_last_error()) .
"n</p>n");
continue;
}
$part = explode("n", $line);
//settings are in second line separated by backslashes
$setting = explode("\", $part[1]);
print("<h2>Configuration</h2>n");
print("<p>n");
for($s=1; $s < count($setting); $s += 2){
print("tt{$setting[$s]} = {$setting[$s+1]}<br>n");
}
print("</p>n");
print("<h2>Players</h2>n");
$lastPlayer = count($part) - 1;
for($p=2; $p < $lastPlayer; $p++){
$player = explode(" ", $part[$p]);
print("{$player[2]} Score={$player[0]} " .
"Ping={$player[1]}<br>n");
}
print("</p>n");
ob_flush();
}
print("</table>n");
socket_close($socket);
?>
resource socket_create(integer family, integer socket_type, integer protocol)
socket_create初始化一個socket的結構。第一個參數是一個protocol family,或域。你必須使用AF_INET來指定一個Internet連接,或是使用AF_UNIX來指定一個Unix socket連接。第二個參數是一個socket的類型,你可以從下面的表格中選擇。一般情況下,使用SOCK_STREAM來使用TCP協議,UDP協定使用SOCK_DGRAM。第三個參數指定為一個協定。使用SOL_TCP或SOL_UDP來分別對應TCP和UDP協定。還有一個選擇是你能夠使用getprotobyname函數來處理。
Socket 類型常數描述
SOCK_DGRAM 自動定址封包socket
SOCK_RAW RAW協定接口
SOCK_RDM 可靠交換訊息
SOCK_SEQPACKET 順序封包socket
SOCK_STREAM 流socket
resource socket_create_listen(integer port, integer backlog)
使用socket_create_listen是一種比socket_create更簡單的產生一個socket進行監聽。這個產生的socket將監聽指定的端口,後面可選的參數backlog是設定允許最大的連接數。
boolean socket_create_pair(integer family, integer socket_type, integer protocol, array handles)
socket_create_pair函數產生一對socket連線。首先前三個參數是對一個socket_create的描述,這個handles參數是一個包含兩個socket資源的陣列。此函數是對C裡面socketpair函數的封裝。
socket_create_pair
<?php
if(!socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $socket)){
print("Couldn't make sockets!n");
exit();
}
$child = pcntl_fork();
if($child == -1){
print("Couldn't fork!n");
exit();
}
elseif($child > 0){
//parent
socket_close($socket[0]);
print("Parent: waiting for messagen");
$message = socket_read($socket[1], 1024, PHP_NORMAL_READ);
print("Parent: got message--$messagen");
socket_write($socket[1], "Hello, Child Process!n");
pcntl_waitpid($child, $status);
}else{
//child
socket_close($socket[1]);
socket_write($socket[0], "Hello, Parent Process!n");
print("Child: waiting for messagen");
$message = socket_read($socket[0], 1024, PHP_NORMAL_READ);
print("Child: got message--$messagen");
exit(0);
}
?>
value socket_get_option(resource socket, integer level, integer option)
socket_get_option函數傳回一個下表所列的一個新增值,你必須提供一個由socket_create產生的socket資源和一個等級。這個取得的socket級別,可以使用SOL_SOCKET來確定這個級別參數。另外,使用協議,例如像SOL_TCP來表示一個TCP協議。這些選項可能是由socket_set_option設定的。
socket_get_options
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
print('SO_BROADCAST: ' .
socket_get_option($socket, SOL_SOCKET,
SO_BROADCAST) . "<br>n");
print('SO_DEBUG: ' .
socket_get_option($socket, SOL_SOCKET,
SO_DEBUG) . "<br>n");
print('SO_DONTROUTE: ' .
socket_get_option($socket, SOL_SOCKET,
SO_DONTROUTE) . "<br>n");
print('SO_ERROR: ' .
socket_get_option($socket, SOL_SOCKET,
SO_ERROR) . "<br>n");
print('SO_KEEPALIVE: ' .
socket_get_option($socket, SOL_SOCKET,
SO_KEEPALIVE) . "<br>n");
print('SO_LINGER: ' .
print_r(socket_get_option($socket, SOL_SOCKET,
SO_LINGER), TRUE) . "<br>n");
print('SO_OOBINLINE: ' .
socket_get_option($socket, SOL_SOCKET,
SO_OOBINLINE) . "<br>n");
print('SO_RCVBUF: ' .
socket_get_option($socket, SOL_SOCKET,
SO_RCVBUF) . "<br>n");
print('SO_RCVLOWAT: ' .
socket_get_option($socket, SOL_SOCKET,
SO_RCVLOWAT) . "<br>n");
print('SO_RCVTIMEO: ' .
print_r(socket_get_option($socket, SOL_SOCKET,
SO_RCVTIMEO), TRUE) . "<br>n");
print('SO_REUSEADDR: ' .
socket_get_option($socket, SOL_SOCKET,
SO_REUSEADDR) . "<br>n");
print('SO_SNDBUF: ' .
socket_get_option($socket, SOL_SOCKET,
SO_SNDBUF) . "<br>n");
print('SO_SNDLOWAT: ' .
socket_get_option($socket, SOL_SOCKET,
SO_SNDLOWAT) . "<br>n");
print('SO_SNDTIMEO: ' .
print_r(socket_get_option($socket, SOL_SOCKET,
SO_SNDTIMEO), TRUE) . "<br>n");
print('SO_TYPE: ' .
socket_get_option($socket, SOL_SOCKET,
SO_TYPE) . "<br>n");
?>
Socket選項表選項描述
SO_BROADCAST 允許自動尋址的socket發送和接受廣播包
SO_DEBUG 開啟socket偵錯功能,只有root才有權限開啟該選項
SO_DONTROUTE 不接受路由包通過網關
SO_ERROR 取得並清除最後一次的socket錯誤,這個選項也許不用設定
SO_KEEPALIVE 開啟保持啟動狀態的訊息
SO_LINGER Socket_colse和socket_shutdown的中止訊息發送逾時,該選項使用一個數組,包括l_onoff和l_linger兩個鍵。
SO_OOBINLINE 把資料直接插入到接受緩衝
SO_RCVBUF 限制接受緩衝的最大位元組
SO_RCVLOWAT 延遲透過接受一個最小的數據
SO_RCVTIMEO 延遲報告一個接受逾時報告,使用陣列的兩個鍵:sec和usec
SO_REUSEADDR 允許重新使用本地位址
SO_SNDBUF 限制發送緩衝的最大位元組
SO_SNDLOWAT 延遲發送資料到這個協定當接受一個最小的位元組
SO_SNDTIMEO 延遲報告逾時錯誤,當發送發送通過一個時間。此選項使用陣列的鍵值:sec和usec
SO_TYPE 取得socket的類型,選項可能不用設定
boolean socket_getpeername(resource socket, string address, integer port)
socket_getpeername從指定的一個連線中取得位址和連接埠。如果連接為Unix socket,那麼將返回檔案系統的路徑。
boolean socket_getsockname(resource socket, string address, integer port)
socket_getsockname放置一個名字到socket中,並且加上address和port參數。失敗回傳false。
(下面的socket_iovec_* 函數不太了解,不敢亂翻譯,保留原文)
boolean socket_iovec_add(resource iovector, integer length)
The socket_iovec_add unction adds an I/O vector to the scatter/gather array.
resource socket_iovec_alloc(integer count, …)
The socket_iovec_alloc function returns a resource for handling a collection of I/O vectors. The first argument specifies the number of vectors. Following arguments specify the length of each vector.
boolean socket_iovec_delete(resource iovector, integer position)
The socket_iovec_delete function removes the I/O vector at the given position.
string socket_iovec_fetch(resource iovector, integer position)
The socket_iovec_fetch function returns the value of the specified vector in the I/O vector resource.
boolean socket_iovec_free(resource iovector)
The socket_iovec_free function frees the memory used for an I/O vector resource.
boolean socket_iovec_set(resource iovector, integer position, string value)
The socket_iovec_set sets the value of I/O vector at the given position.
integer socket_last_error(resource socket)
socket_last_error函數傳回操作中的任何socket函數所產生的最後錯誤。你也許在上面函數中設定了socket資源的socket選項在指定的連線上。下面的表格列出了傳回的錯誤代碼,你同樣可以使用soclet_strerror函數來取得詳細的錯誤。使用socket_clear_error函數清除socket的錯誤。
Socket錯誤代碼表常數 描述
SOCKET_E2BIG 參數列表太長
SOCKET_EACCES 沒有許可權限
SOCKET_EADDRINUSE 位址已經被使用
SOCKET_EADDRNOTAVAIL 無法解析請求的位址
SOCKET_EADV 廣播(廣告)錯誤
SOCKET_EAFNOSUPPORT Address family不支援的協議
SOCKET_EAGAIN 資源暫時無法取得
SOCKET_EALREADY 操作已經在執行
SOCKET_EBADE 無效的交換
SOCKET_EBADF 錯誤的檔案描述符
SOCKET_EBADFD 檔案描述符錯誤的狀態
SOCKET_EBADMSG 錯誤的訊息
SOCKET_EBADR 無效的請求描述
SOCKET_EBADRQC 無效的請求代碼
SOCKET_EBADSLT 無效的操作位置
SOCKET_EBUSY 驅動或資源繁忙
SOCKET_ECHRNG 頻道號碼超出範圍
SOCKET_ECOMM 傳送通訊錯誤
SOCKET_ECONNABORTED 軟體原因導致通行中斷
SOCKET_ECONNREFUSED 連線被拒絕
SOCKET_ECONNRESET 連接被相同的socket重置
SOCKET_EDESTADDRREQ 必須需要目標位址
SOCKET_EDQUOT 超出磁碟配額
SOCKET_EEXIST 檔案已存在
SOCKET_EFAULT 錯誤的位址
SOCKET_EHOSTDOWN 主機已關閉
SOCKET_EHOSTUNREACH 沒有路由到主機
SOCKET_EIDRM 表示ID被刪除
SOCKET_EINPROGRESS 操作正在執行
SOCKET_EINTR 系統呼叫被阻止
SOCKET_EINVAL 無效的參數
SOCKET_EIO 輸入/ 輸出錯誤
SOCKET_EISCONN 傳輸終端機已經連接
SOCKET_EISDIR 是一個目錄
SOCKET_EISNAM 是一個指定的型別文件
SOCKET_EL2HLT 級別2已中止
SOCKET_EL2NSYNC 級別2不同步
SOCKET_EL3HLT 級別3已中止
SOCKET_EL3RST 級別3被重置
SOCKET_ELNRNG 連接號碼超出範圍
SOCKET_ELOOP 太多等級的符號連接
SOCKET_EMEDIUMTYPE 錯誤的媒介類型(中間型別)
SOCKET_EMFILE 太多開啟的文件
SOCKET_EMLINK 太多的連接
SOCKET_EMSGSIZE 訊息太長
SOCKET_EMULTIHOP 嘗試次數太多
SOCKET_ENAMETOOLONG 檔名太長
SOCKET_ENETDOWN 網路已關閉
SOCKET_ENETRESET 網路中斷,連線被重置
SOCKET_ENETUNREACH網路不可達
SOCKET_ENFILE 系統中太多開啟的文件
SOCKET_ENOANO 沒有正極
SOCKET_ENOBUFS 沒有可用的快取空間
SOCKET_ENOCSI 沒有可用的CSI結構
SOCKET_ENODATA 沒有可用的數據
SOCKET_ENODEV 沒有這樣的驅動
SOCKET_ENOENT 沒有這樣的檔案或目錄
SOCKET_ENOLCK 沒有可用的記錄鎖
SOCKET_ENOLINK 已經有的服務的連接
SOCKET_ENOMEDIUM 沒有媒介被找到
SOCKET_ENOMEM 不能分配內存
SOCKET_ENOMSG 沒有指定的訊息類型
SOCKET_ENONET 設備不在網路上
SOCKET_ENOPROTOOPT 協定不可用
SOCKET_ENOSPC 沒有空間在驅動器
SOCKET_ENOSR 超出的流量資源
SOCKET_ENOSTR 驅動程式不是一個流
SOCKET_ENOSYS 函數沒有執行
SOCKET_ENOTBLK 區塊驅動是必須的
SOCKET_ENOTCONN 傳輸終端機沒有連接
SOCKET_ENOTDIR 沒有一個目錄
SOCKET_ENOTEMPTY 目錄為空
SOCKET_ENOTSOCK Socket操作在一個非socket上
SOCKET_ENOTTY 不符的IO控制器
SOCKET_ENOTUNIQ 在網路上名字不是唯一的
SOCKET_ENXIO 沒有這樣的驅動或位址
SOCKET_EOPNOTSUPP 操作不支援
SOCKET_EPERM 操作不允許
SOCKET_EPFNOSUPPORT Protocol family不支持
SOCKET_EPIPE 失敗的管道
SOCKET_EPROTO 協定錯誤
SOCKET_EPROTONOSUPPORT 協定不支援
SOCKET_EPROTOTYPE Socket上協定錯誤的型別
SOCKET_EREMCHG 遠端位址已改變
SOCKET_EREMOTE 物件是遠端的
SOCKET_EREMOTEIO 遠端I/O錯誤
SOCKET_ERESTART 中斷的系統呼叫將會重新開始
SOCKET_EROFS 檔案系統為唯讀
SOCKET_ESHUTDOWN. 傳輸端點中斷不能傳送
SOCKET_ESOCKTNOSUPPORT Socket類型不支持
SOCKET_ESPIPE 不合法的檢索
SOCKET_ESTRPIPE 流管道錯誤
SOCKET_ETIME 定時器到時
SOCKET_ETIMEDOUT 連線逾時
SOCKET_ETOOMANYREFS 太多連線無法結合
SOCKET_EUNATCH 無法附加協定驅動
SOCKET_EUSERS 太多用戶
SOCKET_EWOULDBLOCK 資源暫時無法取得
SOCKET_EXDEV 無效的交叉驅動連接
SOCKET_EXFULL 交換已滿
boolean socket_listen(resource socket, integer backlog)
這個socket_listen函數等待從客戶端過來的連接,backlog參數設定允許最多等待連接的佇列數。
string socket_read(resource socket, integer length, integer type)
socket_read函數從特定的socket讀取指定的字節,如果錯誤回傳false。缺省下,是採用二進位安全的讀取模式。你可以外在的設定type參數為PHP_BINARY_READ來改變讀取模式。你也可以把type設定為PHP_NORMAL_READ。
boolean socket_readv(resource socket, resource iovector)
socket_readv函數把讀取的資料插入iovector資源中。
integer socket_recv(resource socket, string buffer, integer length, integer flags)
socket_recv函數讀取資料插入到緩衝中。 Length參數設定最多讀取的位元組數,flag參數可以使用MSG_OOB或MSG_PEEK。函數傳回讀取的位元組數。
integer socket_recvfrom(resource socket, string buffer, integer length, string host, integer port)
socket_frcvfrom函數讀取資料插入到快取中。 Length參數設定取得最多允許接受的位元組數。設定flags參數可以為MSG_OOB 或MSG_PEEK。 PHP設定主機和連接埠參數適當的值能夠取得從主機發出的資料。
boolean socket_recvmsg(resource socket, resource iovector, array control, integer length, integer flags, string host, integer port)
socket_recvmsg函數從socket讀取資料並且插入到一個I/O向量資源。 PHP設定control參數是一個具有三個元素的聯合數組:cmsg_level, cmsg_type, 和cmsg_data。 Length參數是一個附加在資料中的關於取得資料的長度參數。 Flags參數是設定允許值和傳回值。在寫入的時間,PHP無法執行所有的輸出常數。 PHP設定host和port參數適當的值是為了取得從遠端主機傳送的資料。
(Socket_slect函數沒有翻譯,因為怕詞不達意)
integer socket_select(array read, array write, array exception, integer timeout_seconds, integer timeout_microseconds)
The socket_select function waits for changes to sockets. PHP watches the sockets given in the read array for new data coming in. PHP watches the streams given in the write array for being ready to acceptches the streams given in the write array for being ready to acceptches more data. PHPoo for errors. If the number of seconds specified in the timeout_seconds argument passes, the function returns. Use the optional timeout_microseconds argument to specify a timeout less than 1 second.
The socket_select function returns the number of sockets that changed or FALSE if an error occurred。
If you have no sockets of a particular type to watch, you may pass an empty array or a variable set to NULL.
integer socket_send(resource socket, string buffer, integer length, integer flags)
socket_send函數把寫資料到緩衝中,然後插入到連線中。你必須指定一個緩衝最大可寫位元組數。你同樣可以設定flags參數為空,或是為下面聯合常數中的一個:MSG_DONTROUTE和MSG_OOB。函數結束傳回已經寫好的位元組數,失敗回傳false。
boolean socket_sendmsg(resource socket, resource iovector, integer flags, string address, integer port)
socket_sendmsg嘗試發送資料到一個socket。它適合無連接的socket。 Iovector參數是一個透過socket_iovec_alloc函數產生的資源。你必須指定flags參數為:NULL, MSG_DONTROUTE, MSG_OOB,或者是兩個聯合常數。你應指定一個位址和一個Internet請求的連接埠。
Socket_sendmsg函數傳送資料回傳true,但無法保證資料一定到達。
integer socket_sendto(resource socket, string buffer, integer length, integer flags, string address, integer port)
socket_sendto函數嘗試寫入資料到buffer緩衝中,並且傳送給一個socket。它適合大部分無連接的socket。你必須指定flags為:NULL,MSG_DONTROUTE,MSG_OOB或一個兩個聯合常數。你也應但指定位址和一個請求的連接埠。
Socket_sendto函數資料送出去回傳true,但不能保證資料一定會到達。
boolean socket_set_block(resource socket)
socket_set_block函數設定socket插入到一個區塊模式中,這是缺省模式。在區塊模式中,I/O操作正對於一個完成的請求。
boolean socket_set_nonblock(resource socket)
socket_set_nonblock函數設定socket插入到意個非區塊模式中。在非區塊模式中,I/O操作馬上返回,即使沒有資料。
boolean socket_set_option(resource socket, integer level, integer option, integer value)
socket_set_option函數為socket設定一個選項。 Level參數設定一個標誌等級的常數。有效的值包括:SOL_SOCKET, SOL_TCP和SOL_UDP。 Option參數必須符合文章上面的Socket選項表中的常數。
boolean socket_shutdown(resource socket, integer how)
socket_shutdown函數關閉一個關於I/O的socket。設定how為0則中止接受數據,設定為1則停止傳送數據,設定為2則中止二者操作。
string socket_strerror(integer error)
socket_strerror函數傳回一個錯誤號碼的詳細錯誤訊息。
integer socket_write(resource socket, string buffer, integer length)
socket_write函數寫資料到buffer緩衝中然後輸出到socket。你可以指定length參數來指定緩衝的最大位元組數。這個函數通常情況下比socket_send更方便。
boolean socket_writev(resource socket, resource iovector)
socket_writev函數透過I/O向量寫資料到一個socket。