/首页
/开源
/关于
xue微xue微深入地聊一聊PHP session
发表@2019-11-15 15:43:15
更新@2023-04-27 22:30:58
大家好,我今天打算换一个新的出场方式。所以,我打算从下面倒计时后开始重新打招呼,你们就假装开头这句话我没写配合一下,谢谢。 你们准备好了吗,我要重新从头开始了... 5 4 3 2 1 . . . 大家好!  事情是这样的,昨天晚上我先发了一篇关于API Token的文章,然后又引入了PHP Session,虽然这两篇文章阅读量创了历史新低(我仿佛看到了永强新欣慰的脸庞泛着笑容和淫光),但是还是依旧有些个问题就像屎一样甩在了我脸上,其中第一个问题角度还是比较刁钻的,你们感受下: - 老李,双11那么一大坨人访问PHP商城,PHP session id会不会重复啊? - 答:你确定你们用户量过一千了吗? - 老李,为毛我多个控制器访问同一个session成员,其他页面会被卡住,你遇到过咩? - 答:没遇到过,就特么你事儿多...告诉用户让TA们等等就行了,又不是不能用 - 老李,用什么方法可以精确控制PHP session过期以及删除 - 答:用爱 看到这三个令人绝望的回答,我穿过网线就已经听到了有人似乎在说:“ 老李,你变了... ”,然而我要告诉你并没有,你李哥办事你们心里不清楚么?  ### 第一个问题 这个问题实际上是在考验session id的生成策略,抽象一下就是【某个空间中生成全局唯一的id】。这个其实没啥好说的,得去简单翻一下PHP源码中关于生成session id这里的部分了,我手里常年备着一份PHP 7.2.8的源码,但我基本没这么看过只是有需要的时候翻翻,比如现在。你可以在ext / session / session.c 文件里连蒙带搜加grep找到相关代码,你们感受下(如果我找错了,记得来打我脸,我专门出一期修正): ```php /* 这个叫做 php_session_create_id 的函数生成了session id 但是生成的核心函数是调用的 bin_to_readable 函数 */ PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */ { // 声明一个 char 数组,数组长度就是后面两个常量相加 unsigned char rbuf[PS_MAX_SID_LENGTH + PS_EXTRA_RAND_BYTES]; // zend_string 是zend封装好的字符串struct,类似于redis里d的 sds // 这里是声明一个指向 zend_string 的指针 zend_string *outid; /* Read additional PS_EXTRA_RAND_BYTES just in case CSPRNG is not safe enough */ // 这里看起来就是如果生成失败的情况. if (php_random_bytes_throw(rbuf, PS(sid_length) + PS_EXTRA_RAND_BYTES) == FAILURE) { return NULL; } // zend_string_alloc 应该是zend封装好的为string分配内存的函数 // 功能类似于 malloc 函数... outid = zend_string_alloc(PS(sid_length), 0); /* ZSTR_LEN可以获取zend_string的长度 ZSTR_VAL可以获取zend_string的值 但谁能告诉我这个PS宏是做什么用的... ... */ ZSTR_LEN(outid) = bin_to_readable(rbuf, PS(sid_length), ZSTR_VAL(outid), (char)PS(sid_bits_per_character)); return outid; } static char hexconvtab[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-"; /* returns a pointer to the byte after the last valid character in out */ /* * 注意这个函数里的玩意略风骚。至于你们能不能顶住,我反正顶不住 */ static size_t bin_to_readable(unsigned char *in, size_t inlen, char *out, char nbits) /* {{{ */ { unsigned char *p, *q; unsigned short w; size_t len = inlen; int mask; int have; p = (unsigned char *)in; q = (unsigned char *)in + inlen; w = 0; have = 0; mask = (1 << nbits) - 1; while (inlen--) { if (have < nbits) { if (p < q) { // 。。。。。。。。 // 你们有兴趣好好研究一下这行,反正我特么不看了,艹 w |= *p++ << have; have += 8; } else { /* consumed everything? */ if (have == 0) break; /* No? We need a final round */ have = nbits; } } /* consume nbits */ // 关键行在这里...out是字符串数组指针 // 这里就是将hexconvtab数组里的字符一个一个 // 随机出来赋值给out指针 *out++ = hexconvtab[w & mask]; w >>= nbits; have -= nbits; } // 这个,没啥好说的,就是给字符数组最后加上一个\0,变成字符串 *out = '\0'; return len; } ``` 说句实话,跟我想象中猜测推理的还是不太一样的,按照我之前理解,PHP的session id生成应该至少有时间戳在其中的,然而真的并没有...这段充斥着位移运算和位运算的代码,真的是...给我整吐了,恕我直言我没仔细研究。不过既然核心依然是伪随机出一个偏移量,然后取出偏移量位置上字符,那么重复还是有一定概率,只是这个概率一定是非常非常非常低,我感觉我在说废话... 然后是上面那坨代码,如果以前哪位分析过,可以简单给投稿介绍下。我感觉这个C函数可以拿走实现自己的低碰撞率随机序列了。 ### 第二个问题 这个问题其实还是有点儿意思的,而且我估计注意到的人不多。我给下demo代码,你们复制粘贴走感受下: ```php // 首先在a.php里