PHP的FTP操作函数大全

        ftp_connect: 打开 FTP 链接。
  ftp_login: 登入 FTP 服务器。
  ftp_pwd: 取得目前所在路径。
  ftp_cdup: 回上层目录。
  ftp_chdir: 改变路径。
  ftp_mkdir: 建新目录。
  ftp_rmdir: 删除目录。
  ftp_nlist: 列出指定目录中所有文件。
  ftp_rawlist: 详细列出指定目录中所有文件。
  ftp_systype: 显示服务器系统。
  ftp_pasv: 切换主被动传输模式。
  ftp_get: 下载文件。
  ftp_fget: 下载文件,并存在已开的档中。
  ftp_put: 上传文件。
  ftp_fput: 上传已打开文件。
  ftp_size: 获得指定文件的大小。
  ftp_mdtm: 获得指定文件的最后修改时间。
  ftp_rename: 将文件改名。
  ftp_delete: 将文件删除。
  ftp_quit: 关闭 FTP 连接。

  ftp_connect 打开 FTP 链接。
  语法: int ftp_connect(string host, int [port]);
  返回值: 整数
  函数种类: 网络系统
  内容说明: 本函数可打开 FTP 服务器的链接。参数 host 为 FTP 服务器的网址。参数 port 通常省略,若 FTP 服务器的埠号 (port) 不是 21 时才需要加本参数。若无错误则返回连接代码
,失败则返回 false 值。
  参考: ftp_quit()

  ftp_login 登入 FTP 服务器。
  语法: boolean ftp_login(int ftp_stream, string username, string password);
  返回值: 布尔值
  函数种类: 网络系统
内 容说明: 本函数可登入已链接的 FTP 服务器。参数 ftp_stream 为 FTP 的连接代码。参数 username 及 password 分别为服务器的使用者帐号及密码,通常 anonymous 为公开的使用帐号,密码则为 Email。成功则返回 true 值。

  ftp_pwd 取得目前所在路径。
  语法: string ftp_pwd(int ftp_stream);
  返回值: 字符串
  函数种类: 网络系统
  内容说明: 本函数用来取得目前在 FTP 服务器中的路径。参数 ftp_stream 为 FTP 的连接代码。若有错误则返回 NULL 值。

  ftp_cdup 回上层目录。
  语法: boolean ftp_cdup(int ftp_stream);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来回到上层目录,也就是目前目录的父目录。参数 ftp_stream 为 FTP 的连接代码。成功则返回 true 值。

  ftp_chdir 改变路径。
  语法: boolean ftp_chdir(int ftp_stream, string directory);
  返回值: 布尔值
  函数种类: 网络系统
