◆ Socket 基礎
php使用Berkley的socket庫來建立它的串連。socket只不過是一個資料結構。你使用這個socket資料結構去開始一個用戶端和伺服器之間的會話。這個伺服器是一直在監聽準備產生一個新的會話。當一個用戶端串連伺服器,它就開啟伺服器進行中監聽的一個連接埠進行會話。這時,伺服器端接受用戶端的串連請求,那麼就進行一次迴圈。現在這個用戶端就能夠發送資訊到伺服器,伺服器也能發送資訊給用戶端。
產生一個Socket,你需要三個變數:一個協議、一個socket類型和一個公用協議類型。產生一個socket有三種協議供選擇,繼續看下面的內容來擷取詳細的協議內容。
定義一個公用的協議類型是進行串連一個必不可少的元素。下面的表我們看看有那些公用的協議類型。
表一:協議
名字/常量 描述
AF_INET 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用在ipv4的地址
AF_INET6 與上面類似,不過是來用在IPv6的地址
AF_UNIX 本地協議,使用在Unix和linux系統上,它很少使用,一般都是當用戶端和伺服器在同一台機器上的時候使用
表二:Socket類型
名字/常量 描述
SOCK_STREAM 這個協議是按照順序的、可靠的、資料完整的基於位元組流的串連。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。
SOCK_DGRAM 這個協議是不需連線的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的串連。
SOCK_SEQPACKET 這個協議是雙線路的、可靠的串連,發送固定長度的資料包進行傳輸。必須把這個包完整的接受才能進行讀取。
SOCK_RAW 這個socket類型提供單一的網路訪問,這個socket類型使用ICMP公用協議。(ping、traceroute使用該協議)
SOCK_RDM 這個類型是很少使用的,在大部分的作業系統上沒有實現,它是提供給資料連結層使用,不保證資料包順序
表三:公用協議
名字/常量 描述
ICMP 互連網控制訊息協議,主要使用在網關和主機上,用來檢查網路狀況和報告錯誤資訊
UDP 使用者資料報文協議,它是一個無串連,不可靠的傳輸協議
TCP 傳輸控制通訊協定,這是一個使用最多的可靠的公用協議,它能保證資料包能夠到達接受者那兒,如果在傳輸過程中發生錯誤,那麼它將重新發送出錯資料包。
現在你知道了產生一個socket的三個元素,那麼我們就在php中使用socket_create()函數來產生一個socket。這個socket_create()函數需要三個參數:一個協議、一個socket類型、一個公用協議。socket_create()函數運行成功返回一個包含socket的資源類型,如果沒有成功則返回false。
Resourece socket_create(int PRotocol, int socketType, int commonProtocol);
現在你產生一個socket,然後呢?php提供了幾個操縱socket的函數。你能夠綁定socket到一個IP,監聽一個socket的通訊,接受一個socket;現在我們來看一個例子,瞭解函數是如何產生、接受和監聽一個socket。
$commonProtocol = getprotobyname(“tcp”);//使用公用協議名字來擷取一個協議類型
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);//產生一個socket並且返回一個socket資源的執行個體
socket_bind($socket, ‘localhost’, 1337);//綁定socket到本機電腦
socket_listen($socket);//監聽所有進來的socket串連
// More socket functionality to come
?>
上面這個例子產生一個你自己的伺服器端。例子第一行,
$commonProtocol = getprotobyname(“tcp”);
使用公用協議名字來擷取一個協議類型。在這裡使用的是TCP公用協議,如果你想使用UDP或者ICMP協議,那麼你應該把getprotobyname()函數的參數改為“udp”或“icmp”。還有一個可選的辦法是不使用getprotobyname()函數而是指定SOL_TCP或SOL_UDP在socket_create()函數中。
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
例子的第二行是產生一個socket並且返回一個socket資源的執行個體。在你有了一個socket資源的執行個體以後,你就必須把socket綁定到一個IP地址和某一個連接埠上。
socket_bind($socket, ‘localhost’, 1337);
在這裡你綁定socket到本機電腦(127.0.0.1)和綁定socket到你的1337連接埠。然後你就需要監聽所有進來的socket串連。
socket_listen($socket);
在第四行以後,你就需要瞭解所有的socket函數和他們的使用。
表四:Socket函數
函數名 描述
socket_accept() 接受一個Socket串連
socket_bind() 把socket綁定在一個IP地址和連接埠上
socket_clear_error() 清除socket的錯誤或者最後的錯誤碼
socket_close() 關閉一個socket資源
socket_connect() 開始一個socket串連
socket_create_listen() 在指定連接埠開啟一個socket監聽
socket_create_pair() 產生一對沒有區別的socket到一個數組裡
socket_create() 產生一個socket,相當於產生一個socket的資料結構
socket_get_option() 擷取socket選項
socket_getpeername() 擷取遠程類似主機的ip地址
socket_getsockname() 擷取本地socket的ip地址
socket_iovec_add() 添加一個新的向量到一個分散/彙總的數組
socket_iovec_alloc() 這個函數建立一個能夠發送接收讀寫的iovec資料結構
socket_iovec_delete() 刪除一個已經分配的iovec
socket_iovec_fetch() 返回指定的iovec資源的資料
socket_iovec_free() 釋放一個iovec資源
socket_iovec_set() 設定iovec的資料新值
socket_last_error() 擷取當前socket的最後錯誤碼
socket_listen() 監聽由指定socket的所有串連
socket_read() 讀取指定長度的資料
socket_readv() 讀取從分散/彙總數組過來的資料
socket_recv() 從socket裡結束資料到緩衝
socket_recvfrom() 接受資料從指定的socket,如果沒有指定則預設當前socket
socket_recvmsg() 從iovec裡接受訊息
socket_select() 多路選擇
socket_send() 這個函數發送資料到已串連的socket
socket_sendmsg() 發送訊息到socket
socket_sendto() 發送訊息到指定地址的socket
socket_set_block() 在socket裡設定為塊模式
socket_set_nonblock() socket裡設定為非塊模式
socket_set_option() 設定socket選項
socket_shutdown() 這個函數允許你關閉讀、寫、或者指定的socket
socket_strerror() 返回指定錯誤號碼的詳細錯誤
socket_write() 寫資料到socket緩衝
socket_writev() 寫資料到分散/彙總數組
以上所有的函數都是PHP中關於socket的,使用這些函數,你必須把你的socket開啟,如果你沒有開啟,請編輯你的php.ini檔案,去掉下面這行前面的注釋:
extension=php_sockets.dll
如果你無法去掉注釋,那麼請使用下面的代碼來載入擴充庫:
if(!extension_loaded(‘sockets’)) {
if(strtoupper(substr(PHP_OS, 3)) == “WIN”) {
dl(‘php_sockets.dll’);
}else{
dl(‘sockets.so’);
}
}
?>
如果你不知道你的socket是否開啟,那麼你可以使用phpinfo()函數來確定socket是否開啟。你通過查看phpinfo資訊瞭解socket是否開啟。
查看phpinfo()關於socket的資訊
◆ 產生一個伺服器
現在我們把第一個例子進行完善。你需要監聽一個指定的socket並且處理使用者的串連。
$commonProtocol = getprotobyname("tcp");
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// Accept any incoming connections to the server
$connection = socket_accept($socket);
if($connection){
socket_write($connection, "You have connected to the socket...\n\r");
}
?>
你應該使用你的命令提示字元來運行這個例子。理由是因為這裡將產生一個伺服器,而不是一個Web頁面。如果你嘗試使用Web瀏覽器來運行這個指令碼,那麼很有可能它會超過30秒的限時。你可以使用下面的代碼來設定一個無限的已耗用時間,但是還是建議使用命令提示字元來運行。
set_time_limit(0);
在你的命令提示字元中對這個指令碼進行簡單測試:
Php.exe example01_server.php
如果你沒有在系統的環境變數中設定php解譯器的路徑,那麼你將需要給php.exe指定詳細的路徑。當你運行這個伺服器端的時候,你能夠通過遠程登陸(telnet)的方式串連到連接埠1337來測試這個伺服器。
上面的伺服器端有三個問題:1. 它不能接受多個串連。2. 它只完成唯一的一個命令。3. 你不能通過Web瀏覽器串連這個伺服器。
這個第一個問題比較容易解決,你可以使用一個應用程式去每次都串連到伺服器。但是後面的問題是你需要使用一個Web頁面去串連這個伺服器,這個比較困難。你可以讓你的伺服器接受串連,然後些資料到用戶端(如果它一定要寫的話),關閉串連並且等待下一個串連。
在上一個代碼的基礎上再改進,產生下面的代碼來做你的新伺服器端:
// Set up our socket
$commonProtocol = getprotobyname("tcp");
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);
socket_bind($socket, 'localhost', 1337); //socket_bind() 把socket綁定在一個IP地址和連接埠上
socket_listen($socket);
// Initialize the buffer
$buffer = "NO DATA";
while(true) {
// Accept any connections coming in on this socket
$connection = socket_accept($socket);//socket_accept() 接受一個Socket串連
printf("Socket connected\r\n");
// Check to see if there is anything in the buffer
if($buffer != ""){
printf("Something is in the buffer...sending data...\r\n");
socket_write($connection, $buffer . "\r\n"); //socket_write() 寫資料到socket緩衝
printf("Wrote to socket\r\n");
}else {
printf("No Data in the buffer\r\n");
}
// Get the input
while($data = socket_read($connection, 1024, PHP_NORMAL_READ))//socket_read() 讀取指定長度的資料
{
$buffer = $data;
socket_write($connection, "Information Received\r\n");
printf("Buffer: " . $buffer . "\r\n");
}
socket_close($connection); //socket_close() 關閉一個socket資源
printf("Closed the socket\r\n\r\n");
}
?>
這個伺服器端要做什麼呢?它初始化一個socket並且開啟一個緩衝收發資料。它等待串連,一旦產生一個串連,它將列印“Socket connected”在伺服器端的螢幕上。這個伺服器檢查緩衝區,如果緩衝區裡有資料,它將把資料發送到串連過來的電腦。然後它發送這個資料的接受資訊,一旦它接受了資訊,就把資訊儲存到資料裡,並且讓串連的電腦知道這些資訊,最後關閉串連。當串連關閉後,伺服器又開始處理下一次串連。
◆ 產生一個用戶端
處理第二個問題是很容易的。你需要產生一個php頁串連一個socket,發送一些資料進它的緩衝並處理它。然後你有個處理後的資料在還頓,你能夠發送你的資料到伺服器。在另外一台用戶端串連,它將處理那些資料。
下面的例子示範了使用socket:
// Create the socket and connect
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connection = socket_connect($socket,’localhost’, 1337);
while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)) {
if($buffer == “NO DATA”) {
echo(“
NO DATA
”);
break;
}else{
// Do something with the data in the buffer
echo(“
Buffer Data: “ . $buffer . “
”);
}
}
echo(“
Writing to Socket
”);
// Write some test data to our socket
if(!socket_write($socket, “SOME DATA\r\n”)){
echo(“
Write failed
”);
}
// Read any response from the socket
while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)){
echo(“
Data sent was: SOME DATA
Response was:” . $buffer . “
”);
}
echo(“
Done Reading from Socket
”);
?>
這個例子的代碼示範了用戶端串連到伺服器。用戶端讀取資料。如果這是第一時間到達這個迴圈的首次串連,這個伺服器將發送“NO DATA”返回給用戶端。如果情況發生了,這個用戶端在串連之上。用戶端發送它的資料到伺服器,資料發送給伺服器,用戶端等待響應。一旦接受到響應,那麼它將把響應寫到螢幕上。