Python 异步多个并发服务器
我尝试使用Python的asyncio一起运行多个服务器,在它们之间传递数据。对于我的具体情况,我需要一个带有WebSocket的web服务器,一个到外部设备的UDP连接,以及数据库和其他交互。我可以找到几乎任何一个单独的例子,但我正在努力找出正确的方法,让它们与在它们之间推送的数据同时运行 我在这里找到的最接近的例子是:(尽管我无法使它在Python3.6上运行) 关于更具体的示例:我将如何从以下aiohttp示例代码: 以及以下TCP echo服务器示例(): 并将它们组合成一个脚本,其中通过WebSocket或TCP echo服务器接收的任何消息都会发送到其中一个的所有客户端Python 异步多个并发服务器,python,python-3.x,python-asyncio,aiohttp,Python,Python 3.x,Python Asyncio,Aiohttp,我尝试使用Python的asyncio一起运行多个服务器,在它们之间传递数据。对于我的具体情况,我需要一个带有WebSocket的web服务器,一个到外部设备的UDP连接,以及数据库和其他交互。我可以找到几乎任何一个单独的例子,但我正在努力找出正确的方法,让它们与在它们之间推送的数据同时运行 我在这里找到的最接近的例子是:(尽管我无法使它在Python3.6上运行) 关于更具体的示例:我将如何从以下aiohttp示例代码: 以及以下TCP echo服务器示例(): 并将它们组合成一个脚本,其中通
我如何添加一段代码(比如)每秒向所有客户端发送一条消息(为了讨论当前的时间戳)?首先,您需要将所有的协同程序放入一个事件循环中。首先,您可以避免使用方便的API启动事件循环,例如
run\u app
。不要使用web.run\u app(app)
,而是编写如下内容:
runner = aiohttp.web.AppRunner(app)
loop.run_until_complete(runner.setup())
# here you can specify the listen address and port
site = aiohttp.web.TCPSite(runner)
loop.run_until_complete(site.start())
然后运行echo服务器安装程序,两者都准备好共享asyncio事件循环。在脚本结束时,使用loop.run_forever()
(或在应用程序中有意义的任何其他方式)启动事件循环
要向客户端广播信息,请创建广播协程并将其添加到事件循环:
# Broadcast data is transmitted through a global Future. It can be awaited
# by multiple clients, all of which will receive the broadcast. At each new
# iteration, a new future is created, to be picked up by new awaiters.
broadcast_data = loop.create_future()
async def broadcast():
global broadcast_data
while True:
broadcast_data.set_result(datetime.datetime.now())
broadcast_data = loop.create_future()
await asyncio.sleep(1)
loop.create_task(broadcast())
最后,在为客户端创建的每个协同程序中等待广播,例如handle\u echo
:
def handle_echo(r, w):
while True:
data = await broadcast_data
# data contains the broadcast datetime - send it to the client
w.write(str(data))
修改websockets处理程序协程,以同样的方式等待和中继广播数据应该很简单。根据@user4815162342的建议,这是我的“工作”代码。我把它作为一个答案发布,因为它是一个完整的工作脚本,可以满足我原始问题的所有要求,但它并不完美,因为它目前还没有完全退出 运行时,它将在端口8080上接受web连接,在端口8081上接受tcp(例如telnet)连接。通过其web表单或telnet接收的任何消息都将广播到所有连接。此外,每5秒广播一次时间 关于如何干净地退出的建议(在建立web连接的情况下,ctrl+C会生成多个“任务已销毁,但它正在等待!”错误)将不胜感激,以便我可以更新此答案 (代码相当长,因为它包含websockets组件的嵌入式HTML和JS。)
导入异步IO
从aiohttp导入web
进口aiohttp
导入日期时间
进口稀土
队列=[]
loop=asyncio.get\u event\u loop()
#广播数据通过全球未来网络传输。这是可以等待的
#通过多个客户端,所有客户端都将接收广播。在每一个新的
#迭代,一个新的未来被创造出来,被新的等待者接受。
广播数据=循环。创建未来()
def广播(msg):
全球广播数据
msg=str(msg)
打印(“>>”,msg)
如果未广播_data.done():
广播数据。设置结果(msg)
广播数据=循环。创建未来()
#虚拟循环,每5秒广播一次时间
异步def broadcastLoop():
尽管如此:
广播(datetime.datetime.now())
#打印('#',结束='',刷新=真)
等待异步睡眠(5)
#www请求的处理程序
异步def wwwhandler(r):
host=re.search('https?:/([^/]+)/',str(r.url)).group(1)
name=r.match_info.get('name','Anonymous')
text=”“”
WebSocket PHP开放组聊天应用程序
var输出;
var-websocket;
函数WebSocketSupport(){
if(browserSupportsWebSockets()==false){
document.getElementById(“ws_支持”).innerHTML=“对不起!您的web浏览器不支持web套接字”;
var元素=document.getElementById(“包装器”);
element.parentNode.removeChild(元素);
返回;
}
输出=document.getElementById(“聊天盒”);
websocket=newwebsocket('ws:{{HOST}}/ws');
websocket.onopen=函数(e){writeToScreen(“您已成功连接到服务器”);};
websocket.onmessage=函数(e){onmessage(e)};
websocket.onerror=函数(e){onerror(e)};
}
消息(e){writeToScreen(''+e.data+'');}上的函数
函数onError(e){writeToScreen('ERROR:'+e.data);}
函数doSend(消息){
var validationMsg=userInputSupplied();
如果(validationMsg!=''){
警报(validationMsg);
返回;
}
var chatname=document.getElementById('chatname').value;
//document.getElementById('msg')。value=“”;
//document.getElementById('msg').focus();
var msg=chatname+'表示:'+消息;
websocket.send(msg);
写入屏幕(msg);
}
功能写入屏幕(消息){
var pre=document.createElement(“p”);
pre.style.wordWrap=“断开单词”;
pre.innerHTML=消息;
输出.appendChild(pre);
}
函数userInputSupplied(){
var chatname=document.getElementById('chatname').value;
var msg=document.getElementById('msg').value;
如果(chatname==''{return'请输入您的用户名';}
如果(msg==''{return'请发送消息';}
返回“”;
}
函数浏览器支持SWebSockets(){
if(“窗口中的WebSocket”){return true;}else{return false;}
}
欢迎使用WebSocket PHP开放群聊天应用v1
名称
"""
text=text.replace({{HOST}}',HOST)
return web.Response(text=text,headers={'content-type':'text/html'})
#websocket连接的处理程序
异步定义wshandler(r):
#获取websocket连接
ws=web.WebSocketResponse()
等待ws.准备(r)
#附加它
# Broadcast data is transmitted through a global Future. It can be awaited
# by multiple clients, all of which will receive the broadcast. At each new
# iteration, a new future is created, to be picked up by new awaiters.
broadcast_data = loop.create_future()
async def broadcast():
global broadcast_data
while True:
broadcast_data.set_result(datetime.datetime.now())
broadcast_data = loop.create_future()
await asyncio.sleep(1)
loop.create_task(broadcast())
def handle_echo(r, w):
while True:
data = await broadcast_data
# data contains the broadcast datetime - send it to the client
w.write(str(data))
import asyncio
from aiohttp import web
import aiohttp
import datetime
import re
queues = []
loop = asyncio.get_event_loop()
# Broadcast data is transmitted through a global Future. It can be awaited
# by multiple clients, all of which will receive the broadcast. At each new
# iteration, a new future is created, to be picked up by new awaiters.
broadcast_data = loop.create_future()
def broadcast(msg):
global broadcast_data
msg = str(msg)
print(">> ", msg)
if not broadcast_data.done():
broadcast_data.set_result(msg)
broadcast_data = loop.create_future()
# Dummy loop to broadcast the time every 5 seconds
async def broadcastLoop():
while True:
broadcast(datetime.datetime.now())
# print('#',end='',flush=True)
await asyncio.sleep(5)
# Handler for www requests
async def wwwhandler(r):
host = re.search('https?://([^/]+)/', str(r.url)).group(1)
name = r.match_info.get('name', "Anonymous")
text = """<!DOCTYPE html>
<html>
<head>
<title>WebSocket PHP Open Group Chat App</title>
<!-- <link type="text/css" rel="stylesheet" href="style.css" /> -->
<script>
var output;
var websocket;
function WebSocketSupport() {
if (browserSupportsWebSockets() === false) {
document.getElementById("ws_support").innerHTML = "<h2>Sorry! Your web browser does not supports web sockets</h2>";
var element = document.getElementById("wrapper");
element.parentNode.removeChild(element);
return;
}
output = document.getElementById("chatbox");
websocket = new WebSocket('ws:{{HOST}}/ws');
websocket.onopen = function(e) { writeToScreen("You have have successfully connected to the server"); };
websocket.onmessage = function(e) { onMessage(e) };
websocket.onerror = function(e) { onError(e) };
}
function onMessage(e) { writeToScreen('<span style="color: blue;"> ' + e.data + '</span>'); }
function onError(e) { writeToScreen('<span style="color: red;">ERROR:</span> ' + e.data); }
function doSend(message) {
var validationMsg = userInputSupplied();
if (validationMsg !== '') {
alert(validationMsg);
return;
}
var chatname = document.getElementById('chatname').value;
// document.getElementById('msg').value = "";
// document.getElementById('msg').focus();
var msg = chatname + ' says: ' + message;
websocket.send(msg);
writeToScreen(msg);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
function userInputSupplied() {
var chatname = document.getElementById('chatname').value;
var msg = document.getElementById('msg').value;
if (chatname === '') { return 'Please enter your username'; }
if (msg === '') { return 'Please the message to send'; }
return '';
}
function browserSupportsWebSockets() {
if ("WebSocket" in window) { return true; } else { return false; }
}
</script>
</head>
<body onload="javascript:WebSocketSupport()">
<div id="ws_support"></div>
<div id="wrapper">
<div id="menu">
<h3 class="welcome">Welcome to WebSocket PHP Open Group Chat App v1</h3>
</div>
<div id="chatbox"></div>
<div id ="controls">
<label for="name"><b>Name</b></label>
<input name="chatname" type="text" id="chatname" size="67" placeholder="Type your name here" value="MyName" />
<input name="msg" type="text" id="msg" size="63" placeholder="Type your message here" value="Test" />
<input name="sendmsg" type="submit" id="sendmsg" value="Send" onclick="doSend(document.getElementById('msg').value)" />
</div>
</div>
</body>
</html>"""
text = text.replace('{{HOST}}', host)
return web.Response(text=text, headers={'content-type':'text/html'})
# Handler for websocket connections
async def wshandler(r):
# Get the websocket connection
ws = web.WebSocketResponse()
await ws.prepare(r)
# Append it to list so we can manage it later if needed
r.app['websockets'].append(ws)
try:
# Create the broadcast task, and add it to list for later management
echo_task = asyncio.Task(echo_loop(ws))
r.app['tasks'].append(echo_task)
# Tell the world we've connected
# Note: Connecting client won't get this message, not really sure why
broadcast('Hello {}'.format(r.remote))
# await ws.send_str('Hello {}'.format(r.remote))
# Loop through any messages we get from the client
async for msg in ws:
# .. and broadcast them
if msg.type == web.WSMsgType.TEXT:
print('<< ', msg.data)
broadcast(msg.data)
# await ws.send_str("Hello, {}".format(msg.data))
# elif msg.type == web.WSMsgType.BINARY:
# await ws.send_bytes(msg.data)
elif msg.type == web.WSMsgType.CLOSE:
print('WS Connection closed')
break
elif msg.type == web.WSMsgType.ERROR:
print('WS Connection closed with exception %s' % ws.exception())
break
else:
print('WS Connection received unknown message type %2' % msg.type)
# ws has stopped sending us data so broadcast goodbye
broadcast('Goodbye {}'.format(r.remote))
except GeneratorExit:
pass
finally:
# Close the ws and remove it from the list
await ws.close()
r.app['websockets'].remove(ws)
# Cancel the task and remove it from the list
# Note: cancel() only requests cancellation, it doesn't wait for it
echo_task.cancel()
r.app['tasks'].remove(echo_task)
return ws
# ws broadcast loop: Each WS connection gets one of these which waits for broadcast data then sends it
async def echo_loop(ws):
while True:
msg = await broadcast_data
await ws.send_str(str(msg))
# web app shutdown code: cancels any open tasks and closes any open websockets
# Only partially working
async def on_shutdown(app):
print('Shutting down:', end='')
for t in app['tasks']:
print('#', end='')
if not t.cancelled():
t.cancel()
for ws in app['websockets']:
print('.', end='')
await ws.close(code=aiohttp.WSCloseCode.GOING_AWAY, message='Server Shutdown')
print(' Done!')
# Code to handle TCP connections
async def echo_loop_tcp(writer):
while True:
msg = await broadcast_data
writer.write( (msg + "\r\n").encode() )
await writer.drain()
async def handle_echo(reader, writer):
echo_task = asyncio.Task(echo_loop_tcp(writer))
while True:
data = await reader.readline()
if not data:
break
message = data.decode().strip()
# addr = writer.get_extra_info('peername')
broadcast(message)
print("Connection dropped")
echo_task.cancel()
tcpServer = loop.run_until_complete(asyncio.start_server(handle_echo, '0.0.0.0', 8081, loop=loop))
print('Serving on {}'.format(tcpServer.sockets[0].getsockname()))
# The application code:
app = web.Application()
app['websockets'] = []
app['tasks'] = []
app.router.add_get('/ws', wshandler)
app.router.add_get('/', wwwhandler)
app.router.add_get('/{name}', wwwhandler)
app.on_shutdown.append(on_shutdown)
def main():
# Kick off the 5s loop
tLoop=loop.create_task(broadcastLoop())
# Kick off the web/ws server
async def start():
global runner, site
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '0.0.0.0', 8080)
await site.start()
async def end():
await app.shutdown()
loop.run_until_complete(start())
# Main program "loop"
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
# On exit, kill the 5s loop
tLoop.cancel()
# .. and kill the web/ws server
loop.run_until_complete( end() )
# Stop the main event loop
loop.close()
if __name__ == '__main__':
main()