内容说明: 本函数用来改变路径。参数 ftp_stream 为 FTP 的连接代码。参数 directory 为欲前往的目录。成功则返回 true 值,失败则返回 false 值。

  ftp_mkdir 建新目录。
  语法: string ftp_mkdir(int ftp_stream, string directory);
  返回值: 字符串
  函数种类: 网络系统
  内容说明: 本函数用来建立新的目录。参数 ftp_stream 为 FTP 的连接代码。参数 directory 为欲建立的新目录。成功则返回已建立的目录名,失败则返回 false 值。

  ftp_rmdir 删除目录。
  语法: boolean ftp_chdir(int ftp_stream, string directory);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来删除空目录。参数 ftp_stream 为 FTP 的连接代码。参数 directory 为欲删除的目录。成功则返回 true 值,失败则返回 false 值。

  ftp_nlist 列出指定目录中所有文件。
  语法: array ftp_nlist(int ftp_stream, string directory);
  返回值: 数组
  函数种类: 网络系统
  内容说明: 本函数用来列出指定路径中的所有文件名称。参数 ftp_stream 为 FTP 的连接代码。参数 directory 为指定的目录。成功则返回文件名称的数组,失败则返回 false 值。

  ftp_rawlist 详细列出指定目录中所有文件。
  语法: array ftp_rawlist(int ftp_stream, string directory);
  返回值: 数组
  函数种类: 网络系统
  内容说明: 本函数可详细列出指定路径中的所有文件名称。参数 ftp_stream 为 FTP 的连接代码。参数 directory 为指定的目录。成功则返回文件名称的数组,失败则返回 false 值。

  ftp_systype 显示服务器系统。
  语法: string ftp_systype(int ftp_stream);
  返回值: 字符串
  函数种类: 网络系统
  内容说明: 本函数可显示远端 FTP 服务器的系统,也就等于对 FTP 服务器下 system 或 syst 指令。参数 ftp_stream 为 FTP 的连接代码。成功则返回字符串,如: "215 UNIX Type: L8",失败则返回 false 值。

  ftp_pasv 切换主被动传输模式。
  语法: boolean ftp_systype(int ftp_stream);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数可以切换成主动传输或者被动传输模式,也就等于对 FTP 服务器下 passive 或 pass 指令。参数 ftp_stream 为 FTP 的连接代码。成功则返回 true 值,失败则返回 false 值。

  ftp_get 下载文件。
  语法: boolean ftp_get(int ftp_stream, string local_file, string remote_file, int mode);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来下载指定的文件。参数 ftp_stream 为 FTP 的连接代码。参数 local_file 为欲存成本地端的文件名。参数 remote_file 为欲下载的文件名。参数 mode 的值有 FTP_ASCII 及 FTP_BINARY 二种,分别表示文字档宁或者是二进位文件。成功则返回 true 值,失败则返回 false 值。

  ftp_fget 下载文件,并存在已开的文件中。
  语法: boolean ftp_fget(int ftp_stream, int fp, string remote_file, int mode);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来下载指定的文件。参数 ftp_stream 为 FTP 的连接代码。参数 fp 为本地端的已开文件的文件指针。参数 remote_file 为欲下载的文件名。参数 mode 的值有 FTP_ASCII 及 FTP_BINARY 二种,分别表示文字档宁或者是二进位文件。成功则返回 true 值,失败则返回 false 值。

  ftp_put 上传文件。
  语法: boolean ftp_put(int ftp_stream, string remote_file, string local_file, int mode);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来上传指定的文件。参数 ftp_stream 为 FTP 的连接代码。参数 remote_file 为欲存在远端的文件名。参数 local_file 为欲上传文件的文件名。参数 mode 的值有 FTP_ASCII 及 FTP_BINARY 二种,分别表示文字档宁或者是二进位文件。成功则返回 true 值,失败则返回 false 值。

  ftp_fput 上传已打开文件。
  语法: boolean ftp_fput(int ftp_stream, string remote_file, int fp, int mode);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来上传指定的文件。参数 ftp_stream 为 FTP 的连接代码。参数 remote_file 为欲存在远端的文件名。参数 fp 为欲上传的已开文件文件指针。参数 mode 的值有 FTP_ASCII 及 FTP_BINARY 二种,分别表示文字档宁或者是二进位文件。成功则返回 true 值,失败则返回 false 值。

  ftp_size 获得指定文件的大小。
  语法: int ftp_size(int ftp_stream, string remote_file);
  返回值: 整数
  函数种类: 网络系统
  内容说明: 本函数用来获取 FTP 服务器上指定文件的大小。参数 ftp_stream 为 FTP 的连接代码。参数 remote_file 为欲获取大小文件名。返回值为文件大小,失败则返回 -1 值。

  ftp_mdtm 获得指定文件的最后修改时间。
  语法: int ftp_mdtm(int ftp_stream, string remote_file);
  返回值: 整数
  函数种类: 网络系统
  内容说明: 本函数用来获取 FTP 服务器上指定文件的最后修改时间。参数 ftp_stream 为 FTP 的连接代码。参数 remote_file 为欲获取修改时间的文件名。返回值为 UNIX 的时间格式 (timestamp),失败则返回 -1 值。

  ftp_rename 将文件改名。
  语法: boolean ftp_rename(int ftp_stream, string from, string to);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数可将远端 FTP 服务器的文件改名字,值的 注意的是权限不符时无法改动。参数 ftp_stream 为 FTP 的连接代码。参数 from 为原来的文件名。参数 to 为欲改的新文件名。成功则返回 true 值,失败则返回 false 值。

  ftp_delete 将文件删除。
  语法: boolean ftp_delete(int ftp_stream, string remote_file);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数可将远端 FTP 服务器的文件删除,若是权限不符则无法删除。参数 ftp_stream 为 FTP 的连接代码。参数 remote_file 为欲删除的文件名。成功则返回 true 值,失败则返回 false 值。

  ftp_quit 关闭 FTP 连接。
  语法: boolean ftp_quit(int ftp_stream);
  返回值: 布尔值
  函数种类: 网络系统
  内容说明: 本函数用来将远端 FTP 服务器连接关闭。参数 ftp_stream 为 FTP 的连接代码。成功则返回 true 值,失败则返回 false 值。

服务器推技术简介及php实现服务器推技术的聊天室

IBM:Comet:基于 HTTP 长连接的“服务器推”技术

DEMO1:

  首先是首页,包含一个文本输入和一个显示聊天内容的iframe,还有一个隐藏iframe用来提交form表单:

<?php

//chat.php

header('cache-control: private');

header('Content-Type: text/html; charset=utf-8');

?>

<html>

<script type="text/javascript">

function submitChat(obj) {

obj.submit();

document.getElementsByName('content')[0].value = '';

}

</script>

<iframe src="./chat_content.php" height="300" width="100%"></iframe>

<iframe name="say" height="0" width="0"></iframe>

<form method="POST" target="say" action="./say.php" onsubmit="submitChat(this)">

