揭秘如何用Python黑掉智能锅炉
引文
去年我买了一个新的冷凝式锅炉(家用取暖产品),于是考虑上面必须有一个“智能恒温器”,而选择也很多,包括Google Nest、 Hive(英国天然气公司设计的) 以及伍斯特·博世‘Wave’。但最终还是选择了后者。
最后它被安装在家中楼梯下面的墙上,并利用电线连接到设备上。正如你所看到的上面有一个触摸面板,你可以在上面调整恒温器的参数,当然你也可以在上面看到温度以及蓄水情况,而你也可以切换自动模式来自动加热,其实这里最应该关心的是设备的工作状态。如果想要使用类似定时之类的功能,就不得不用到移动应用程序了,就像这样。
看起来似曾相识吧?也许你也有一款应用程序的主页面和这个看起来很像,当然你也可以设置更高级的功能,比如定时等。
对于目前还是再使用伍斯特恒温(调节)器,它可以通过手机上的应用程序来进行定时功能的设置。当然这里面还有不少其它高级的功能,例如这个功能“optimisation”,系统可以根据你室内温度的情况来自动调节,来维持你选择的温度,而这个时候你可外出游玩或者做一些别的事情。
无论怎么样,当我第一次接触这个设备的时候,就开始安装了应用程序,这样就可以监控该设备了。而作者理想的情况是通过一个Python脚本程序来完成这个功能,但设备官网上没有这样的程序。作者后来又打电话给客服人员,客服人员称没有这样的程序提供给作者。于是作者开始分析设备,并使用Python脚本程序来完成这些工作。
于是开始分析设备来完成需要做的工作,首先需要知道的一点就是应用程序会将一些设备情况以及信息发送给远程的服务器(可能是有效监控设备),然后再将反馈信息回传到恒温器,反之亦然。这就意味着可以在不同的地方且不必通过同一个无线网络就可以完成对设备的控制。当然设备也可以连接在家用电脑上,所以任何开放的端口都是需要通过防火墙才能够和服务器连接的。
在查看产品信息的时候发现了硬件以及软件版本信息,包括该设备使用的开源软件信息,打开后发现了开源软件包列表以及各种许可协议,包括
AndroidPlot-开源图表库
Guava-一个 Google 的基于java1.6的类库集合的扩展项目
XMP Toolkit -可扩展元数据管理平台
Smack-一个开源,易于使用的XMPP(jabber)客户端类库
JSR305 Expert Group-代码缺陷检测(Java)
Lucent technologies-原文作者没有注明
Chromium -Google 的chrome浏览器
Takayua OOURA-Ooura Mathematical(数学)软件
Eigen-C++矩阵处理工具
libresample-数据重采样(主要是音频)工具
这样你就可以看到确实存在基于标准通用标记语言的子集XML的协议,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序,同时XMPP提供一个通用的可扩展的框架来交换XML数据,用于准实时消息和出席信息以及请求-响应服务。而XMPP可用于服务类实时通讯、表示和需求响应服务中的XML数据元流式传输。前面那个图表库的部分与这次工作没有太大关联,而对于数学软件部分可以做出这样一个猜测,即利用这一点可以完成一些高级功能的应用。
现在我们可以清楚了XMPP协议很重要,但是目前需要发送数据以及接收数据是什么情况,于是打开Wireshark软件,然后监控流量信息,然后进行协议过滤,之后就会看到这个
好吧,现在这些证明我的猜测是正确的。安装在手机上的设备应用程序传输信息使用的是XMPP协议(本地IP92.168.0.42),博世设备的服务器(wa2-mz36-qrmzh6.bosch.de)。现在就清楚协议具体是什么情况了。
危险终究还是来了!
然后坏消息是,出现了STARTTLS,可以看出这是一个电子邮件设置对话框,同时这也是一个 POP3/IMAP/SMTP 服务安全选项。基本上可以说是“传输层安全协议”,可以说成是“我想从现在开始对这段对话进行加密,可以吗”(当然这也是一个参考,有些通信可以一开始就加密来确保通信安全),当然设备的服务器可以继续运行,而现在就已经看到了 TLS安全协议,然后就可以移动设备来发送信息了。
当然我们没有看到任何的明文信息,这些信息都是加密过的,例如这样80414a90ca64968de3a0acc5eb1b50108bbc5b26973626daaa…,这些信息都不是很有用的。
上面已经对 XMPP协议以及TLS安全协议进行了分析,但由于做了加密处理所以我们看不到信息内容。想要解密的话,需要完成一个中间人攻击,在攻击中将会截取以及分析信息的内容,在这里虽然被称为“攻击”,但是用的手机连接的网络是自己的,所以这里算是安全测试。
这里将会用到一个工具sslsplit,而在这里我无法利用sslsplit 来分析 STARTTLS(前文有对应,STARTTLS出现,然后利用TLS安全协议通信)。
作者使用的方法
开始搭建linux 服务器(NAT转换)-它工作起来像是路由器。同时也意味着我可以在网络上使用其它设备并将Linux作为网关服务器(NAT转换),
我在服务器上创建了服务器自签名根证书,而这个证书是可以信任的。
我将这个证书创建在了一个备用的安卓手机上,通过手机连接WiFi最后将Linux作为网关服务器。最后作者测试了一下,可以通过手机访问网络,然后所有的通信都经过了服务器。
然后当我在使用设备应用程序的时候,手机连接服务器,也就是说所有的通信都连接我的服务器然后在连接到厂商那边的服务器,当然证书是必不可少的。
现在我们已经创建了证书,配置了网络,我们需要开始解密信息了。正如上面所述,开始使用SSLSplit,但是这款工具似乎不能应对STARTTLS,所以需要另外一个解决途径。幸运的是,我又发现了一款工具starttls-mitm,值得注意的是它仅仅有80行Python程序代码,但可能功能上比一些相对复杂的工具要少,比如SSLSplit。但是这也都不重要,这就是我想要的。它甚至默认支持XMPP协议。
所以现在可以通过指令来运行 starttls-mitm(基本上是密匙、证书等),现在就可以分析未加密前的 STARTTLS,或者解密已加密的 STARTTLS ,如果我们分析已经运行的应用程序会得到什么?
好吧,现在我们利用starttls-mitm来分析一下登录信息看看它在做什么
LISTENER ready on port 8443
CLIENT CONNECT from: (’192.168.0.42′, 57913)
RELAYING
我们得到的初始信息
C->S 129 '<stream:stream to="wa2-mz36-qrmzh6.bosch.de" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">'
S->C 442 '<?xml version=\'1.0\' encoding=\'UTF-8\'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="wa2-mz36-qrmzh6.bosch.de" id="260d2859" xml:lang="en" version="1.0"><stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism></mechanisms><auth xmlns="http://jabber.org/features/iq-auth"/></stream:features>'
相当明显,C->S是客户端发送信息到服务器的意思,S->C意思就是刚刚那个反过来(数字代表信息的长度),这些也是刚刚分析 XMPP协议的结果,我们之前在Wireshark也看到了这些,然而我们看见了更有趣的事情。
C->S 51 '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'
S->C 50 '<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>'
Wrapping sockets.
STARTTLS验证信息发送,服务器会发出响应PROCEED,starttls-mitm分析之后会有提示“‘wrapping sockets”(而这一点也表示已经可以解密通信信息了)
而这里我会跳过TLS协议的握手过程,直接切入到XMPP协议部分。我没有分析XMPP协议的经验,而info/query也是代表握手过程的一部分,整个过程都是在交流信息比如它们是谁,做什么的等等,每一部分完成之后都会发出消息“presence”(xmpp甚至可以被看做是即时通讯协议,就相当于你在使用即时通讯软件,发出是否在线等信息)
C->S 110 '<iq id="lj8Vq-1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>70</resource></bind></iq>'
S->C 188 '<iq type="result" id="lj8Vq-1" to="wa2-mz36-qrmzh6.bosch.de/260d2859"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70</jid></bind></iq>'
C->S 87 '<iq id="lj8Vq-2" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'
S->C 86 '<iq type="result" id="lj8Vq-2" to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70"/>'
C->S 74 '<iq id="lj8Vq-3" type="get"><query xmlns="jabber:iq:roster" ></query></iq>'
S->C 123 '<iq type="result" id="lj8Vq-3" to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70"><query xmlns="jabber:iq:roster"/></iq>'
C->S 34 '<presence id="lj8Vq-4"></presence>'
C->S 34 '<presence id="lj8Vq-5"></presence>'
现在信息都已经处理好了,下面是客户端(手机应用程序)到服务器之间的信息
C->S 162 '<message id="lj8Vq-6" to="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de" type="chat"><body>GET /ecus/rrc/uiStatus HTTP /1.0\nUser-Agent: NefitEasy</body></message>'
而这基本上就是一个http GET请求并用到了XMPP 协议,而在应用程序“home screen”选项下,你可以看到主页面以及用户设备信息(当前的温度、湿度),现在我们就可以看到服务器的响应信息了
S->C 904 '<message to="rrccontact_458921440@wa2-mz36-qrmzh6.bosch.de/70" type="chat" xml:lang="en" from="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de/RRC-RestApi"><body>HTTP/1.0 200 OK\nContent-Length: 640\nContent-Type: application/json\nconnection: close\n\n5EBW5RuFo7QojD4F1Uv0kOde1MbeVA46P3RDX6ZEYKaKkbLxanqVR2I8ceuQNbxkgkfzeLgg6D5ypF9jo7yGVRbR/ydf4L4MMTHxvdxBubG5HhiVqJgSc2+7iPvhcWvRZrRKBEMiz8vAsd5JleS4CoTmbN0vV7kHgO2uVeuxtN5ZDsk3/cZpxiTvvaXWlCQGOavCLe55yQqmm3zpGoNFolGPTNC1MVuk00wpf6nbS7sFaRXSmpGQeGAfGNxSxfVPhWZtWRP3/ETi1Z+ozspBO8JZRAzeP8j0fJrBe9u+kDQJNXiMkgzyWb6Il6roSBWWgwYuepGYf/dSR9YygF6lrV+iQdZdyF08ZIgcNY5g5XWtm4LdH8SO+TZpP9aocLUVR1pmFM6m19MKP+spMg8gwPm6L9YuWSvd62KA8ASIQMtWbzFB6XjanGBQpVeMLI1Uzx4wWRaRaAG5qLTda9PpGk8K6LWOxHwtsuW/CDST/hE5jXvWqfVmrceUVqHz5Qcb0sjKRU5TOYA+JNigSf0Z4CIh7xD1t7bjJf9m6Wcyys/NkwZYryoQm99J2yH2khWXyd2DRETbsynr1AWrSRlStZ5H9ghPoYTqvKvgWsyMVTxbMOht86CzoufceI2W+Rr9</body></message>'
这看起来有些复杂不容易解释,但是整理一下信息格式或许看起来更好一些。
HTTP/1.0 200 OK
Content-Length: 640
Content-Type: application/json
connection: close
5EBW5RuFo7QojD4F1Uv0kOde1MbeVA46P3RDX6ZEYKaKkbLxanqVR2I8ceuQNbxkgkfzeLgg6D5ypF9jo7yGVRbR/ydf4L4MMTHxvdxBubG5HhiVqJgSc2+7iPvhcWvRZrRKBEMiz8vAsd5JleS4CoTmbN0vV7kHgO2uVeuxtN5ZDsk3/cZpxiTvvaXWlCQGOavCLe55yQqmm3zpGoNFolGPTNC1MVuk00wpf6nbS7sFaRXSmpGQeGAfGNxSxfVPhWZtWRP3/ETi1Z+ozspBO8JZRAzeP8j0fJrBe9u+kDQJNXiMkgzyWb6Il6roSBWWgwYuepGYf/dSR9YygF6lrV+iQdZdyF08ZIgcNY5g5XWtm4LdH8SO+TZpP9aocLUVR1pmFM6m19MKP+spMg8gwPm6L9YuWSvd62KA8ASIQMtWbzFB6XjanGBQpVeMLI1Uzx4wWRaRaAG5qLTda9PpGk8K6LWOxHwtsuW/CDST/hE5jXvWqfVmrceUVqHz5Qcb0sjKRU5TOYA+JNigSf0Z4CIh7xD1t7bjJf9m6Wcyys/NkwZYryoQm99J2yH2khWXyd2DRETbsynr1AWrSRlStZ5H9ghPoYTqvKvgWsyMVTxbMOht86CzoufceI2W+Rr9
它似乎看起来像是HTTP标准的响应,但主要的消息看起来像是某种编码格式,我认为解码之后会出现例如 JSON 、XML或者另外的一些东西,但是该这么做呢?
我尝试做了不少事情( MD5、Base64解码 ),但似乎没有起作用。于是我开始放下手头的工作,思考它。当我在工作的时候,忽然意识到数据是加密的。当你想要访问设备代码时候还需要注意你的登录密码(第一次登录设备程序时候设置的),当然解密就需要知道加密方式是什么样子的,于是需要用到反编译器。
为了了解设备的程序,就需要分析其apk文件,通过反编译的方式来查看代码。我用到的工具是 Android APK Decompiler,值得注意的是得到了通俗易懂的Java程序代码。(这里面还有一些goto语句,并设置了合理的变量名)
作者表示显然完全解释加密以及解密详细情况是非常困难的,包括下面作者的 Python程序代码,但作者做了一个简短的解释,主要的加密是AES( ECB),通过 MD5 sums命令(访问程序代码整合)生成密钥、密码以及secret(应用程序中的硬编码)
def encode(s):
abyte1 = get_md5(access + secret)
abyte2 = get_md5(secret + password)
key = abyte1 + abyte2
a = AES.new(key)
a = AES.new(key, AES.MODE_ECB)
res = a.encrypt(s)
encoded = base64.b64encode(res)
return encoded
def decode(data):
decoded = base64.b64decode(data)
abyte1 = get_md5(access + secret)
abyte2 = get_md5(secret + password)
key = abyte1 + abyte2
a = AES.new(key)
a = AES.new(key, AES.MODE_ECB)
res = a.decrypt(decoded)
return res
利用这些函数就可以在GET /ecus/rrc/uiStatus响应获得信息,然后就看到了这些
{'id': '/ecus/rrc/uiStatus',
'recordable': 0,
'type': 'uiUpdate',
'value': {'ARS': 'init',
'BAI': 'CH',
'BBE': 'false',
'BLE': 'false',
'BMR': 'false',
'CPM': 'auto',
'CSP': '31',
'CTD': '2014-12-26T12:34:27+00:00 Fr',
'CTR': 'room',
'DAS': 'off',
'DHW': 'on',
'ESI': 'off',
'FPA': 'off',
'HED_DB': '',
'HED_DEV': 'false',
'HED_EN': 'false',
'HMD': 'off',
'IHS': 'ok',
'IHT': '16.70',
'MMT': '15.5',
'PMR': 'false',
'RS': 'off',
'TAS': 'off',
'TOD': '0',
'TOR': 'on',
'TOT': '17.0',
'TSP': '17.0',
'UMD': 'clock'},
'writeable': 0}
更多的信息
当然信息不可能立刻显示出具体含义(其中三个变量名设置比较好),当然里面还是有一些比较明显的,比如CTD(可能代表像当前时间/日期)、DHW(生活用热水,在暖通领域内牵涉到建筑热负荷时会用到,一般会用日平均每人用量(因建筑功能而异)与人数来确定)。在这一部分,我分析了所有状态信息,并利用了温度监控系统,下面将会进行一些其它的事情(设置新温度等),然后再来看看作者的python 库。
在上面我们介绍了通信加密的信息,以及当前系统的状态。当然如果能利用Python程序设置温度、以及定时时间等,而这些就是要做的事情。我将会再一次的使用“中间人攻击”来监控应用程序。当发现温度改变的时候,你就会出现以下信息。
<message id="lj8Vq-31" to="rrcgateway_458921440@wa2-mz36-qrmzh6.bosch.de"
type="chat">
<subject>/heatingCircuits/hc1/manualTempOverride/temperature</subject><body>PUT /heatingCircuits/hc1/manualTempOverride/temperature HTTP:/1.0\nContent-Type: application/json\nContent-Length: 25\nUser-Agent: NefitEasy\n\n\n\nXmuIR7wCfDZpPrPkrb/CqQ==\n</body></message>
在整合XMPP协议(去掉头信息)之后,你就会看到这个
PUT /heatingCircuits/hc1/manualTempOverride/temperature
HTTP:/1.0
Content-Type: application/json
Content-Length: 25
User-Agent: NefitEasy
XmuIR7wCfDZpPrPkrb/CqQ==
在得到的信息最后,解码可以得到下面信息
{"value":16}\x00\x00\x00\x00
这看起来像是JSON,最后那些补位数据主要是出于对加密程序数据类型的长度考虑的。这可能和我的设置有关系,我将温度设置在了16度。
如果我设置了不同的数据(不同的数字信息),然后温度预设定会改变吗?
当然需要改变温度,取决于你选择的模式,这里有两种模式可供选择,当前状态信息(UMD)会告诉你处于哪种模式,手动或定时模式。如果处于前面那种模式,你可以发送一个简单的 PUT 请求/heatingCircuits/hc1/temperatureRoomManual以及JSON({“value”:21}),这样做主要是就取决于你想要的温度。
如果选择后者那种模式,你就需要设置“override temperature”(一个 PUT请求/heatingCircuits/hc1/manualTempOverride/temperature),然后需要设置“temperature override function”(一个PUT请求/heatingCircuits/hc1/manualTempOverride/status以及 JSON {“value”: “on”})。
如果你想要改变模式可以向 /heatingCircuits/hc1/usermode( JSON {“value”: “clock”} 或{“value”:”manual”})发出一个 PUT请求,当然你会得到一个回应,只要不出现错误信息,你会看到这些信息内容。
No Content
Content-Type: application/json
connection: close
而也可以通过发送其它一些信息来做一些更复杂的事情(例如改变定时器程序),但作者表示还没有去测试这些事情。但这些大致相同,他们可能利用了更复杂的JSON payload,并需要更多的信息来确认。现在我可以控制设备的基本状态(模式以及温度)。
作者到这里并没有提及太多有关Python的事情,但作者表示自己的大部分测试工作都是利用了 基于Python的sleekxmpp库,作者表示可以设计一个真正的有限状态机,来收发信息。而这些工作模式都是异步模式,看起来真的不容易。
所以为了发送信息以及解密文件作者创建了一个BaseWaveMessageBot class文件,并可以做出一些简单的错误处理。由于创建的文件有很多不同的内容,于是作者又在里面添加了一些新的内容(StatusBot 以及SetBot),利用它可以发出信息以及做出响应。作者将这些文件称为WaveThermo,作者需要在里面加入更多的内容,更新一些新功能。
作者提供的程序(点击我)
而作者只是在温控器上面测试过它,当然你可以利用作者的方法再结合实际情况去做出更多的方案。
揭秘如何用Python黑掉智能锅炉的更多相关文章
- 如何用Python从海量文本抽取主题?
摘自https://www.jianshu.com/p/fdde9fc03f94 你在工作.学习中是否曾因信息过载叫苦不迭?有一种方法能够替你读海量文章,并将不同的主题和对应的关键词抽取出来,让你谈笑 ...
- Python黑帽编程2.3 字符串、列表、元组、字典和集合
Python黑帽编程2.3 字符串.列表.元组.字典和集合 本节要介绍的是Python里面常用的几种数据结构.通常情况下,声明一个变量只保存一个值是远远不够的,我们需要将一组或多组数据进行存储.查询 ...
- Python黑帽编程 4.0 网络互连层攻击概述
Python黑帽编程 4.0 网络互连层攻击概述 是时候重新温习下下面这张图了. 图2 本章的内容核心包含上图中的网络层和传输层.TCP/IP是整个网络协议体系中的核心,因为从这里开始,数据传输从局域 ...
- 从“黑掉Github”学Web安全开发
Egor Homakov(Twitter: @homakov 个人网站: EgorHomakov.com)是一个Web安全的布道士,他这两天把github给黑了,并给github报了5个安全方面的bu ...
- Python黑科技:6行代码轻松搭建FTP服务器
Python 黑科技 六行代码轻松搭建个人FTP服务器 什么是FTP服务器? FTP (File Transfer Protocol) 是一个用于客户端与服务器之间文件的协议.利用FTP我们就能做到在 ...
- 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学
编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...
- 如何用 Python 和 API 收集与分析网络数据?
摘自 https://www.jianshu.com/p/d52020f0c247 本文以一款阿里云市场历史天气查询产品为例,为你逐步介绍如何用 Python 调用 API 收集.分析与可视化数据.希 ...
- Python 黑帽编程大纲(变化中)
Python 黑帽编程大纲(预览版) 教程说明: 本系列教程,采用的大纲母本为<Understanding Network Hacks Attack and Defense with Pytho ...
- Python黑帽编程 3.4 跨越VLAN
Python黑帽编程 3.4 跨域VLAN VLAN(Virtual Local Area Network),是基于以太网交互技术构建的虚拟网络,既可以将同一物理网络划分成多个VALN,也可以跨越物理 ...
随机推荐
- iOS-调用网页聊天、拨打电话
@property (nonatomic,strong) UIButton *but;@property (nonatomic,strong) UIButton *but1;@property (st ...
- sed工具的基本用法
sed文本处理工具的用法: 用法1:前置命令 | sed [选项] '条件指令' 用法2:sed [选项] '条件指令' 文件.. .. 认识sed工具的基本选项 sed命令的常用选项如下: -n(屏 ...
- 最新 汇量科技java校招面经 (含整理过的面试题大全)
从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿. 汇量科技等10家互联网公司的校招Offer,因为某些自身原因最终选择了 汇量科技.6.7月主要是做系统复习.项目复盘.Le ...
- 用elasticsearchdump备份恢复数据
1.安装elastic searchdump mkdir /data/nodejs cd /data/nodejs wget https://nodejs.org/dist/v10.16.2/node ...
- 《Brennan's Guide to Inline Assembly》学习笔记
原文见Brennan's Guide to Inline Assembly. AT&T语法 vs Intel语法 DJGPP是基于GCC的,因此它使用AT&T/UNIT语法,这和Int ...
- 【AtCoder】ARC070
ARC070 C - Go Home 题目大意:一只袋鼠第i秒可以向左或向右跳i步或者不跳,问从0跳到x的最小时间 就是1,2,3,4...k总和超过x的最小的k,因为如果超过了x的那部分需要减掉的那 ...
- C++ 简单实现 依赖注入(IOC)
由于C++ 不支持“反射机制”, 在C++中需要实现依赖注入或控制反转需要增加辅助程序.例如在Windows 开发程序中根据类名动态创建对象,需要在类定义中增加宏.本文主要介绍C++ Ioc的一种实现 ...
- hdu 6319 逆序建单调队列
题目传送门//res tp hdu 维护递增单调队列 根据数据范围推测应为O(n)的. 我们需要维护一个区间的信息,区间内信息是"有序"的,同时需要在O(1)的时间进行相邻区间的信 ...
- 多线程学习:win32多线程编程基本概念(转)
一.定义: 1.进程和线程的区别 进程:是程序的执行过程,具有动态性,即运行的程序就叫进程,不运行就叫程序 ,每个进程包含一到多个线程.线程:系统中的最小执行单元,同一进程中有多个线程,线程可以共享资 ...
- Photon Server 实现注册与登录(二) --- 服务端代码整理
一.有的代码前端和后端都会用到.比如一些请求的Code.使用需要新建项目存放公共代码. 新建项目Common存放公共代码: EventCode :存放服务端自动发送信息给客户端的code Operat ...