php

浅谈PHP中打开文件(fopen)的一些坑

PHP
3410
12
2016-11-23

准备自己实现一个基于文件的简单缓存类,用于一些小外包项目。原本只是打算按照特定的JSON格式进行存储,然后用很方便的两个函数 file_get_contentfile_put_content 进行读写
后来想给缓存类加个自增/减的方法,用来做简单的统计之类。如果考虑到瞬间并发情况,为了防止两个请求互相干扰,就必须得上 文件锁

首先是 设置缓存,这个没啥难度:

  • 首先一个$h = fopen($path, 'w+b')打开文件
  • 然后flock($h, LOCK_EX)锁定它,防止两次并发请求同时读写
  • 接着fwrite($h, 'xxx...')写入内容
  • 继续flock($h, LOCK_UN)解锁文件
  • 最后fclose($h)关闭文件指针

然后是 读取缓存,按照上面流程如法炮制。只是把第3步的 fwrite 换成 fread,把读出来的内容解析一下返回。看起来好像没什么问题。

然而我无论怎么试,缓存总是能正常写入,却无法读取

这我就懵逼了。经过一阵调试,发现 fread 总是返回空,这是为毛!?
再一阵调试过后,我发现在 fopen 之后,文件就已经变成空了。这就很奇怪了
我打开官方文档,仔细地再次阅读了w+模式的说明

读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。

沃日感情你所谓的 将文件大小截为零 就是会清空文件内容的意思啊?这表达得也太隐晦了吧?
看来w+是不能用了。我试着把fopen的模式切换到 r+x+a+ 以及 c+,都无法达到 先读取文件内容,然后覆盖写入 的效果

中间折腾过程略……

后来发现一个函数 ftruncate ,文档中描述为 将文件截断到给定的长度。我用 r+b 打开、锁定文件,读取内容后用该函数将其清空;递增/减数值后写入,解锁。看起来一切都那么顺利

然而我最终保存的文件,在正常内容前有一串奇怪的空字符,导致再次读取时格式不对
怀疑是清空内容后没有将指针复位。找到一个 rewind 函数,执行后再进行写入操作。最终一切正常,可喜可贺、可喜可贺!

昵称
邮箱
网址
、 这不科学的头像 2017-05-18 09:38
、 这不科学

学习了

做整容手术要多少钱的头像 2016-12-01 15:07

不错 谢谢 博主www.1688616.com

胡杨的头像 2016-12-09 16:56

程序员棒棒的

呆头空的头像 2016-12-08 18:25

所以菊苣你的Pixiv挂件是不是得更新了 _(:з」∠)_考虑一下上HTTPS呗

mokeyjay的头像 2016-12-08 19:54
mokeyjay 博主

Pixiv挂件没啥问题呀,干嘛要更新,还是说你有什么想要的功能?至于HTTPS,我穷,免费的国内CDN不支持HTTPS,所以我也没法部署 sada_org.gif

呆头空的头像 2016-12-08 20:10

VeryCloud可以,每个月免费50G流量

mokeyjay的头像 2016-12-08 21:34
mokeyjay 博主

sb_org.gif 还不够我用几天的

上海seo的头像 2016-11-30 16:50

博主是程序员的啊?

mokeyjay的头像 2016-11-30 20:16
mokeyjay 博主

是啊,看不出来吗?

上海seo的头像 2016-12-10 17:00

看出来了,哈哈

月的固执的头像 2016-11-28 09:43

file_put_content 第三个参数 不是有锁定选项吗?会有什么问题吗?

mokeyjay的头像 2016-11-28 17:15
mokeyjay 博主

举个例子,有个缓存的值为0,通过一个接口我们可以递增这个缓存值。此时请求1开始file_get_content,然后值+1,正准备file_put_content时,另外一个并发请求2也开始file_get_content了,此时请求2获取到的值为0。也就导致请求2最终把缓存值设为1了。所以要做递增的话,必须保证从读取到写入期间一直独占锁定缓存文件