<input type="text" size="30" name="content" /> <input type="button" value="say"onclick="submitChat(this.form)" />

</form>

</html>

  另外一个就是保存用户提交的聊天内容了,我简单的写一下文本,而且没有做什么锁定,这个只是简易版本:

<?php

$content = trim($_POST['content']);

if ($content) {

$fp = fopen('./chat.txt', 'a');

fwrite($fp, $content . "\n");

fclose($fp);

clearstatcache();

}

?>

  接下来看主要的HTTP长连接部分,也就是chat_content.php文件:

<?php

header('cache-control: private');

header('Content-Type: text/html; charset=utf-8');

 

//测试设置30秒超时,一般会设置比较长时间。

set_time_limit(30);

 

//这一行是为了搞定IE这个BT

echo str_repeat(' ', 256);

 

ob_flush();

flush();

$fp = new SplFileObject('./chat.txt', 'r+');

$line = 0;

$totalLine = 0;

while (!$fp->eof()) {

$fp->current();

$totalLine++;

$fp->next();

}

$fp->seek($totalLine);

$i = $totalLine - 1;

while (true) {

if (!$fp->eof()) {

if ($content = trim($fp->current())) {

echo '<div>';

echo htmlspecialchars($content);

echo "</div>";

flush();

$fp->next();

$i++;

}

} else {

$fp->seek($i - 1);

$fp->next();

}

 

{

//这里可以添加心跳检测后退出循环

}

usleep(1000);

}

?>

  06. 设置一个超时时间,由于要保持HTTP长连接,这个时间肯定要比较长,可能要几个小时吧,上面提到的文章里也有说明,这种HTTP长连接只能打开两个,由于浏览器的限制。另外其实即使你设置了一个永不超时,其实上服务器部分(如Apache)的配置文件也可能对HTTP请求设置了最长等待时间,所以也可能效果会不是你想的,一般默认可能都是15分钟超时。如果有兴趣可以自己尝试修改。

  09. 这里输出了一段空白,主要是手册上已经说明了,IE浏览器在前面256个字符是不会直接输出的,所以我们先随便输出些空白,以便让后面的内容输出来,可能其他浏览器也有其他浏览器的设置,具体可以查看PHP手册的frush函数的说明。接下去11、12行就是强制把这些空白符丢给浏览器输出。

  13. ~ 20. 这里主要是为了计算文件行数,以便从这一行后面开始读内容。

  接下去的while循环就是一个死循环了,就是循环输出文件内容,每次判断是否到达文件末尾,如果有用户写入文件,则当前检测肯定不是文件末尾,就将该行读取出来输出,否则将指针往前移动一行,继续循环,每次等待1000微秒,

  39. 如果一直保持长连接,那么即使客户端断开,服务端也不一定能知道客户端已经断开,所以这里可能还需要做一些心跳记录,比如每个用户保持一个心跳flag,每格几秒更新一下最后心跳时间,当检测最后时间很久没更新后,推出这个死循环,关闭这个HTTP连接。

 

 

DEMO2:

 

传统的B/S结构的应用程序,都是采用\"客户端拉\"结束来实现客户端和服务器端的数据交换。 

本文将通过结合Ticks,来实现一个服务器推的PHP聊天室简单构想。 

 

PHPer,尤其是用过set_cookie, header的,一定见过这样的提示信息:\"Warning: Cannot modify header information - headers already sent by.....\", 这是因为通过HTTP协议通信,数据包会包含俩个部分,一个是Header,一个是data。一般来说,都是先Header部分,在Heaer部分指明了Data部分的长度,然后使用\\r\\n\\r\\n来表示header部分结束,接下来是Data部分。 

 

当我们有任何输出的时候,Header部分就发送了,这个时候,你再想header函数来改变一些Header部分的域信息,就会得到上面的提示信息。 

    

一个简单的办法就是使用output_buffering。让它来缓存服务器的输出,不要太早将Header部分发给客户端。 

 

那么,如果不使用output_buffering,是不是就可以实现,每当服务器有输出,就立即发送给客户端呢? 

 

做个如下试验://设置php.ini中output_buffering=0 或者使用ob_end_flush()关闭缓存 

 

set_time_limit(0); 

