세션을 DB로 관리하기 + 쪽지 확인하기

[1] 대개의 경우, 회원 로그인에 세션을 사용합니다. 즉 회원으로 로그인하지 않으면 세션을 사용할 일이 없는 경우가 많습니다. 따라서 여기서는 회원 전용 세션만 관리하도록 하겠습니다.

[2] 세션 테이블을 만들어서, 여기에 매번 insert, delete를 하는 것은 비록 테이블 크기가 작다고 하더라도 제법 부하를 줍니다. 따라서 여기서는 delete를 쓰지 않고 update만 사용하도록 하겠습니다. 회원이 몇만명이라고 할지라도 이게 더 나은 것 같더군요.

세션 테이블을 다음과 같이 만듭니다. 회원목록 테이블과 똑같은 rows를 갖게 되겠죠.
CREATE TABLE `user_sessions` (
`uid` int(10) unsigned NOT NULL default ‘0’, /* 회원ID */
`sess_key` varchar(32) NOT NULL default ”, /* 세션키 */
`last_log` int(11) unsigned NOT NULL default ‘0’, /* 세션을 기록하는 시간 */
`last_ip` varchar(15) NOT NULL default ”, /* 꼭 필요하지는 않지만, 추후 필요성을 느끼게 될 것임 😉 */
`sess_value` text NOT NULL, /* 세션값 */
PRIMARY KEY (`uid`),
KEY `sess_key` (`sess_key`)
) TYPE=MyISAM;

세션을 처리하는 스크립트는 다음과 같습니다.
(편의상 DB 클래스를 사용했습니다만, 뭐하는건지는 다 아시겠죠? –;;)

session_set_save_handler(“sess_open”, “sess_close”, “sess_read”, “sess_write”, “sess_destroy”, “sess_gc”);
session_start();

function sess_open($save_path, $session_name) {
return 1;
}

function sess_read($key) {
$DB =& DB::getInstance();
$DB->query(“SELECT sess_value FROM user_sessions WHERE sess_key = ‘$key’ AND last_log > ‘”.(time()-get_cfg_var(“session.gc_maxlifetime”)).”‘ “);
$row=$DB->get();
return $row[0];
}

function sess_write($key, $value) {
$DB =& DB::getInstance();
$DB->query(“UPDATE user_sessions SET last_log='”.time().”‘, last_ip='”.$_SERVER[“REMOTE_ADDR”].”‘, sess_value=’$value’ WHERE sess_key=’$key’ “);
return ”;
}

function sess_close() {
return 1;
}

function sess_destroy($key) {
$DB =& DB::getInstance();
$DB->query(“UPDATE user_sessions SET sess_key=”, last_log='”.time().”‘, last_ip='”.$_SERVER[“REMOTE_ADDR”].”‘ WHERE sess_key=’$key’ “);
}

function sess_gc($lifetime) {
return 1;
}

[3] 회원 로그인하는 부분에서 sess_key 값을 할당합니다. 그 다음부터 그 회원은 세션 테이블에서 데이타를 읽고 쓸 수 있습니다.
비회원인 경우는 session_start()를 하더라도 세션키값만 주어질 뿐, 여러가지 $_SESSION값을 사용할 수 없습니다. (물론 쿠키값을 이용해 비회원의 경우는 아예 session_start()를 실행하지 않는 방법도 있죠. ^^;;)

[4] 이렇게 함으로써 굳이 가비지 콜렉션(update 혹은 delete)을 하지 않아도 됩니다. 또 회원의 마지막 접속시간이 최신으로 유지됩니다.

[5] 회원의 중복 로그인이 방지됩니다. 다만, 같은 위치에서 다른 세션을 다시 시작하는 경우인지, 이미 로그인된 상태인지 등의 예외적 상황을 처리하기 위해 로그인을 처리하는 부분을 보강해야겠죠. 😉

——————————
자, 이쯤에서 한가지 욕심을 내어볼까요? 이른바, 쪽지 기능을 추가해보겠습니다. (실시간 쪽지 기능 아닙니다. –;;)
제로보드인가? 쪽지를 받으면 “딩동, 새로운 메시지가 도착했습니다” 하는 소리가 나쟎아요.
받은 쪽지 테이블을 따로 만들면, 매번 해당 테이블을 읽어야 하지 않습니까. 뭐 굳이 2번 작업을 하냐, 세션을 시작하면서 자동적으로 그 데이타를 읽어오자 하는거지요.

`user_sessions` 테이블에 chk_msg 라는 필드를 하나 추가합니다.
ALTER TABLE `user_sessions` ADD `chk_msg` TINYINT(2) UNSIGNED NOT NULL AFTER `last_ip` ;
다른 회원이 쪽지를 보낼때, chk_msg 값을 하나씩 증가시키고, 쪽지를 확인하면 chk_msg 값을 0로 update하려는 것입니다.

sess_read 함수를 변형시켜서, chk_msg 필드값을 읽어오도록 합니다.
function sess_read($key) {
$DB =& DB::getInstance();
$DB->query(“SELECT uid, chk_msg, sess_value FROM user_sessions WHERE sess_key = ‘$key’ AND last_log > ‘”.(time()-get_cfg_var(“session.gc_maxlifetime”)).”‘ “);
$row=$DB->get();
$GLOBALS[“USER”][“id”]=$row[0];
$GLOBALS[“USER”][“chk_msg”]=$row[1];
return $row[2];
}

여기서는 $USER라는 변수에 회원ID, 받은쪽지 갯수를 저장하도록 했습니다. 즉 회원ID를 알기 위해 $_SESSION[“id”]를 쓰지 않고 $USER[“id”]를 쓸 수 있다는 거지요. 받은 쪽지 갯수는 $USER[“chk_msg”]에 들어가니까 매번 이 값을 검사해서 “딩동~ 메시지가 도착했습니다” 라고 알려주면 됩니다.

“뭐야, 난데없이 $USER 라는 글로벌 오브젝트라니…. 난 이런거 싫어” 하시는 분.
여기서 $USER와 같은 변수를 할당하지 않고, $_SESSION을 사용해도 됩니다. $USER[“chk_msg”]=”1″ 대신에 $_SESSION[“chk_msg”] = “1” 이라고 써도 된다는 겁니다. 세션함수 안에서 $_SESSION을 사용한다니, 황당한 트릭이죠? 번거롭게 세션값($row[2])을 직접 파싱할 필요가 없습니다.
다만, 이것은 맨처음 1번만 적용됩니다. 다음번에 $_SESSION[“chk_msg”] = “2” 라고 하더라도, 이미 세션 데이타 안에 “chk_msg”값이 1로 들어있기 때문에, 결과는 “1”로 나옵니다. 따라서 $_SESSON[“chk_msg”] 값을 확인해서 “딩동~” 하는 메시지를 보여주거나 말거나 한 후에, 매번 unset($_SESSION[“chk_msg”])를 해줘야하겠죠?

——————————

재미있으셨나요?
“한번 더 생각하고 DB를 만들자”는 취지에서 한번 써봤고, 나름대로 효율적이라고 생각하는데… 장담은 못하겠네요. 오류나 정정사항은 리플 부탁드립니다.