聊聊WebSocket

  • WebSocket介绍

WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。WebSocket协议在2011年由IETF标准化为RFC 6455,后由RFC 7936补充规范。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

  • HTTP和WebSocket之间的异同点。

WebSocket是一种与HTTP不同的协议。两者都位于OSI模型的应用层,并且都依赖于传输层的TCP协议。 虽然它们不同,但是RFC 6455中规定:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries(WebSocket通过HTTP端口80和443进行工作,并支持HTTP代理和中介)。 为了实现兼容性,WebSocket握手使用HTTP Upgrade头从HTTP协议更改为WebSocket协议。
与HTTP不同,WebSocket提供全双工通信。
WebSockets URI的格式如下:

ws-URI = “ws:” “//” host [ “:” port ] path [ “?” query ]
wss-URI = “wss:” “//” host [ “:” port ] path [ “?” query ]

ws和wss分别对应明文和加密连接(对比HTTP和HTTPS)。

  • WebSocket实战

因为WebSocket被设计为和HTTP/HTTPS一起工作在80/443端口并且支持HTTP代理和网关,所以一个服务端程序同时接收HTTP和WebSocket请求。对,没错,一个Server端程序可以同时接收HTTP请求和WebSocket请求。

Demo如下:

//处理普通的HTTP请求
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, world!"))
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "index.html") 
})
http.HandleFunc("/ws/chat", func(w http.ResponseWriter, r *http.Request) {
    //这里写一个读取cookie的实例是告诉大家,在处理WebSocket的handshake请求时,服务端是可以读到该域名下的cookie的,具体细节和HTTP协议规范一致。 
    cookie, err := r.Cookie("key")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("cookie:" + cookie.String())
    }
    //使用github.com/gorilla/websocket 
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    //... 
    //此处conn就是一个websocket连接了,服务端可以通过conn来发送消息给客户端 
})

仔细一想,你就会明白。客户端发起ws请求,第一次握手(handshake)的请求和正常的HTTP请求体的格式是遵循的同一个规范——RFC2616。所以WebSocket的第一次握手请求正好被设计为要求和正常的HTTP请求体一样,这样就解答了为什么在一个普通的HTTP Server程序只监听一个端口的情况下可以同时处理HTTP请求和WebSocket请求了。同时也请注意对客户端而言,是建立了两个连接:一个HTTP连接,一个WebSocket连接。

如果你还是不明白,那么我换另外一种说法:

客户端发起了一个请求,可能是HTTP请求也可能是WebSocket,但是他们的请求体格式是一样的。服务端可以事先和客户端约定,根据path路径区分来源,进而决定哪一个是当HTTP请求处理:返回正常HTTP Response,哪一个当WebSocket请求处理:返回Upgrade,继而持有连接实现双向通信。

摘抄部分RFC内容如下:

The handshake MUST be a valid HTTP request as specified by [RFC2616].
The method of the request MUST be GET, and the HTTP version MUST be at least 1.1.
For example, if the WebSocket URI is “ws://example.com/chat”, the first line sent should be “GET /chat HTTP/1.1”.

 

References:

https://datatracker.ietf.org/doc/rfc6455

https://zh.wikipedia.org/wiki/WebSocket

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

 

记录使用unredacter的流水账