for($i=0;$i<10;$i++){ 

  echo \"Now Index is :\". $i; 

  sleep(1); 

  结果我们发现,还是要等到脚本全部执行完以后,才能一次看到所有的结果。。 

  为什么呢? 

  这是因为我们只是解决了缓存问题,但是还有一个缓冲问题,PHP会缓冲程序的输出。所以,这个时候,我们还需要调用,flush(), 来强制使得PHP将所有的程序输出发送给客户端。//设置php.ini中output_buffering=0 

ob_end_flush();//关闭缓存 

 

set_time_limit(0); 

for($i=0;$i<10;$i++){ 

  echo \"Now Index is :\". $i; 

  flush(); 

  sleep(1); 

    现在是不是看到了,不断有服务器的数据显示出来? 

 

    有几个概念之间的关系,我这里补充以下: 

    在代码中使用ob_start(), 就相当于在php.ini中使用output_buffering=on一样,使用服务器缓存。 

    在代码中使用ob_end_flush() 就相当于在php.ini中使用output_buffering = false一样,关闭服务器缓存。 

     

     基于前面的讨论,我们就有可能使用Ticks来实现,一个无刷新,无ajax的聊天室: 页面中包含俩个iframe,一个是不断获取聊天室的聊天内容,一个包含用户发表聊天内容的form. 这样,在第一个frame的脚本中: 

ob_end_clear();//关闭缓存 

set_time_limit(0); 

ob_implicit_flush(); //这个语句将强制每当有输出就自动刷新,相当于在每个echo后,调用flush() 

$new_mesg = NULL; 

register_tick_function(\"getNewMesg\"); 

declare(ticks=1){ 

  while(1){ 

     if(!is_null($new_mesg)){ 

          foreach($new_mesg as $msg){ 

                echo $msg; 

          } 

          $new_mesg = null; 

     }      

  } 

 

function getNewMesg(){ 

//通过查询数据库,或者共享内存,来获取现在的聊天室大厅的内容。 

//返回一个数组,包含所有的新的聊天内容 

 

 这样就实现了一个简单的使用服务器推技术的聊天室的框架。 

 当然,关于实时输出,还有一些其他的限制,比如在PHP5手册中讲到的: 

个别web服务器程序,特别是Win32下的web服务器程序,在发送结果到浏览器之前,仍然会缓存脚本的输出,直到程序结束为止。 

 

有些Apache的模块,比如mod_gzip,可能自己进行输出缓存,这将导致flush()函数产生的结果不会立即被发送到客户端浏览器。 [Page]

 

甚至浏览器也会在显示之前,缓存接收到的内容。例如 Netscape 浏览器会在接受到换行或 html 标记的开头之前缓存内容,并且在接受到 </table> 标记之前,不会显示出整个表格。 

 

一些版本的 Microsoft Internet Explorer 只有当接受到的256个字节以后才开始显示该页面,所以必须发送一些额外的空格来让这些浏览器显示页面内容。 

 

 接下来,我贴一个很有趣的代码,有兴趣的同学,可以试试: 

<?php

header('Content-type: multipart/x-mixed-replace;boundary=endofsection'); 

print "endofsection"; 

for( $i = 0; $i <10;$i ++ ) 

        sleep(1); 

        print "Content-type: text/plain "; 

        print "Part $i "; 

        print "--endofsection "; 

        ob_flush(); //强制将缓存区的内容输出 

        flush(); //强制将缓冲区的内容发送给客户端 

  } 

print "Content-type: text plain "; 

print "The end "; 

print "--endofsection-- ";

 

?>

 

  使用firefox打开,看看你看到了什么。 

  这个例子,使用了ob_flush(), 这样可以在代码中控制缓存区内容的输出时机,更加灵活一些。

Windows 服务器配置 RamDisk

 

给 Windows 服务器增加 RamDisk以前在 IIS 6 的 PHP 最佳配置一文中提到过 RamDisk,后来在那篇文章的留言里,就看到有人问关于 RamDisk 的事。正好那几天转移服务器,需要重新配置这个东西,发现这个东西确实挺难找的,所以在这里写下来,一来可以方便自己以后的工作,二来可以方便跟我有相同需求的朋友们。

 

网上能找到的 RamDisk 程序大都不能支持 Windows 2003,而且许多都是收费软件。原来我用的也是一个试用版的软件,虽然没有功能限制和时间限制,但是总会在随机的某个时间弹出提示框,让人很不爽,而且还不是很稳定。于是这次找了一个免费的却更好的 RamDisk 软件。这个支持 Windows 2000 以上的系统,包括 Windows 2003。

 

这里主要以 IIS 为例来讲。

 

首先用 lyh728 做到 RamDisk 的 GUI 前端来安装 RamDisk 驱动。直接双击 ramdisk.exe 然后点击 install ramdisk 按钮就可以了。然后你可以调整盘符和磁盘大小。RamDisk 的盘符默认是 R:,因为我的服务器只有一个软驱 A:,所以我把 RamDisk 的盘符设为了 B:,主要是为了保证这个盘符不会变。RamDisk 上一般放的都是临时性质的文件,比如 IE 的缓存、IIS 的压缩页面缓存、临时文件夹、PHP 的 Sessions 文件等。这些内容在服务器重启以后就没有了。我给我的服务器分了 256M 的内存作 RamDisk,实际上我发现用不了这么多,目前也只占了 20 多兆的空间而已,所以如果你的内存本来就不是很多的话,分 32M 作 RamDisk 给服务器我觉得基本上就足够了,不放心的话,也可以多给它一点空间,比如 64M 或者 128M。但是默认的 RamDisk 是格式化成 FAT 格式的,并且已经建好了一个 TEMP 文件夹。你会发现这个文件夹在服务器重启后,依然存在。你甚至会发现,你即使将 RamDisk 重新格式化为 NTFS 格式,再重启后,仍然会变为 FAT 格式,并且还有那个 TEMP 文件夹。原因在于默认的安装已经把这个配置写到注册表里了。如果想修改的话,需要用里面的 rdutil 这个工具。

 

首先安装完 RamDisk,并调整好盘符和大小后,什么也别做。尤其是不要急着把 IE 的缓存目录改到 RamDisk 上,否则 RamDisk 会被锁定,以至于不能在被修改保存。

 

然后格式化 RamDisk

 

FORMAT /FS:NTFS /Q /V:RamDisk /A:512 B:

其中 /FS: 后面指定的是文件系统,/A: 后面是一个单元块的大小,因为 RamDisk 上保存的大都是小文件,因此指定 512 字节比较合适。B: 是盘符。

 

接下来把 NTFS 的日志大小设到最小(2M)

 

CHKDSK /L:2048 R:

否则,会因为太大而无法压缩到注册表中。

 

在接下来你就可以在上面建文件夹了,比如 Temp、Sessions、Upload Temp Files、IIS Temporary Compressed Files 等。目录不要建太多,够用就行了。比如 Temp 是用来保存系统临时文件的,Sessions 可以保存 PHP 的 Session 文件,Upload Temp Files 可以保存 PHP 上传的临时文件,IIS Temporary Compressed Files 可以保存 IIS 的临时压缩文件。当然这些目录你还要给予足够的权限,比如需要让 IIS(包括用 IIS 发布的 PHP 程序)访问的目录,就需要给 IIS_WPG 用户组或者 IUSR_HOSTNAME 用户足够的权限。注意,这里也不要给 IE 建立缓存目录,即使你想让 IE 缓存也使用 RamDisk,因为当你设置 IE 的缓存到 RamDisk 的时候,IE 会自动建立这个目录的。

 

好了,做完这步后,先不要急着把这些目录用起来,先把这个文件系统和目录结构保存到注册表:

 

rdutil B: registry

好了,如果你看到保存成功的提示,就大功告成了。重启一下试试看,你会发现重新启动后,你的 RamDisk 将会保持你设置的文件系统格式,以及目录和相应的权限。

 

现在你就可以把系统的 Temp 目录、IIS 的压缩文件缓存、IE 缓存、PHP Session 文件目录、PHP 临时上传目录统统设置到 RamDisk 上了。 

PHP 实现多服务器共享 SESSION 数据

 

PHP 实现多服务器共享 SESSION 数据

肖理达 (KrazyNio AT hotmail.com), 2005.09.13, 转载请注明出处



一、问题起源

稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名、密码在整个网站的各个模块中都是可以登录使用的。各个服务器共享用户数据是比较容易实现的,只需要在后端放个数据库服务器,各个服务器通过统一接口对用户数据进行访问即可。但还存在一个问题,就是用户在这个服务器登录之后,进入另一个服务器的别的模块时,仍然需要重新登录,这就是一次登录,全部通行的问题,映射到技术上,其实就是各个服务器之间如何实现共享 SESSION 数据的问题。

二、PHP SESSION 的工作原理

在解决问题之前,先来了解一下 PHP SESSION 的工作原理。在客户端(如浏览器)登录网站时,被访问的 PHP 页面可以使用 session_start() 打开 SESSION,这样就会产生客户端的唯一标识 SESSION ID(此 ID 可通过函数 session_id() 获取/设置)。SESSION ID 可以通过两种方式保留在客户端,使得请求不同的页面时,PHP 程序可以获知客户端的 SESSION ID;一种是将 SESSION ID 自动加入到 GET 的 URL 中,或者 POST 的表单中,默认情况下,变量名为 PHPSESSID;另一种是通过 COOKIE,将 SESSION ID 保存在 COOKIE 中,默认情况下,这个 COOKIE 的名字为 PHPSESSID。这里我们主要以 COOKIE 方式进行说明,因为应用比较广泛。 
那么 SESSION 的数据保存在哪里呢?当然是在服务器端,但不是保存在内存中,而是保存在文件或数据库中。默认情况下,php.ini 中设置的 SESSION 保存方式是 files(session.save_handler = files),即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的目录由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 SESSION ID,如:sess_c72665af28a8b14c0fe11afe3b59b51b。文件中的数据即是序列化之后的 SESSION 数据了。如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:session.save_path="N;/save_path",N 为分级的级数,save_path 为开始目录。当写入 SESSION 数据的时候,PHP 会获取到客户端的 SESSION_ID,然后根据这个 SESSION ID 到指定的 SESSION 文件保存目录中找到相应的 SESSION 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 SESSION 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 SESSION 变量。

三、多服务器共享 SESSION 的主要障碍及解决办法

通过了解 SESSION 的工作原理,我们可以发现,在默认情况下,各个服务器会各自分别对同一个客户端产生 SESSION ID,如对于同一个用户浏览器,A 服务器产生的 SESSION ID 是 30de1e9de3192ba6ce2992d27a1b6a0a,而 B 服务器生成的则是 c72665af28a8b14c0fe11afe3b59b51b。另外,PHP 的 SESSION 数据都是分别保存在本服务器的文件系统中。如下图所示:

image 

确定了问题所在之后,就可以着手进行解决了。想要共享 SESSION 数据,那就必须实现两个目标:一个是各个服务器对同一个客户端产生的 SESSION ID 必须相同,并且可通过同一个 COOKIE 进行传递,也就是说各个服务器必须可以读取同一个名为 PHPSESSID 的 COOKIE;另一个是 SESSION 数据的存储方式/位置必须保证各个服务器都能够访问到。简单地说就是多服务器共享客户端的 SESSION ID,同时还必须共享服务器端的 SESSION 数据。

第一个目标的实现其实很简单,只需要对 COOKIE 的域(domain)进行特殊地设置即可,默认情况下,COOKIE 的域是当前服务器的域名/IP 地址,而域不同的话,各个服务器所设置的 COOKIE 是不能相互访问的,如 www.aaa.com 的服务器是不能读写 www.bbb.com 服务器设置的 COOKIE 的。这里我们所说的同一网站的服务器有其特殊性,那就是他们同属于同一个一级域,如:aaa.infor96.com 和 www.infor96.com 都属于域 .infor96.com,那么我们就可以设置 COOKIE 的域为 .infor96.com,这样 aaa.infor96.com、www.infor96.com 等等都可以访问此 COOKIE。PHP 代码中的设置方法如下:

ini_set('session.cookie_domain', '.infor96.com');

这样各个服务器共享同一客户端 SESSION ID 的目的就达到了。

第二个目标的实现可以使用文件共享方式,如 NFS 方式,但设置、操作上有些复杂。我们可以参考先前所说的统一用户系统的方式,即使用数据库来保存 SESSION 数据,这样各个服务器就可以方便地访问同一个数据源,获取相同的 SESSION 数据了。

解决办法如下图所示:

image 

四、代码实现

首先创建数据表,MySQL 的 SQL 语句如下:

   CREATE TABLE `sess` (
     `sesskey` varchar(32) NOT NULL default '',
      `expiry` bigint(20) NOT NULL default '0',
      `data` longtext NOT NULL,
      PRIMARY KEY  (`sesskey`),
      KEY `expiry` (`expiry`)
    ) TYPE=MyISAM

sesskey 为 SESSION ID,expiry 为 SESSION 过期时间,data 用于保存 SESSION 数据。

默认情况下 SESSION 数据是以文件方式保存,想要使用数据库方式保存,就必须重新定义 SESSION 各个操作的处理函数。PHP 提供了session_set_save_handle() 函数,可以用此函数自定义 SESSION 的处理过程,当然首先要先将 session.save_handler 改成 user,可在 PHP 中进行设置:

session_module_name('user');


接下来着重讲一下 session_set_save_handle() 函数,此函数有六个参数:

session_set_save_handler ( string open, string close, string read, string write, string destroy, string gc )

各个参数为各项操作的函数名,这些操作依次是:打开、关闭、读取、写入、销毁、垃圾回收。PHP 手册中有详细的例子,在这里我们使用 OO 的方式来实现这些操作,详细代码如下:

<?php define('MY_SESS_TIME', 3600);   //SESSION 生存时长 //类定义 class My_Sess {     function init()     {         $domain = '.infor96.com';         //不使用 GET/POST 变量方式         ini_set('session.use_trans_sid',    0);         //设置垃圾回收最大生存时间         ini_set('session.gc_maxlifetime',   MY_SESS_TIME);          //使用 COOKIE 保存 SESSION ID 的方式         ini_set('session.use_cookies',      1);         ini_set('session.cookie_path',      '/');         //多主机共享保存 SESSION ID 的 COOKIE         ini_set('session.cookie_domain',    $domain);          //将 session.save_handler 设置为 user,而不是默认的 files         session_module_name('user');         //定义 SESSION 各项操作所对应的方法名:         session_set_save_handler(             array('My_Sess', 'open'),   //对应于静态方法 My_Sess::open(),下同。             array('My_Sess', 'close'),             array('My_Sess', 'read'),             array('My_Sess', 'write'),             array('My_Sess', 'destroy'),             array('My_Sess', 'gc')         );     }   //end function      function open($save_path, $session_name) {         return true;     }   //end function      function close() {         global $MY_SESS_CONN;          if ($MY_SESS_CONN) {    //关闭数据库连接             $MY_SESS_CONN->Close();         }         return true;     }   //end function      function read($sesskey) {         global $MY_SESS_CONN;          $sql = 'SELECT data FROM sess WHERE sesskey=' . $MY_SESS_CONN->qstr($sesskey) . ' AND expiry>=' . time();         $rs =& $MY_SESS_CONN->Execute($sql);         if ($rs) {             if ($rs->EOF) {                 return '';             } else {    //读取到对应于 SESSION ID 的 SESSION 数据                 $v = $rs->fields[0];                 $rs->Close();                 return $v;             }   //end if         }   //end if         return '';     }   //end function      function write($sesskey, $data) {         global $MY_SESS_CONN;                  $qkey = $MY_SESS_CONN->qstr($sesskey);         $expiry = time() + My_SESS_TIME;    //设置过期时间                  //写入 SESSION         $arr = array(             'sesskey' => $qkey,             'expiry'  => $expiry,             'data'    => $data);         $MY_SESS_CONN->Replace('sess', $arr, 'sesskey', $autoQuote = true);         return true;     }   //end function      function destroy($sesskey) {         global $MY_SESS_CONN;          $sql = 'DELETE FROM sess WHERE sesskey=' . $MY_SESS_CONN->qstr($sesskey);         $rs =& $MY_SESS_CONN->Execute($sql);         return true;     }   //end function      function gc($maxlifetime = null) {         global $MY_SESS_CONN;          $sql = 'DELETE FROM sess WHERE expiry<' . time();         $MY_SESS_CONN->Execute($sql);         //由于经常性的对表 sess 做删除操作,容易产生碎片,         //所以在垃圾回收中对该表进行优化操作。         $sql = 'OPTIMIZE TABLE sess';         $MY_SESS_CONN->Execute($sql);         return true;     }   //end function }   ///:~  //使用 ADOdb 作为数据库抽象层。 require_once('adodb/adodb.inc.php'); //数据库配置项,可放入配置文件中(如:config.inc.php)。 $db_type = 'mysql'; $db_host = '192.168.212.1'; $db_user = 'sess_user'; $db_pass = 'sess_pass'; $db_name = 'sess_db'; //创建数据库连接,这是一个全局变量。 $GLOBALS['MY_SESS_CONN'] =& ADONewConnection($db_type); $GLOBALS['MY_SESS_CONN']->Connect( $db_host, $db_user, $db_pass, $db_name); //初始化 SESSION 设置,必须在 session_start() 之前运行!! My_Sess::init(); ?>


五、遗留问题

如果网站的访问量很大的话,SESSION 的读写会频繁地对数据库进行操作,这样效率就会明显降低。考虑到 SESSION 数据一般不会很大,可以尝试用 C/Java 写个多线程的程序,用 HASH 表保存 SESSION 数据,并通过 socket 通信进行数据读写,这样 SESSION 就保存在内存中,读写速度应该会快很多。另外还可以通过负载均衡来分担服务器负载。不过这些都只是我自己的一些想法和假设,并没有实践过 :( 。。。。。。

多Web服务器之间共享Session的解决方案

 

很多开发中涉及到用户的Session验证很保留的问题,这个问题比较有意思,总结了几种方案,只供参考。


[  问题提出 ]

为了满足足够大的应用,满足更多的客户,于是我们架设了N台Web服务器(N>=2),在多台Web服务器的情况下,我们会涉及到一个问题:用户登陆一台服务器以后,如果在跨越到另一台服务器的时候能够继续使用客户的Session?
(以下描述方案只是针对Linux/Unix + Apache + Mysql + PHP的开发架构,当然,也可以扩展到其他平台。)

 

[  问题解决方案 ]

既然我们的问题已经摆在面前了,那么就要从技术角度去解决问题,给我们的客户更好的体验,总结了几个方案。

1. 写客户端Cookie的方式
当用户登陆成功以后,把网站域名、用户名、密码、token、session有效时间全部采用cookie的形式写入到客户端的cookie里面,如果用户从一台Web服务器跨越到另一台服务器的时候,我们的程序主动去检测客户端的cookie信息,进行判断,然后提供对应的服务,当然,如果cookie过期,或者无效,自然就不让用户继续服务了。当然,这种方法的弊端就不言而喻了,比如客户端禁用了cookie或者cookie被黑客窃取了呢?


2. 服务器之间Session数据同步的方式
假设Web服务器A是所有用户登陆的服务器,那么当用户验证登陆一下,session数据就会写到A服务器里,那么就可以自己写脚本或者守护进程来自动把session数据同步到其他Web服务器,那么当用户跳转到其他服务器的时候,那么session数据是一致的,自然就能够直接进行服务无须再次登陆了。缺点是,可能会速度慢,不稳定,如果是单向同步的话,登陆服务器出现问题,那么其他服务器也无法服务,当然也可以考虑双向同步的问题。


3. 利用NFS共享Session数据的方式
其实这个方案和下面的Mysql方案类似,只是存储方式不一样。大致就是有一台公共的NFS服务器(Network File Server)做共享服务器,所有的Web服务器登陆的时候把session数据写到这台服务器上,那么所有的session数据其实都是保存在这台NFS服务器上的,不论用户访问那太Web服务器,都要来这台服务器获取session数据,那么就能够实现共享session数据了。缺点是依赖性太强,如果NFS服务器down掉了,那么大家都无法工作了,当然,可以考虑多台NFS服务器同步的形式。
(关于NFS的经典文章:http://linux.vbird.org/linux_server/0330nfs.php


4. 利用Mysql数据库共享Session数据的方式
这个方式与NFS的方式类似,也是采用一台Mysql服务器做共享服务器,把所有的session的数据保存到Mysql服务器上,所有Web服务器都来这台Mysql服务器来获取Session数据。缺点也是依赖性太强,Mysql无法工作了影响所有的Web服务器,当然,可以考虑多太Mysql数据库来共享session,使用同步Mysql数据的方式。
(Mysql同步我写过文章:http://blog.csdn.net/heiyeshuwu/archive/2005/10/31/520007.aspx


5. 使用硬件设备
这个算是比较成熟的解决方案了,使用类似BIG-IP的负载设备来实现资源共享,那么就能够又稳定又合理的的共享Session了。目前很多门户网站采用这种方式。缺点很明显了,就是要收费了,硬件设备肯定需要购买成本的,不过对于专业或者大型应用来讲,是比较合理并且值得的。
(关于BIG-IP设备:http://www.f5.com.cn/channel.php?channel=product&type=BIG-IP-%D3%A6%D3%C3%C1%F7%C1%BF%B9%DC%C0%ED&id=36

以上这些只是我的个人愚见,没有经过试验,不保证其准确实在性,只是提供一种想法和参考。

ajax中文乱码完美解决(兼容ie ff)

AJAX的中文乱码可以大概分为两中,第一种是向服务器端发送中文参数时 (xmlhttp.open(“get|post”,url,true)),服务器端接收到的为乱码,这个也是我今天遇到的问题,没做处理之前,在IE里 是正常的,但是在Firefox里面就出现了乱码,我先把接收到参数输出到一个文本里,没有发现什么问题,郁闷了,然后我就把查询语句在输出来观察(我这 里是要从数据库里查出与参数相关的东西),终于发现问题,IE和Firefox输出的参数不一样,虽然汉字上都一样,但是和前后连接上有细小的区别,于是 认定了是编码问题,在网上查找了相关资料,都没能解决问题,但是得到一些启示,因为AJAX发送数据都是采用UTF-8编码的方式发送的,所以要在服务器 端进行编码转换(我这里页面是采用GB2312编码的,如果是采用UTF-8的话应该不会有这步的问题),所以我在服务器端进行了UTF-8转 GB2312,

 

$str=iconv("UTF-8","GB2312",$str);

 

然后测试,在Firefox上顺利解决了问题,以为大公告成了,可是再到IE下测试,发现IE又出现了问题,服务器端接收到的参数没值,这下就郁闷 了,突然看到发送头设置了setRequestHeader("Content-Type","application/x-www-form- urlencoded");,就找到问题所在了,然后就在发送那里进行了参数编码:

 

geturl=encodeURI(geturl);

       geturl=encodeURI(geturl); //两次也可以写成geturl=encodeURI(encodeURI(geturl));

xmlhttp.open("GET",geturl,true);

 

然后再到服务器端进行URL解码:

 

   $str=urldecode($str); //解码

$ str =iconv("UTF-8","GB2312",$ str); //编码转换

 

   注意:解码必须在编码转换前面,不然得不到正确值

 

保存测试,IE和Firefox都能正常了。

 

第二种就是服务器端向客户端输出中文时出现乱码,这类问题网上的答案就比较多了,也都能解决,为了避免各位再去查找,我在这里就COPY下J

 

原因:AJAX在接收responseText或responseXML的值的时候是按照UTF-8的格式来解码的,如果服务器段发送的数据不是UTF-8的格式,那么接收responseText或responseXML的值有可能为乱码。


    解决办法:

 

    在服务器指定发送数据的格式:
    在jsp文件中:
    response.setContentType("text/text;charset=UTF-8");//返回的是txt文本文件
    或是
    response.setContentType("text/xml;charset=UTF-8");//返回的xml文件

     PHP:header('Content-Type:text/html;charset=GB2312');
     ASP:Response.Charset("GB2312")
     JSP:response.setHeader("Charset","GB2312");