天天看點

php Socket 基礎

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。

<?php

$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);

// 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

socket_bind($socket, 'localhost', 1337); //socket_bind()     把socket綁定在一個IP位址和端口上

// 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

$connection = socket_connect($socket,’localhost’, 1337);

while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)) {

if($buffer == “NO DATA”) {

echo(“<p>NO DATA</p>”);

break;

// Do something with the data in the buffer

echo(“<p>Buffer Data: “ . $buffer . “</p>”);

echo(“<p>Writing to Socket</p>”);

// Write some test data to our socket

if(!socket_write($socket, “SOME DATA\r\n”)){

echo(“<p>Write failed</p>”);

// Read any response from the socket

while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)){

echo(“<p>Data sent was: SOME DATA<br> Response was:” . $buffer . “</p>”);

echo(“<p>Done Reading from Socket</p>”);

這 個例子的代碼示範了用戶端連接配接到伺服器。用戶端讀取資料。如果這是第一時間到達這個循環的首次連接配接,這個伺服器将發送“NO DATA”傳回給用戶端。如果情況發生了,這個用戶端在連接配接之上。用戶端發送它的資料到伺服器,資料發送給伺服器,用戶端等待響應。一旦接受到響應,那麼 它将把響應寫到螢幕上。