標籤:

session和cookie的生命周期?session如何不同伺服器共享?


自己實現一套基於資料庫存儲的會話機制即可.
我是這樣做的:
一個名叫sessid的cookie用於驗證用戶身份,裡面的內容是:
aes(user_id,user_salt)
也就是經過aes加密的cookie里包含了用戶編號和用戶的鹽.
解密後根據user_id查詢資料庫找到用戶對應的鹽,對比cookie中的鹽,一致則通過認證.
然後在資料庫里設置一張session表,欄位包括:
user_id, session, version, flag
其中session欄位存儲JSON編碼的會話數據.
其中version和flag用於實現版本號樂觀鎖,保證會話讀寫的數據完整性.
session表結構和PHP讀寫會話演算法如下:

CREATE TABLE `io_session` (

`user_id` bigint(20) unsigned NOT NULL,

`session` text NOT NULL,

`version` smallint(5) unsigned NOT NULL,

`flag` tinyint(3) NOT NULL,

PRIMARY KEY (`user_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

function app_session_get() {

global $app;

if(!isset($app[user][id])) return false;

$db = app_db();

$table = $app[db_prefix].session;

$sql = "SELECT `user_id`, `session`, `version`, `flag` FROM `{$table}` WHERE `user_id` = ?";

$stmt = $db-&>prepare($sql);

$stmt-&>execute(array($app[user][id]));

$arr = $stmt-&>fetchAll(PDO::FETCH_ASSOC);

if($arr !== array()) {

$app[user][session] = json_decode($arr[0][session], true);

$app[user][session_version] = $arr[0][version];

$app[user][session_flag] = $arr[0][flag];

}

//register_shutdown_function(app_session_set);

}

function app_session_set() {

global $app;

if(!isset($app[user][session])) return false;

$session = json_encode($app[user][session]);

if(mb_strlen($session, UTF-8) &> 20000) return false;

$db = app_db();

$table = $app[db_prefix].session;

switch(true) {

//version 類型 smallint 範圍 0 到 65535

case ($app[user][session_flag] == 1 $app[user][session_version] != 65535):

$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?

WHERE `user_id` = ? AND `version` = ? AND `flag` = 1";

$version_increase = true;

break;

case ($app[user][session_flag] == 1 $app[user][session_version] == 65535):

$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?, `flag` = -1

WHERE `user_id` = ? AND `version` = ? AND `flag` = 1";

$version_increase = false;

break;

case ($app[user][session_flag] == -1 $app[user][session_version] != 0):

$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?

WHERE `user_id` = ? AND `version` = ? AND `flag` = -1";

$version_increase = false;

break;

case ($app[user][session_flag] == -1 $app[user][session_version] == 0):

$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?, `flag` = 1

WHERE `user_id` = ? AND `version` = ? AND `flag` = -1";

$version_increase = true;

break;

}

$stmt = $db-&>prepare($sql);

$stmt-&>execute(array(

$session,

$version_increase ? $app[user][session_version] + 1 : $app[user][session_version] - 1,

$app[user][id],

$app[user][session_version],

));

return ($stmt-&>rowCount() == 0) ? false : true;

}


說實話這種基礎概念的問題沒有什麼討論的意義,與其在知乎提問,不如


cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在伺服器端保持狀態的方案。

生命周期

cookie如果沒有設置過期時間,那麼稱為會話cookie, 生命期為瀏覽器會話期間,關閉瀏覽器窗口,cookie就消失。

如果設置過期時間, 瀏覽器就會把cookie保存到硬碟上,關閉後再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。存儲在硬碟上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對於保存在內存里的cookie,不同的瀏覽器有不同的處理方式 。生命周期為創建至設定的過期時間。

session 則是伺服器使用一種類似於散列表的結構(也可能就是使用散列表)來保存信息。 當程序需要為某個客戶端的請求創建一個session時,伺服器首先檢查這個客戶端的請求里是否已包含了一個session標識(稱為session id),如果已包含則說明以前已經為此客戶端創建過session,伺服器就按照session id把這個session檢索出來使用(檢索不到,會新建一個),如果客戶端請求不包含session id,則為此客戶端創建一個session並且生成一個與此session相關聯的sessionid,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字元串,這個session id將被在本次響應中返回給客戶端保存。保存這個session id的方式可以採用cookie,這樣在交互過程中瀏覽器可以自動的按照規則把這個標識發送給伺服器。一般這個cookie的名字都是類似於SEEESIONID。但cookie可以被人為的禁止,則必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞迴伺服器。

伺服器共享

如上所示,可以看到每個session都會有一個對應的、唯一的session id,通過session id可以取到相應的session值,那麼如何取到呢。首先我們要了解session是保存在什麼地方的。

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信息的保存需要多個伺服器可以同時存取。同時因為伺服器是根據客戶端cookie中的 PHPSESSID 來讀取對應的session信息,那麼就需要不同伺服器對同一客戶端產生相同的cookie

第一個問題,可以通過mysql來保存session信息。主要依靠 session_set_save_handle() 函數,此函數有六個參數: session_set_save_handler ( string open, string close, string read, string write, string destroy, string gc )各個參數為各項操作的函數名,這些操作依次是:打開、關閉、讀取、寫入、銷毀、垃圾回收。PHP 手冊中有詳細的例子,在這裡我們使用 OO 的方式來實現這些操作,詳細代碼如下:

  1. &
  2. session_module_name(user); //設置按用戶設定方式操作session
  3. define(MY_SESS_TIME, 3600); //SESSION 生存時長
  4. //類定義
  5. class My_Sess
  6. {
  7. function init()
  8. {
  9. $domain = .infor96.com;
  10. //不使用 GET/POST 變數方式
  11. ini_set(session.use_trans_sid, 0);
  12. //設置垃圾回收最大生存時間
  13. ini_set(session.gc_maxlifetime, MY_SESS_TIME);
  14. //使用 COOKIE 保存 SESSION ID 的方式
  15. ini_set(session.use_cookies, 1);
  16. ini_set(session.cookie_path, /);
  17. //多主機共享保存 SESSION ID 的 COOKIE
  18. ini_set(session.cookie_domain, $domain);
  19. //將 session.save_handler 設置為 user,而不是默認的 files
  20. session_module_name(user);
  21. //定義 SESSION 各項操作所對應的方法名:
  22. session_set_save_handler(
  23. array(My_Sess, open), //對應於靜態方法 My_Sess::open(),下同。
  24. array(My_Sess, close),
  25. array(My_Sess, read),
  26. array(My_Sess, write),
  27. array(My_Sess, destroy),
  28. array(My_Sess, gc)
  29. );
  30. } //end function
  31. function open($save_path, $session_name) {
  32. return true;
  33. } //end function
  34. function close() {
  35. global $MY_SESS_CONN;
  36. if ($MY_SESS_CONN) { //關閉資料庫連接
  37. $MY_SESS_CONN-&>Close();
  38. }
  39. return true;
  40. } //end function
  41. function read($sesskey) {
  42. global $MY_SESS_CONN;
  43. $sql = SELECT data FROM sess WHERE sesskey= . $MY_SESS_CONN-&>qstr($sesskey) . AND expiry&>= . time();
  44. $rs = $MY_SESS_CONN-&>Execute($sql);
  45. if ($rs) {
  46. if ($rs-&>EOF) {
  47. return ";
  48. } else { //讀取到對應於 SESSION ID 的 SESSION 數據
  49. $v = $rs-&>fields[0];
  50. $rs-&>Close();
  51. return $v;
  52. } //end if
  53. } //end if
  54. return ";
  55. } //end function
  56. function write($sesskey, $data) {
  57. global $MY_SESS_CONN;
  58. $qkey = $MY_SESS_CONN-&>qstr($sesskey);
  59. $expiry = time() + My_SESS_TIME; //設置過期時間
  60. //寫入 SESSION
  61. $arr = array(
  62. sesskey =&> $qkey,
  63. expiry =&> $expiry,
  64. data =&> $data);
  65. $MY_SESS_CONN-&>Replace(sess, $arr, sesskey, $autoQuote = true);
  66. return true;
  67. } //end function
  68. function destroy($sesskey) {
  69. global $MY_SESS_CONN;
  70. $sql = DELETE FROM sess WHERE sesskey= . $MY_SESS_CONN-&>qstr($sesskey);
  71. $rs = $MY_SESS_CONN-&>Execute($sql);
  72. return true;
  73. } //end function
  74. function gc($maxlifetime = null) {
  75. global $MY_SESS_CONN;
  76. $sql = DELETE FROM sess WHERE expiry&< . time();
  77. $MY_SESS_CONN-&>Execute($sql);
  78. //由於經常性的對錶 sess 做刪除操作,容易產生碎片,
  79. //所以在垃圾回收中對該表進行優化操作。
  80. $sql = OPTIMIZE TABLE sess;
  81. $MY_SESS_CONN-&>Execute($sql);
  82. return true;
  83. } //end function
  84. } ///:~
  85. //使用 ADOdb 作為資料庫抽象層。
  86. require_once(adodb/adodb.inc.php);
  87. //資料庫配置項,可放入配置文件中(如:config.inc.php)。
  88. $db_type = mysql;
  89. $db_host = 192.168.212.1;
  90. $db_user = sess_user;
  91. $db_pass = sess_pass;
  92. $db_name = sess_db;
  93. //創建資料庫連接,這是一個全局變數。
  94. $GLOBALS[MY_SESS_CONN] = ADONewConnection($db_type);
  95. $GLOBALS[MY_SESS_CONN]-&>Connect( $db_host, $db_user, $db_pass, $db_name);
  96. //初始化 SESSION 設置,必須在 session_start() 之前運行!!
  97. My_Sess::init();
  98. ?&>

第二個問題,需要對cookie的域進行設置, 默認情況下,COOKIE 的域是當前伺服器的域名/IP 地址,而域不同的話,各個伺服器所設置的 COOKIE 是不能相互訪問的,如 www.aaa.com 的伺服器是不能讀寫 www.bbb.com 伺服器設置的 COOKIE 的。這裡我們所說的同一網站的伺服器有其特殊性,那就是他們同屬於同一個一級域。

  1. &
  2. ini_set(session.cookie_domain, a.com);
  3. ?&>

這樣即可設置程序中的cookie域統一為http://a.com


session同步可以通過redis實現,具體參考 redis session 存儲 同步


關於生命周期的問題樓上已經回復的很好了,我補充一點就是,cookie周期其實就是設置了就按設置的來,沒有設置的話那就是關閉瀏覽器就拉閘。session的話是放在伺服器端,生命周期的話可以設置,好像一般是幾十分鐘還是多少這個我也不確定,一般會設置一個時間去檢測session多久沒有效果了,如果沒有效果就清空內容;session怎麼不同伺服器共享的話,一般是兩個方法,一個是放在一個專門存放session的資料庫的伺服器上,然後調用這個資料庫的數據進行sessionId的查找和生成,還有一個辦法是通過memcache,分散式集群, 他可以把web伺服器中的內存組合起來,成為一個」內存池」,不管是哪個伺服器產生的session都可以放到這個」內存池」中,其他的都可以使用。


推薦閱讀:

為什麼rsa加密時我把密鑰長度設成256位,太長的字元串加密就會出錯?
目前來說在網站架構方面採用nobackend這種方案構建是否真的可行?
如何反駁服務端程序員聲稱SELECT出來的數據直接丟給客戶端的代碼最好?
因不知道Reservoir Sampling演算法而掛掉面試的我是否要檢討自己?

TAG:PHP |