Fork me on GitHub

补完了真实之泪

真实世界里只能是男二这件事让我留下了眼泪 —— 《真实之泪》

以上大雾

总体来说还是不错的,当初为了OP リフレクティア 去补番,最后果然(再次)印证了音乐好的动画质量也不会差这件事

男主挺不错的,不过大部分人也不至于搞得跟诚哥一样。最后折纸飞机告别石动乃绘那段很赞。

男二真苦逼啊,太尼玛苦了。。。这才是真实

所以最后只有石动乃绘和哥哥没有Happy End,果然只能走上禁断的爱这个结局吗(喂

时不时还是会觉得狗血,果然还是不太适合看这类的动画,不过P.A. Works确实良心,让我下决心去把三部曲中剩下的花开给补了

结尾乃绘在鸡笼前面流泪的画面不能更赞(后面行人一直在动有木有,当然鸡“地面”也在动..) 真一郎进比吕美的房间那集分别用两个人的视角来展现简直让我惊呆了,太帅了,这个技巧简直太帅了!!这是本片让我印象最深刻的地方,光有这几分钟,就不枉补番了

Python优化函数执行的技巧:用local变量代替global

这个技巧太简单了,但是直到阅读这篇文章的时候才意识到可以这么做

http://effbot.org/zone/default-values.htm

里面有一段话是这么说的

and, for highly optimized code, local rebinding of global names:
import math

def this_one_must_be_fast(x, sin=math.sin, cos=math.cos):
...

意思是说,如果需要严格地优化(运行时间),可以把gloabl的函数/变量传入某个函数,这样对于这个函数来讲,它们就变成本地的了。Python在搜索变量时按照Local->Enclosing->Global->Builtin的路线进行,因为local最先搜索的,所以用局部变量显然是最快的。如果要弄清这个问题,可以搜索"LEGB",在这里只要知道使用局部变量最快就可以了。

测试了一下,还是差别不小的

import timeit

setup="""
import math

def calc(a, b):
    math.sin(a) + math.cos(b)

def calc2(a, b, sin=math.sin, cos=math.cos):
    sin(a) + sin(b)
"""

def test():
    print(timeit.timeit('calc(1, 2)', setup))
    print(timeit.timeit('calc2(1, 2)', setup))

if __name__ == '__main__':
    test()

calc耗时0.793981s,calc2耗时0.522706s。这里默认测试10000次,可以想象,如果代码中的变量更多,计算更复杂,那么时间的差别会更大。

Python实现STUN+TURN+P2P聊天

作为技术验证,最近实现了一下NAT穿透,并在此基础上完成了P2P聊天的客户端(虽然只能在命令行中打字)。理论上能不论电脑处于何种类型NAT设备后,均可以实现P2P聊天。代码和使用方法参见
https://github.com/laike9m/PyPunchP2P
这篇文章主要(简单)介绍一下必要的背景知识和原理,github上已有的内容就不再说明。

什么是NAT穿透?

穿越防火墙技术

什么是STUN, TURN?

WebRTC and the Ocean of Acronyms

如何实现NAT穿透?

Peer-to-Peer Communication Across Network Address Translators

喂这就算介绍完了吗(╯‵□′)╯︵┻━┻
咳咳,总之原理部分就这样吧


PyPunchP2P工作流程

PART ONE: 连接

假定你已经运行了server.py,并让其监听1234这个端口。客户端A首先会通过从pystun里面弄出来的那部分代码检测自己的NAT类型

nat_type, _, _ = self.get_nat_type()

然后通知服务器端,发起连接请求,同时告知服务器自己的NAT类型。client.py的第三个参数是pool值,这个值是用来匹配客户端用的。如果说两个发起连接的客户端有一样的pool值,那么就认为它们是希望通信的客户端。指定的pool值也会发送给服务器。

self.request_for_connection(nat_type_id=NATTYPE.index(nat_type))

其中

NATTYPE = (FullCone, RestrictNAT, RestrictPortNAT, SymmetricNAT)

如果一切顺利,服务器接到了这个请求,那么它会保存客户端A的信息(addr, pool, nat_type),同时继续等待另一个客户端发起请求。
好,现在客户端B也发了个请求过来,并且pool值和之前相同。服务器意识到A和B希望和对方通信,于是分别把A和B的信息发给对方。显然,这就是STUN server的本职工作。

a, b = poolqueue[pool].addr, addr  
nat_type_id_a, nat_type_id_b = poolqueue[pool].nat_type_id, nat_type_id  
sockfd.sendto(addr2bytes(a, nat_type_id_a), b)  
sockfd.sendto(addr2bytes(b, nat_type_id_b), a)

至此第一部分的工作就完成了,clientA和clientB已经连接起来了,祈祷到这里一切顺利吧。

PART TWO: 通信

注意到我们之前并没有利用NAT类型信息,下面就需要了。让我们分情况看看:

至少有一方是symmetric NAT

这是最优先考虑的情况,因为symmetric NAT是最让人头大的情况。这种情况下只能通过服务器来转发消息。于是我们的服务器华丽变身为TURN server。当然,服务器不可能什么包都转发,所以这种通信方式下双方的消息带有一个msg的前缀,目的就是标识出这是希望服务器转发的消息而不是PART ONE中发起连接的那种消息。我们的服务器是不可能使用多个端口的,因为如果端口和之前建立连接时不同,那么服务器转发的消息就会直接被symmetric NAT丢弃了。既然和之前使用的是一个socket,那么标识显然是必要的。
还有个问题是转发给谁。这一点无须担心,在建立连接时服务器已经把两个client配对了,如果是从一边来的消息,它会自动转发给另一边。

不存在symmetric NAT,至少有一方是restrict NAT

这里所指的restrict NAT包含了 RestrictNAT 和 RestrictPortNAT 两种情况。这时,是restrict NAT的那一方需要做一件事,那就是持续发包!不妨称这种包为punching包,设定为0.5s一次。另一方,不管是不是restrict NAT,接到punching包之后都会自动给出回复。原理上不难理解,因为受限的一方只有持续发包,才能让NAT设备知道对方是“已知”的,而一旦接收到回复,持续发包停止,可以开始聊天。

双方都是Full Cone

这种情况简直是天堂,直接向对方发送就行了,so easy.
实际上,大部分情况都是这种。看来生活还是有希望的╮(╯▽╰)╭

大概就是这样了。再次声明,代码并未在真实情况下测试过,所以未必一定能正常工作。可以保证的是原理正确,以及在模拟状况下测试正常。目前已经测试过了,各种状况下都能正常工作,除非路由器或者防火墙被设定为阻挡来自某些IP的UDP报文,那确实无能为力了。另外,我不知道ICE是具体是怎么工作的,到处都说是对STUN+TURN的封装,难不成就和这个差不多?


top