在PHP中使用Sockets:从Usenet中获取文件

  作者:Armel Fauveau
  原文地址:http://www.phpbuilder.net/columns/armel20010427.php3
  译者:许立强feifengxlq@gmail.com
Http://www.phpobject.net/blog/
  PHP能够打开远程或者本地服务器的sockets!这里是一个使用socket的简单的例子:连接到Usenet的新闻服务器,与服务器沟通,并从一个精确的新闻分组中下载一些文章。
  使用PHP打开Socket
  使用fsockopen()来打开一个Socket。这个函数在PHP3和PHP4中都存在。函数的原型如下:
  <?php
  intfsockopen
   (string hostname,
   int port[,
   int errno[,
   string errstr[,
   double timeout]]])
  ?>
  对于网络主机,它将建立一个TCP的Socket的连接到主机名的端口上。主机名可以是域名或者IP地址。对于UDP连接,你需要明确指出其协议:udp://hostname。对于unix主机,主机名将在socket的路径中使用,在这个例子中端口必须设置成0。可选项timeout可以用来设置连接超时的秒数。
  关于fsockopen()的更多信息可以访问http://www.php.net/manual/function.fsockopen.php
  网络新闻传输协议(NNTP)
  访问一个usenet新闻服务器需要用到一个特别的协议,称作NNTP,即网络新闻传输协议标准。这个协议的详细资料在RFC977中,你可以在http://www.w3.org/Protocols/rfc977/rfc977.html中查看到。这个文档详细的描述了如何使用不同的命令来连接并且和NNTP服务器对话。
  连接服务器
  连接到NNTP服务器需要知道服务器的主机名(或者IP地址)和它将要监听的端口。另外建议你加上一个超时的时间,这样连接失败的时候就不会“冻结”程序。
  <?php
  $cfgServer ="your.news.host";
  $cfgPort =119;
  $cfgTimeOut =10;
  // open asocket
  if(!$cfgTimeOut)
   // without timeout
   $usenet_handle=fsockopen($cfgServer,$cfgPort);
  else
   // with timeout
   $usenet_handle=fsockopen($cfgServer,$cfgPort, &$errno, &$errstr,$cfgTimeOut);
  if(!$usenet_handle) {
   echo"Connexionfailed\n";
   exit();
  }
  else {
   echo"Connected\n";
   $tmp=fgets($usenet_handle,1024);
  }
  ?>
  与服务器交互
  现在我们已经连接上服务器了,而且能够通过先前打开的socket连接与服务器进行交互。让我们对服务器说“我们要从某一新闻分组中获取到最新的10篇文章”。RFC977定义了如何选择正确的新闻分组的命令,如下:
  GROUPggg
  必需的参数ggg是你将要选择的新闻分组的名字,比如net.news。使用list命令你可以获取到一组有效的新闻列表。成功选择响应会返回组中首尾两篇新闻的新闻号以及对存档新闻号估计。
  比如
  chrome:~$ telnetmy.news.host 119
  Trying aa.bb.cc.dd...
  Connected tomy.news.host.
  Escape character is'^]'.
  200 my.news.hostInterNetNews NNRP server INN 2.2.2 13-Dec-1999 ready (posting ok).
  GROUP alt.test
  211 232 222996 223235alt.test
  quit
  205 .
  在接受到命令“GROUP alt.test”,新闻服务器返回了“211232 222996 223235 alt.test”。其中211是RFC标识码(简单的解释说命令已经成功的执行—查看RFC你可以获取更加详细的资料),返回信息说明其中有232篇文章,其中最旧的新闻的索引号是222996,而最新的新闻索引号是223235。现在让我们计算下:222996+232并不等于232235。这丢失的文章或者从这服务器移除出去了,或者被他的作者取消了(是的,这是可能的,也是很容易实现的),或者是删除了。
  小心起见,在选择新闻分组之前,服务器可能需要认证,当然这是由服务器是否公开或者私有来决定的。一般是允许任何人获取新闻,但发表新闻需要通过认证。
  <?php
  //$cfgUser = "xxxxxx";
  //$cfgPasswd = "yyyyyy";
  $cfgNewsGroup ="alt.php";
  // identification required on private server
  if($cfgUser) {
   fputs($usenet_handle,"AUTHINFO USER".$cfgUser."\n");
   $tmp=fgets($usenet_handle,1024);
   fputs($usenet_handle,"AUTHINFOPASS".$cfgPasswd."\n");
   $tmp=fgets($usenet_handle,1024);
   // check error
   if($tmp!="281Ok\r\n") {
   echo"502Authentication error\n";
   exit();
   }
  }
  // select newsgroup
  fputs($usenet_handle,"GROUP ".$cfgNewsGroup."\n");
  $tmp=fgets($usenet_handle,1024);
  if($tmp=="480 Authentication required for command\r\n") {
   echo"$tmp\n";
   exit();
  }
  $info=split(" ",$tmp);
  $first=$info[2];
  $last=$info[3];
  print"First : $first\n";
  print"Last : $last\n";
  ?>
  获取一些文章
  现在我们已经有最新文章的A索引号,那就能很容易的获取最新的十篇文章。RFC977指出使用ARTICLE命令可以和文章的索引号或者消息的ID一起使用。为了小心起见,在这里,文章的索引号和消息ID是不同的,因为每个新闻服务器定义不同,所以在不同的新闻服务器上相同文章的索引号都会不一样的,但是消息ID好是唯一的(包含在文章的头部中)
  <?php
  $cfgLimit =10;
  // upload last articles
  $boucle=$last-$cfgLimit;
  while ($boucle<=$last) {
   set_time_limit(0);
   fputs($usenet_handle,"ARTICLE$boucle\n");
   $article="";
   $tmp=fgets($usenet_handle,4096);
   if(substr($tmp,0,3) !="220") {
   echo"+----------------------+\n";
   echo"Error onarticle $boucle\n";
   echo"+----------------------+\n";
   }
   else {
   while($tmp!=".\r\n") {
   $tmp=fgets($usenet_handle,4096);
   $article=$article.$tmp;
   }
   echo"+----------------------+\n";
   echo"Article$boucle\n";
   echo"+----------------------+\n";
   echo"$article\n";
   }
   $boucle++;
  }
  ?>
  我们仅仅从这个服务器的这个分组上获取了十条最新的新闻。你也可以使用HEAD命令来至获取文章的头部信息,或者使用BODY命令来获取新闻的正文。
  关闭连接
  使用fclose()函数你就可以结束与NNTP服务器之间的会话,当然你可以些一个新的文件,如下:
  <?php
  // close connexion
  fclose($usenet_handle);
  ?>
  更多关于fclose()的信息,请看:http://www.php.net/manual/function.fclose.php
  结论
  本文中,我们只说明了在确定的情况下如何打开、使用和关闭一个socket连接:连接上一个NNTP服务器然后从新闻分组中取回一些文章。使用POST命令在NNTP服务器上发表一篇文章并不复杂多少。
  因此,下一步就是编写一个新闻客户端(并去掉一些Netscape),它需要能很容易的保存文章,并使用一些搜索引擎(比如htgid,http://www.htdig.org/)来索引这些文章,而且要有一个WEB应用程序能进行新闻分组下的关键字搜索。这里有一个例子,你可以访问http://www.phpindex.com/ng/去下载。