一直以来很少看到有多少人使用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)