unredacter是一个实验性的还原图片中打马赛克的文字的小工具。具体介绍请见github(https://github.com/BishopFox/unredacter)。 README中使用说明部分描述的使用步骤很简单,但是如果你照着尝试,会发现程序跑不起来,各种报错(node的三方库版本依赖的天坑😭),我就将我淌坑的经历记录下来吧。

工作环境:windows 11(因为windows 11才支持wslg),启用wsl2,使用Ubuntu 22.04镜像

步骤:

1. (wsl2的Ubuntu里)安装node v12版本。(直接去官网下载v12版本的二进制文件 https://nodejs.org/download/release/v12.22.12 ,将bin目录加到path路径即可)
2. (wsl2的Ubuntu里)下载源码到本地https://github.com/BishopFox/unredacter
3. (wsl2的Ubuntu里)进入项目根目录,执行 npm --registry https://registry.npmmirror.com install (注意:不要再使用https://registry.npm.taobao.org这个镜像地址了,该地址已经停用了)
4. (wsl2的Ubuntu里)进入项目根目录执行npm --registry https://registry.npmmirror.com install electron@10 (注意一定要安装electron v10版本, v11版本就不行)
5. (wsl2的Ubuntu里)进入项目根目录执行npm start

最终效果图:

问题1: 如果在执行 npm start时遇到如下错误:[5095:0621/232535.423408:FATAL:gpu_data_manager_impl_private.cc(442)] GPU process isn’t usable. Goodbye.
/home/{username}/unredacter /node_modules/electron/dist/electron exited with signal SIGTRAP

解决办法:在windows 11下需要按照官方说明安装对应显卡厂商的显卡驱动(https://github.com/microsoft/wslg ),这样wsl2就支持使用GPU了。

问题2: unredacter已经能正常工作了,但是隔了几天突然又不能工作了,有如下报错:
../../third_party/tcmalloc/chromium/src/tcmalloc.cc:337] Attempt to free invalid pointer 0x55dd1f867dc0
/home/{username}/unredacter-main/node_modules/electron/dist/electron exited with signal SIGSEGV
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! unredacter@1.0.0 start: `npm run build && electron ./dist/main.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the unredacter@1.0.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

解决办法:检查你的显卡驱动是不是被操作系统升级,导致和wslg不兼容了。对于AMD:

 

 

References:

https://mp.weixin.qq.com/s/W20dZpX6nJrNFMrXB4P4Mg

 

查看Linux内核和发行版本的方法

查看Linux发行版本的方法

  • cat /etc/issue,适用于所有的发行版本
  • lsb_release -a,也适用于所有的发行版本,但是有些发行版本(尤其是一些精简版的镜像)可能没有预置lsb_release,需要手动安装。
  • cat /etc/redhat-release,这种方法只适合Redhat系的发行版本,包括CentOS
  • cat /etc/centos-release,只适用于CentOS

 

查看Linux内核版本命令

  • cat /proc/version
  • uname -a

记PHP比较运算符的一个用例

最近被问到如下的一道题:

$x = false;
$i = 9;
for ($i = 9; $i > $x; $i -= 2) {
    echo $i . "\n";
}
//请说出代码执行后的输出结果。

一般想到int类型$i和bool类型的$x比较,bool类型的false会被转换成int类型的0,所以代码块会有5次输出后停止结束。

那么实际执行结果是怎样的呢?

实际执行一次,就会发现,结果是无限循环输出:9,7,5,3,1,-1,-3,-5,……。

为什么呢?

仔细查看PHP文档:https://www.php.net/manual/en/language.operators.comparison.php

会发现有这样的描述:

Type of Operand 1 Type of Operand 2 Result
null or string string Convert null to “”, numerical or lexical comparison
bool or null anything Convert both sides to boolfalse < true
object object Built-in classes can define its own comparison, different classes are uncomparable, same class see Object Comparison
stringresourceint or float stringresourceint or float Translate strings and resources to numbers, usual math
array array Array with fewer members is smaller, if key from operand 1 is not found in operand 2 then arrays are uncomparable, otherwise – compare value by value (see following example)
object anything object is always greater
array anything array is always greater

其中第二行就有关于bool类型和其他类型的比较,有明确说明,会把其他类型转换成bool类型。那么上文中的这道题,$i总是不等于0,即意味着$i总是会被转换成bool类型的true,true永远大于false,所以会有无限循环的输出。

 

无法比较的两个概念:JWT和Session

这两个概念单独解释其实很简单,但是混在一起可能会说不清。正好看到网上有人聊起了这个,我就做个整理,也当知识回顾。

问题:为什么要用 jwt token 来做身份认证,而不是使用session+cookie方式?

那么我们先看概念本身的解释:

JWT ——JSON Web Token

JWE——JSON Web Encryption

JWS——JSON Web Signature

Session,此处可以翻译为会话,指客户端和服务端维持有状态的一种通信机制。根据Session Data的存储位置,可以分为Server Side Session和 Client Side Session。一般说的session & cookie机制是指使用的Server Side Session。

所以JWT和Session不是同一个概念的东西,没法拿来比较。回头再看这个问题就发现有点意思了。我们使用jwt token来做身份认证,我们可以把jwt token(会含有一些用户信息)通过cookie、HTTP头,url参数等多种方式下发给客户端(Client Side Session)从而实现session机制;而传统的服务端session-data + session-id(by cookie)方式,session-id只是一串随机字符串,不用解析,在和服务端交互时直接原样传输就行了。所以原作者问题可能是Server Side Session和Client Side Session的区别!

References:

https://en.wikipedia.org/wiki/JSON_Web_Token

https://blog.by24.cn/archives/about-session.html

https://www.v2ex.com/t/774127

1MB=1024KB? 1000KB?

如题,这个问题第一感觉是1MB肯定等于1024KB,只有在硬盘生产厂商那里才会使用1000进制。事实是如此吗?最近被人补了一课,了解单位换算中的两种标准。

按照国际单位制的标准: 1MB=1000KB (1Megabyte = 1000Kilobyte)
按照IEC 60027-2标准: 1MiB=1024KiB (1Mebibyte = 1024 Kibibyte)

字节的次方单位
十进制前缀
(SI)
名字 缩写 次方
Kilobyte KB 103
Megabyte MB 106
Gigabyte GB 109
Terabyte TB 1012
Petabyte PB 1015
Exabyte EB 1018
Zettabyte ZB 1021
Yottabyte YB 1024
二进制前缀
(IEC 60027-2)
名字 缩写 次方
Kibibyte KiB 210
Mebibyte MiB 220
Gibibyte GiB 230
Tebibyte TiB 240
Pebibyte PiB 250
Exbibyte EiB 260
Zebibyte ZiB 270
Yobibyte YiB 280

所以严格来说如果显示为1MB,则说明使用的是国际单位制标准,等于1000KB。1MiB说明是使用的IEC 60027-2标准,等于1024KiB。

  • 但是注意,Microsoft Windows系统中仍在大量使用公制前缀的二进制写法(比如显示为1MB的文件,按理说应该是使用的SI标准,等于1000KB,但是实际windows却按IEC 60027-2标准来处理的,实际文件大小是1024KB)。
  • Mac OS X自Snow Leopard(v10.6)发行以来,文件大小均是以十进制前缀记录的(比如显示为14KB,87.1MB)。(但是在shell环境里,部分软件使用的M,K等标识符表示1024进制,比如ls;但是df命令使用的是Gi,Mi等SI单位制的符号)

 

References:

https://zh.wikipedia.org/wiki/国际单位制

https://zh.wikipedia.org/wiki/千字节

聊聊固态硬盘(SSD)的接口和协议

固态硬盘固态驱动器(英语:Solid-state drive或Solid-state disk,简称SSD)是一种以集成电路制作的电脑存储设备。

最近如果你要买SSD,可能会频繁接触到M.2,NVMe,PCIe这几个名词。我上一次单独买SSD时,虽然已经是M.2接口了,但是当时没见NVMe协议,最近正好关心了SSD,就正好接机学习了解下。

  1. 接口:M.2是一种接口规范,与之类似的有U.2SATA接口mSATA接口,SATA Express接口,PCIe接口等。
    注意:SATA同时也是一种系统总线的名称,所以要区别SATA接口和SATA总线。SATA Express协议使用的接口是由两个传统的SATA样式的接口和一个小接口组成,因此也能当作两个SATA来用。
  2. 逻辑设备接口规范NVMe(NVM Express,或者Non-Volatile Memory Host Controller Interface Specification (NVMHCIS),AHCI(Advanced Host Controller Interface)
  3. 总线: SATA,SATA Express,PCI,PCI Express(PCIe)

 

下面这个短视频有很生动的介绍:

https://www.youtube.com/watch?v=alb6-zp52mA&t=505s

NVM Express(缩写NVMe),或称非易失性内存主机控制器接口规范(英语:Non-Volatile Memory Host Controller Interface Specification,缩写:NVMHCIS),是一个逻辑设备接口规范。它是基于设备逻辑接口的总线传输协议规范(相当于通讯协议中的应用层),用于访问通过PCI Express(PCIe)总线附加的非易失性存储器介质(例如采用闪存固态硬盘驱动器),虽然理论上不一定要求PCIe总线协议。NVMe是一种协议,是一组允许SSD使用PCIe总线的软硬件标准;而PCIe是实际的物理连接通道。

所以当下性能最佳的SSD的技术方案组合应该是:M.2接口+NVMe+PCIe。早些年在NVMe普及之前,有M.2接口+AHCI+SATA的SSD。SATA接口+AHCI+SATA总线的SSD也有,估计都是没有M.2接口的老电脑升级用吧,现在京东上都还有卖的。

 

References:

https://zh.wikipedia.org/wiki/PCI_Express

https://zh.wikipedia.org/wiki/SATA

https://zh.wikipedia.org/wiki/SATA_Express

       (SFFWG Renames PCIe SSD SFF-8639 Connector To U.2)

https://zh.wikipedia.org/wiki/NVM_Express

https://zh.wikipedia.org/wiki/M.2

 

 

《中国近代史》读书笔记

《中国近代史》——蒋廷黻(著)

最后更新时间:2021-01-06

黻(

  1. 古代礼服上黑与青相间的花纹:黼~。
  2. 同“韨”。

琦善

琦(qí)

  1. 美玉。
  2. 珍奇,美好:~玮(奇丽的意思)。瑰意~行。

马戛尔尼(乔治·马戛尔尼,George Macartney)

戛(jiá)

  1. 长矛。
  2. 敲,敲打。
  3. 常礼;常法。
  4. 刮。
  5. 象声词:~然长鸣。

恭亲王奕(yì)䜣(xīn)

郭嵩焘(guo song tao)

伊里布和耆英

耆(qí)

  1. 年老,六十岁以上的人:~老。~年。~绅。~宿(sù)(指在社会上有名望的老年人)。
  2. 强横。

 

端王载漪子溥(pǔ)隽(jùn)

漪(yī)

水波纹:~沦。~澜。清~。涟~(细小的波纹)。

隽(jùn)

古同“俊”。

 

《中国近代史》是蒋廷黻所著的,于1938年首次出版的中国近代史研究的重要著作。他作为一个“知外交”的学者和政客,从外交的角度讲述中国近代史以及从外交的角度分析近代历史事件发生的原因,背景。

在讲述鸦片战争爆发前后清政府(实际更多是地方官员自主决定)与英国(商人、政府代表)交涉的过程中,通过史料讲述了清政府官员对外的极其无知,从上到下不了解外面的世界,不愿意给外国平等交往的机会;在交涉的过程中,又极其自大,不守诚信,不主动了解敌人情况,很多事情都凭道听途说和意淫。战争失败后,不能接受失败思检讨问题,认清差距,反而对外争执一些无关紧要的内容,对关于主权的事情放任英国制定规则(当然作者也知道,我们是以后来着的角度来看待这个问题,觉得痛心棘手,当时人并不觉得)。

协定关税和治外法权是我们近些年所认为不平等条约的核心,可是当时的人却并不这样看治外法权,在道光时代的人的目光中,不过是“让夷人管夷人”。他们想那是最方便、最省事的办法。至于协定关税,他们觉得也是方便省事的办法。每种货物应该纳多少税都明白地载于条约,那就可以省去争执。负责交涉条约的人如伊里布、耆英、黄恩彤诸人知道战前广东地方官吏的苛捐杂税是引起战争原因之一,现在把收税标准明文规定岂不是一个釜底抽薪,一劳永逸的办法?而且,新的收税标准平均到5%,比旧日的自主关税还要略微高一点。负交涉责任者计算以后的海关收入比以前还要多,所以他们洋洋得意,以为他们的外交是成功的。其实,他们牺牲了国家的主权,贻害不少。总而言之,道光年间的中国人完全不懂国际公法和国际形势,所以他们争所不应争的,放弃所不应放弃的。

想起了书中关于海关税收的内容了。在下篇“外交策略不进反退“一节中,作者认为在康熙中后期的外交是积极的,对外平等的(在涉及和俄国签订尼布楚条约的章节有讲到)。之后的时间里基本都是开倒车。关于税收税率和规则有一段很深刻的描述:

清政府正式设海关监督,规定粤海关由内务府派,闽海关由福州将军兼,浙海关及江海关的由各省巡抚兼。按法律,中国旧关税制度完备极了、公道极了。康熙的训谕说:“各省关钞之设,原期通商利民以资国用”;“国家设关榷税,必征输无弊,出入有经,庶百物流通,民生饶裕”。世宗的旨趣相同:“国家之设关税,所以通商而非累商,所以便民而非病民也。”乾隆也说过:“朕思商民皆为赤子,轻徭薄赋,俾人民实沾惠泽,乃朕爱养黎庶之本怀。”户部颁有税则,其平均率不到百分之五,比《南京条约》以后的协定税则还要低廉。

……

很明显的,中国自十七世纪末年起,已有了法定的、公开的海关税则。

实际上,中国海关收税的情形不但离高尚道德甚远,且与法律绝不相符。直到鸦片战争,外商不知中国的税则的模样。历康雍乾嘉四朝,外人索看海关税则多次,每次概被衙门拒绝。关税分两种:船钞与货税。照户部的章程,船钞应丈量船的大小而定:大船约纳一千二百两,中船约九百六十两,小船约五百四十两。实际除船钞外,还须“官礼”。在十七世纪末年,官礼的多少,每次须讲价。到康熙末年,十八世纪初年,官礼渐成固定:不问船的大小,概须送一千九百五十两,比正钞还多。货税也有正税及“陋规”。陋规最初也是由收税者及纳税者临时去商议,到康熙末年,大约已达货价百分之六,比正税亦大。雍正初年,杨文乾以巡抚兼关监督的时候,官礼报部归公,于是官吏在货税上加了百分之十的陋规,名曰“缴送”。正税及各种陋规总起来约当百分之二十,这是中国实行的税则。

在讲曾国藩时,作者认为,曾国藩技术革新和思想守旧两条路一起走,在对抗太平天国过程中起到重要作用。但是对于一个国家、民族振兴而言是远远不够的,是必然会失败的。作者认为失败原因两点:曾国藩为代表的改革派本身由于成长经历,思想素质等原因不愿因全面改革(比如不赞同政体改革,……);另外一点就是反对者总舵,阻力太大。

作者虽然“知外交”,但却不是鼓吹外交决定一切的人。书里,作者有一句特别让我感慨:“在近代世界,败仗是千万不能打的。”。所以作者在讲到中日在朝鲜的争端时有说到:“近代战争固不是儿戏。不战而求和当然要吃亏……但战败以后而求和,吃亏之大远过于不战而和。”

总体而言我觉得这本书还是蛮好的。对于1840~1912年间,几个大事件和时人为之努力(改革)的分析很精彩。

 

 

名句摘录:

中西的关系是特别的。在鸦片战争以前,清政府不肯给外国平等待遇;在以后,西方国家不肯给清政府平等待遇。

 

在维持清政府作为政治中心的大前提之下,曾国藩的工作分两方面进行。一方面他要革新,那就是说,他要接受西洋文化的一部分;另一方面他要守旧,那就是说,恢复中国固有的美德。革新守旧同时举行,这是曾国藩对中国近代史的大贡献。

 

曾国藩及其他自强运动的领袖虽然走的路线不错,然而他们不能救国救民族。此其故何在?在于他们的不彻底。他们为什么不彻底呢?一部分因为他们自己不要彻底,大部分因为时代不容许他们彻底。我们试先研究领袖们的短处。

恭亲王奕、文祥、曾国藩、李鸿章、左宗棠这五个大领袖都出身于旧社会,受的都是旧教育。他们没有一个人能读外国书,除李鸿章以外,没有一个人到过外国。就是李鸿章的出洋尚在甲午战败以后,他的建设事业已经过去了。这种人能毅然决然推行新事业就了不得,他们不能完全了解西洋文化是自然的,很可原谅的。他们对于西洋的机械是十分佩服的,十分努力要接受的。他们对于西洋的科学也相当尊重,并且知道科学是机械的基础。但是,他们自己毫无专业的机械常识,此外更不必说了。他们觉得中国的政治制度及立国精神是至善至美,无须学西方的。事实上他们的建设事业就遭了旧的制度和旧的精神文化的阻碍。

 

总之,资本主义可变为帝国主义,也可以不变为帝国主义。未开发的国家容易受资本主义的国家的压迫和侵略,也可以利用外国的资本来开发自己的富源及利用国际的通商来提高人民的生活程度。资本主义如同水一样:水可以资灌溉,可以便利交通,也可以成灾,要看人怎样对付。

同时我们不要把帝国主义看得过于简单,以为世界上没有资本主义就没有帝国主义了。…… 据我们所知,历史上各种政体,君主也好,民主也好,各种社会经济制度,资本主义也好,封建主义也好,共产主义也好,都有行帝国主义的可能。

 

在近代世界,败仗是千万不能打的。

 

我们不要以为顽固分子不爱国,从鸦片战争起,他们是一贯的反对屈服,坚强的主张抗战。

 

近代战争固不是儿戏。不战而求和当然要吃亏……但战败以后而求和,吃亏之大远过于不战而和。

 

马戛尔尼的外交失败是由于中西方邦交观念之不相容。中国抱定“天朝统驭万国”的观念,不承认有所谓“国际”者存在;西方在近代则步步地推演出国际生活及其所需的惯例和公法。马戛尔尼的失败证明中国绝不愿意自动或和平地放弃这种传统观念。因此中国外交史有一大特点:除康熙亲政初年外,中外曾无平等邦交的日子。在鸦片战争以前,中国居上,外国居下;鸦片战争以后则相反。

 

 

 

Unix Timestamp(Unix时间戳)

UNIX时间,或称POSIX时间是UNIX或类UNIX系统使用的时间表示方式:从UTC 1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。在多数Unix系统上Unix时间可以透过date +%s指令来检查。(Unix time (also known as Epoch timePOSIX timeseconds since the Epoch, or UNIX Epoch time) is a system for describing a point in time. It is the number of seconds that have elapsed since the Unix epoch.) (英 [ˈiːpɒk]   美 [ˈepək])

从这个解释可以看出来,同一时刻,在全世界任一时区,获取的Unix时间戳是相同的。

所以,针对PHP而言,time()函数获取的到时间戳与时区无关。

time ( ) : int

Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT).

那么,进一步延伸,对于一个给定的日期时间字符串,例如:2020-01-01 00:00:00,那么获取这个日期时间对应的时间戳就是与时区有关的。因为不同时区下的2020-01-01 00:00:00距离UTC的1970-01-01 00:00:00的时间间隔是不一样的。

strtotime ( string $datetime [, int $now = time() ] ) : int

对于PHP而言,在使用strtotime函数时,如果日期时间字符串中没有包含时区信息,那么会使用默认的时区date_default_timezone_get()。(Each parameter of this function uses the default time zone unless a time zone is specified in that parameter. Be careful not to use different time zones in each parameter unless that is intended. )

$dateStr = '2020-01-01 00:00:00';

$timezone = 'Asia/Shanghai';
date_default_timezone_set($timezone);
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStr, strtotime($dateStr));

$dateStrWithTimezone = '2020-01-01T00:00:00+08:00';
date_default_timezone_set($timezone);//优先读取日期时间字符串里的时区信息,此处单独设置的时区对下一行的strtotime无效
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStrWithTimezone, strtotime($dateStrWithTimezone));

$timezone = 'UTC';
date_default_timezone_set($timezone);
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStr, strtotime($dateStr));
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStrWithTimezone, strtotime($dateStrWithTimezone));

output:
Asia/Shanghai)2020-01-01 00:00:00 ==> 1577808000
Asia/Shanghai)2020-01-01T00:00:00+08:00 ==> 1577808000
UTC)2020-01-01 00:00:00 ==> 1577836800
UTC)2020-01-01T00:00:00+08:00 ==> 1577808000 //优先读取日期时间字符串里的时区信息,runtime设置的时区对strtotime无效

 

date ( string $format [, int $timestamp = time() ] ) : string

Format a local time/date

同样的道理,同一个时间戳在不同的时区下,对应的日期时间字符串是不一样的。

date_default_timezone_set('UTC');
$timestamp = 1577836800;//UTC 2020-01-01 00:00:00
$sourceDatetime = new \DateTime(date('Y-m-d H:i:s', $timestamp));
echo sprintf("source datetime:%s(%s)\n", $sourceDatetime->format('Y-m-d H:i:s'), $sourceDatetime->getTimezone()->getName());

$timezone = 'Asia/Shanghai';
$targetDatetime = (new \DateTime(date('Y-m-d H:i:s', $timestamp)))
    ->setTimezone(new \DateTimeZone($timezone));
echo sprintf("target datetime:%s(%s)\n", $targetDatetime->format('Y-m-d H:i:s'), $targetDatetime->getTimezone()->getName());

output:
source datetime:2020-01-01 00:00:00(UTC)
target datetime:2020-01-01 08:00:00(Asia/Shanghai)

 

References:

https://zh.wikipedia.org/wiki/UNIX时间