一直以來很少看到有多少人使用php的socket模塊來做一些事情,大概大家都把它定位在腳本語言的范疇內吧,但是其實php的socket模塊可以做很多事情,包括做ftplist,http post提交,smtp提交,組包并進行特殊報文的交互(如smpp協議),whois查詢。這些都是比較常見的查詢。
特別是php的socket擴展庫可以做的事情簡直不會比c差多少。
預備知識:
php的socket連接函數
1、集成于內核的socket
這個系列的函數僅僅只能做主動連接無法實現端口監聽相關的功能。而且在4.3.0之前所有socket連接只能工作在阻塞模式下。
此系列函數包括
fsockopen,pfsockopen
這兩個函數的具體信息可以查詢php.net的用戶手冊
他們均會返回一個資源編號對于這個資源可以使用幾乎所有對文件操作的函數對其進行操作如fgets(),fwrite(), fclose()等單注意的是所有函數遵循這些函數面對網絡信息流時的規律,例如:
fread() 從文件指針 handle 讀取最多 length 個字節。 該函數在讀取完 length 個字節數,或到達 EOF 的時候,或(對于網絡流)當一個包可用時就會停止讀取文件,視乎先碰到哪種情況。
可以看出對于網絡流就必須注意取到的是一個完整的包就停止。
2、php擴展模塊帶有的socket功能。
php4.x 有這么一個模塊extension=php_sockets.dll,RHT9上安裝后也有一個extension=php_sockets.so的(這個依稀記得是有的需要確認一下,好久沒有玩linux了)
當打開這個此模塊以后就意味著php擁有了強大的socket功能,包括listen端口,阻塞及非阻塞模式的切換,multi-client 交互式處理等
這個系列的函數列表參看
http://www.php.net/manual/en/ref.sockets.php
看過這個列表覺得是不是非常豐富呢?不過非常遺憾這個模塊還非常年輕還有很多地方不成熟,相關的參考文檔也非常少:(
我也正在研究中,因此暫時不具體討論它,僅給大家一個參考文章
http://www.zend.com/pecl/tutorials/sockets.php
下面舉例說明:
例子1
簡單應用——whois查詢
看一段代碼
CODE:
<?php
$server="whois.verisign-grs.com";//TLD .com whois server
$data = "";
$domain = "abc.com";//serch domain
$fp = fsockopen($server,43);
if ($fp) {
fputs($fp,$domain."\r\n");
while (!feof($fp)) {
$data .= fgets($fp,1000);
}
}
fclose($fp);
echo ln2br($data);
}
[Copy to clipboard]
這個應用因該非常常見了:),不用多廢話了。
下面看看對于ftplist的應用
CODE:
<?php
$server="192.168.1.3";//服務器ip端口用默認的21舉例而已所以不要那么復雜
$data = "";
$fp = fsockopen($server,21);//打開服務器
$data .= fread($fp,1024);//讀取狀態注意用的fread那么是一個可用報文結束為一段讀取。
fwrite($fp,"USER hack\r\n");//登陸信息
$data .= fgets($fp,1024);//讀取是否有此用戶,是否等待密碼
fwrite($fp,"PASS 123456\r\n");//密碼
$data .= fgets($fp,1024);//是否驗證成功
fwrite($fp,"REST 100\r\n");//重置數據流
$data .= fgets($fp,1024);
fwrite($fp,"PWD\r\n");//當前目錄
$data .= fgets($fp,1024);
fwrite($fp,"TYPE A\r\n");//切換傳輸模式為A——ASCII模式
$data .= fgets($fp,1024);
fwrite($fp,"CWD ./123456\r\n");//更換目錄為123456
$data .= fgets($fp,1024);
fwrite($fp,"PASV\r\n");//切換為被動模式
$tstring = fgets($fp,1024);
$data .=$tstring;
$ports=ftp_pasvs($tstring);//獲取服務器分配的端口及ip
fwrite($fp,"LIST -al\r\n");//列表
$sock_data=ftp_data_connection($ports);//連接被動模式下的端口
while (!feof($sock_data)) {//循環獲取數據
$data .= fgets($sock_data, 1024);
}
echo nl2br($data);
function ftp_pasvs($string)//用于獲取被動模式下的相關連接信息
{
$ip_port = preg_replace("/^(.+
\()([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+)(\)?.*\r\n)$/i","\\2",$string);
return $ip_port;
}
function ftp_data_connection($ip_port)//連接服務器數據端口
{
$ip_port=trim($ip_port);
if (!preg_match("/^[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+$/i", $ip_port)) {
return "false";
}
$DATA = explode(",", $ip_port);
$ipaddr = $DATA[0].".".$DATA[1].".".$DATA[2].".".$DATA[3];
$port = $DATA[4]*256 + $DATA[5];
//echo $port."|".$ipaddr;//exit;
$data_connection = @fsockopen($ipaddr, $port);
if (!$data_connection) {
return "false";
}
return $data_connection;
}
?>
[Copy to clipboard]
以上這段代碼作了一下簡單的注釋應該比較清晰,現說明幾點問題。
1、為什么采用fread:因為此函數是以網絡包為截斷的,使用這樣的截斷來獲得信息可以很好的保持兼容性,因為對于ftpclient來講服務器是一個未知的環境(wu-ftp,ser-u,g6ftp...),在這樣的環境下有利于讀取ftp的特征字符串以便以后使用。
2、為什么不用主動模式連接服務器傳輸數據:主動模式必須客戶端指定端口然后進行傳輸,而php自帶的函數并不具備此特性。除非使用擴展庫,這樣程序兼容性就會。。同時由服務器指定端口避免了客戶端尋找合適端口造成的額外負擔,盡管這樣的負擔微乎其微。
與這個代碼類似的應用還有estmp發送郵件,管與此應用我不多說,有興趣的朋友可以到我的blog上看看有相關代碼(www.freeplug.org)冰血老弟不介意我做點小廣告吧哈哈。
POST,GET提交這個話題很老了:)我想不需要我在這里提起了文章很多。
在看下面一個例子前先提及一組函數pack,unpack。
任何一款擁有socket操作能力的語言都有一個專門用于組包的函數,php也不例外當然這組函數的用途不僅僅是組包。
下面簡單的介紹一下:
應用一:
輸入16進制或者2進制流。
CODE:
$src="3B06";
$binvar = pack('H*',$src);
echo $binvar;
[Copy to clipboard]
看看這個程序,相當于下面的程序
CODE:
echo chr(0x3B).chr(0x06);
[Copy to clipboard]
在數據量很小的時候后面的做法,更為簡便。但是大量數據的時候,前一種做法則更為實際工整些,代碼量也很少。
應用二:
網絡數據流拆包
CODE:
$elength = str_len($bin);
extract(unpack("NLEN/Hcontent", $bin));
[Copy to clipboard]
將$bin拆成
一維數組并解開到變量$LEN和$content內。
下面看例子了
這個是利用php實現中國移動cmpp登陸消息的一個縮寫例子
CODE:
<?php
define("LOGINID",0x00000001);//登陸消息
class SMS{
var $host;
var $mtport;
var $moport;
var $connectout;
var $sms_sock;
var $loginuser;
var $loginpass;
var $error_stop=0;
function SMS($host,$user,$pass,$mtport,$moport,$timeout=30,$if_conn=0){
$this->host=$host;
$this->mtport=$mtport;
$this->moport=$moport;
$this->loginuser=$user;
$this->loginpass=$pass;
$if_conn && $this->login();
}
function login(){
$fp=@fsockopen($this->host,$this->mtport,$errno,$errstr,$this->connectout);
if(!$fp){
$this->sms_sock='';
$this->halt("error in login num=$errno, msg=$errstr");
return false;
}else{
//$this->sms_sock=$fp;
$data=pack("Na10a32",LOGINID,$this->loginuser,md5($this->loginpass));//這個地方就是組包了
$data=pack("N",strlen($data)+4).$data;//$data是實際內容前面這個表示整個報文長度
if(fputs($fp,$data) !== false){
//print_r(unpack("N",fread($fp,4)));//此處用于調試時檢測用
//print_r(unpack("N",fread($fp,4)));
//print_r(unpack("c",fread($fp,4)));
//print_r(unpack("N",fread($fp,4)));
@fread($fp,12);
$results=@fread($fp,4);
if($results){
$rs=@unpack("Ncounts",$results);//返回socket結果。
$this->sms_sock=$fp;
}else{
$this->sms_sock='';
$this->halt("error in login: loginid=$this->loginuser loginpass=$this->loginpass");
return false;
}
}else{
$this->sms_sock='';
$this->halt("error in submit logininfo: loginid=$this->loginuser
loginpass=$this->loginpass");
return false;
}
}
return true;
}
function halt($msg){
echo $msg;
flush();
$this->error_stop && exit;
}
}
[Copy to clipboard]
此例應用:主要用于底層的不依賴于http一類協議的通訊使用。在phpclass這個站點上有個smpp模塊更為詳細的演示了此類應用有興趣的朋友可以看看。
末了,不多說。由于時間倉促,有些得不對的地方,望各位用pm通知我,我會及時更正,有什么疑問也可以pm我。
參考資料:
1、rfc 959 (ftp 協議)
2、RFC 3912 (WHOIS Protocol)
3、SMPP(SMPP Short Message Peer to Peer)
4、CMPP (China Mobile Peer to Peer)