大家好,我是公众号「线下聚会游戏」作者HullQin,开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏。
Django,用过Channels,对它的底层依赖daphne有所了解,所以我直接选择了daphne。它是ASGI标准的一种实现。
daphne是一个非常轻量的选择,不像Django+Channels这套框架提供了很重的解决方案。daphne只提供了基础的ASGI实现,没有其它冗余的功能。就好比:我开发五子棋前端时,使用了SVG + Dom API,没有用React框架一样。
daphne要求我们以这样的格式定义一个服务:
# server.py
async def application(scope, receive, send):
# 处理websocket协议
if scope['type'] == 'websocket':
# 先接收第一个包,必须是建立连接的包(connect),否则拒绝服务
event = await receive()
if event['type'] != 'websocket.connect':
return
# 校验通过,发送accept,表明建立ws连接成功
await send({'type': 'websocket.accept'})
# 此后双方可以互相随时发消息。开启个无限循环
while True:
# 接收一个包
event = await receive()
# 如果是断开连接的请求,就结束循环
if event['type'] == 'websocket.disconnect':
break
# 这种方式可以读取包的文本内容
data = event['text']
# 这种方式可以发送一个包给浏览器,这里是把浏览器发来的包原封不动传回去
await send({'type': 'websocket.send', 'text': data})
运行方法:
pip install daphne
daphne -b 0.0.0.0 -p 8001 server:application
house
house = {}
编写玩家初次连接(进入房间)的逻辑:
import json
async def application(scope, receive, send):
if scope['type'] == 'websocket':
event = await receive()
if event['type'] != 'websocket.connect':
return
await send({'type': 'websocket.accept'})
# 建立连接后,要求前端发送一个EnterRoom事件,以json格式提供用户id和房间号room
event = await receive()
data = json.loads(event['text'])
if data['type'] != 'EnterRoom' or not data['id'] or not data['room']:
# 若前端发送的第一个事件不是这个,就报错,断开连接
await send({'type': 'websocket.close', 'code': 403})
return
room_id = data['room']
user_id = data['id']
# 看看房间号是否在house内,不在则创建一个room
if room_id not in house:
house[room_id] = {
'black': None,
'white': None,
'pieces': [],
'sends': [],
'users': [],
}
room = house[room_id]
old = False # 看玩家是不是老玩家(断线重连进来的)
if room['black'] == user_id or room['white'] == user_id:
old = True
if user_id in room['users']:
old_send = room['sends'][room['users'].index(user_id)]
room['sends'].remove(old_send)
room['users'].remove(user_id)
await old_send({'type': 'websocket.close', 'code': 4000})
else: # 说明玩家是第一次进,给他拿黑棋或白棋
if room['black'] is None:
room['black'] = user_id
elif room['white'] is None:
room['white'] = user_id
# 如果玩家没拿到黑棋也没拿到白旗,就是观战者
visiting = room['black'] != user_id and room['white'] != user_id
# 把玩家的send函数存到room里,方便其他玩家下棋时调用,从而广播下棋事件
room['sends'].append(send)
# 把玩家ID存进去
room['users'].append(user_id)
玩家进入房间后,我们需要给他通知一下这个房间的基本信息,例如是否已经开始了?当前场上的期局是怎样的?
await send({'type': 'websocket.send', 'text': json.dumps({
'type': 'InitializeRoomState',
'pieces': room['pieces'], # 场上棋子情况
'visiting': visiting, # 你是否是观战者
'black': room['black'] == user_id if not visiting else bool(len(room['pieces']) % 2), # 如果你在下棋:黑棋是你吗?如果你是观战者:黑棋是谁?
'ready': bool(room['black'] and room['white']), # 房间是否准备好开局了?只要有2个人同时在,就可以开了
})})
# 因为有人进入了房间,所以需要广播一下这个消息。
if not old and (room['black'] == user_id or room['white'] == user_id):
for _send in room['sends']:
if _send == send:
continue
await _send({'type': 'websocket.send', 'text': json.dumps({
'type': 'AddPlayer',
'ready': bool(room['black'] and room['white']),
})})
while True:
event = await receive()
# 有人断线了,处理一下。若房间空了,还要删掉房间,以防内存占用无限增大
if event['type'] == 'websocket.disconnect':
if send in room['sends']:
room['sends'].remove(send)
room['users'].remove(user_id)
if len(room['pieces']) == 0 and len(room['sends']) == 0:
del house[room_id]
break
# 有人发送了事件,接收一下
data = json.loads(event['text'])
# 如果是下棋事件,就改一下room的pieces数据,并广播给大家
if data['type'] == 'DropPiece':
room['pieces'].append((data['x'], data['y']))
for _send in room['sends']:
if _send == send: # 不需要给自己通知,所以跳过自己
continue
await _send({'type': 'websocket.send', 'text': json.dumps({
'type': 'DropPiece',
'x': data['x'],
'y': data['y'],
})})
当然,写好这些后,还需要测试,最好直接写好前端一起联调。我们下篇文章把前端的WebSocket逻辑补充一下。
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的