Is there a way to memorize all the client instances (in a dictionary for example), and send a message/communicate on demand to whichever client needed?
import asyncio
class EchoServerClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.peername = transport.get_extra_info('peername')
print('Connection from {}'.format(self.peername))
def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))
print('Send: {!r}'.format(message))
self.transport.write(data)
def connection_lost(self, exc):
print('Lost connection of {}'.format(self.peername))
self.transport.close()
loop = asyncio.get_event_loop()
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
If I understand your question correctly, you want to be able to maintain a map of connections and then send data to the connections at a later time.
import asyncio
from functools import partial
from random import choice, randint
class Echo(asyncio.Protocol):
def __init__(self, client_map):
self.client_map = client_map
def connection_made(self, transport):
self.transport = transport
peername = transport.get_extra_info("peername")
print(f"[x] connected to {peername}")
self.client_map[transport.get_extra_info("peername")] = self
def connection_lost(self, exc):
peername = self.transport.get_extra_info("peername")
print(f"[!] disconnected from {peername}")
del self.client_map[peername]
def data_received(self, data):
print(f"<<< {data}")
self.transport.write(b">>> " + data)
def protocol_factory(client_map):
proto = Echo(client_map)
return proto
def random_hello(loop, client_map):
delay = randint(1, 10)
if len(client_map) > 0:
peername = choice(sorted(client_map))
client_map[peername].transport.write(b">>> random hello\n")
loop.call_later(
delay, random_hello, loop, client_map,
)
async def main():
loop = asyncio.get_running_loop()
client_map = {}
_protocol_factory = partial(protocol_factory, client_map=client_map)
server = await loop.create_server(_protocol_factory, "127.0.0.1", 8888,)
random_hello(loop, client_map)
async with server:
await server.serve_forever()
asyncio.run(main())
What makes this all work is protocol_factory(). You pass in a client_map: dict, which will then be a shared variable in all the protocol objects, and as clients connect, the protocol object will be stored under the peername key. The random_hello() function demonstrates sending data to a protocol. Pass in client_map anywhere you want to be able to access connected clients and send data over the network.
Related
I have an echo client based on the python documentation as follow:
import asyncio
class EchoClientProtocol(asyncio.Protocol):
def __init__(self, message, on_con_lost):
self.message = message
self.on_con_lost = on_con_lost
self.loop = asyncio.get_running_loop()
def connection_made(self, transport):
self.transport = transport
transport.write(self.message.encode())
print('Data sent: {!r}'.format(self.message))
def write_to_device(self):
self.transport.write(self.message.encode())
def data_received(self, data):
print('Data received: {!r}'.format(data.decode()))
self.loop.call_later(1.0 , self.write_to_device)
def connection_lost(self, exc):
print('The server closed the connection')
self.on_con_lost.set_result(True)
async def main():
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = 'Hello World!'
transport, protocol = await loop.create_connection(
lambda: EchoClientProtocol(message, on_con_lost),
'127.0.0.1', 8888)
try:
await on_con_lost
finally:
transport.close()
asyncio.run(main())
Everytime it receives a message from server, it sends again the message after 1 second. My question is: How can I change the message to send once the connection is made?
Oki, just simply modifying the protocol instance as suggested works... here is a simple proof:
import asyncio
class EchoClientProtocol(asyncio.Protocol):
def __init__(self, message, on_con_lost):
self.message = message
self.on_con_lost = on_con_lost
self.loop = asyncio.get_running_loop()
def connection_made(self, transport):
self.transport = transport
transport.write(self.message.encode())
print('Data sent: {!r}'.format(self.message))
def write_to_device(self):
print('Data sent: {!r}'.format(self.message))
self.transport.write(self.message.encode())
def data_received(self, data):
print('Data received: {!r}'.format(data.decode()))
self.loop.call_later(1, self.write_to_device)
def connection_lost(self, exc):
print('The server closed the connection')
self.on_con_lost.set_result(True)
async def changeMessage(message, protocol):
await asyncio.sleep(10)
print('Changing message')
protocol.message = message
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = 'Hello World!'
transport, protocol = await loop.create_connection(
lambda: EchoClientProtocol(message, on_con_lost),
'127.0.0.1', 65432)
await changeMessage('Message is now changed', protocol)
# Wait until the protocol signals that the connection
# is lost and close the transport.
try:
await on_con_lost
finally:
transport.close()
asyncio.run(main())
python3 client.py
/home/aijax/.local/lib/python3.6/site-packages/socketio/client.py:592: RuntimeWarning: coroutine 'initial' was never awaited
self._handle_event(pkt.namespace, pkt.id, pkt.data)
connection established
despite of having the await I'm getting the error
PS: I have little to no knowledge of Async-io of python I kinda have finish this task overnight for a school proj
my client.py
import socketio
import time
sio = socketio.Client()
val={"num":35}
#sio.event
def connect():
print('connection established')
#sio.event
def disconnect():
print('disconnected from server')
#sio.event
async def initial(sid):
global val,sio
print("hey there!! ")
await sio.emit('clientUpdate',val)
#sio.event
async def serverAggregate(avg):
global val
val["num"] = (avg["num"] + val["num"])/2
# print(val)
print("kaada ",avg)
time.sleep(4)
await sio.emit('clientUpdate',val)
# await
sio.connect('http://0.0.0.0:8080')
# sio.wait()
# sio.my_message()
sio.wait()
My task is to create a server and many clients which send a number to server and server aggregates that and sends it back to the client and perform a local update and send back the number
my server.py
from aiohttp import web
import socketio
import time
import threading
# creates a new Async Socket IO Server
sio = socketio.AsyncServer()
users_count=0 #num of clients
temp_count=users_count
avg={"num":0}
temp_sum=0
initFlag=True
# Creates a new Aiohttp Web Application
# app = web.Application()
async def handle(request):
print(request)
name = request.match_info['name']
text = "Hello, " + name
return web.Response(text=text)
app = web.Application()
sio.attach(app)
async def resetter():
global avg,temp_sum,users_count,temp_count,sio
avg = temp_sum/users_count
temp_count = users_count
temp_sum = 0
print("broadcast requests to clients")
time.sleep(2)
await sio.emit('serverAggregate',avg,broadcast = True, include_self = False)
#sio.on('clientUpdate')
async def serverAggregate(sid, data):
global temp_count,users_count,initFlag,sio,resetter
if initFlag:
temp_count=users_count
initFlag=False
temp_count=temp_count -1
if(temp_count==0):
await resetter()
else:
print("message ", data)
global temp_sum
temp_sum = temp_sum + data["num"]
print("message ", data)
#sio.event
async def connect(sid, environ):
global users_count,sio
print("connect ", sid)
users_count=users_count+1
# time.sleep(3)
# print("say something")
time.sleep(2)
await sio.emit('initial')
# await sio.emit('intialize', broadcast = True, include_self = False)
#sio.event
def disconnect(sid):
global users_count
print('disconnect ', sid)
users_count=users_count-1
app.add_routes([web.get('/', handle),
web.get('/{name}', handle)])
# app.router.add_static('/static', 'static')
class BgProc(threading.Thread):
def __init__(self, sio):
# calling superclass init
threading.Thread.__init__(self)
# self.text = text
self.sio = sio
def run(self):
time.sleep(5)
print("sending broadcast to all clients ")
self.sio.emit("initial",broadcast = True, include_self = False)
if __name__ == '__main__':
web.run_app(app)
You are using the socketio.Client() class which is the standard Python client. If you want to write an asyncio application, you must use socketio.AsyncClient().
So this should be fairly simple, i have a client that connects to a server, and it can receive messages from the server just fine. However i need to be able to send messages to the server. I am using asyncio to handle these asynchronously but i have a problem with that. How do i get the user input to my client so it can use its transport to send the data up to the server. Heres the code.
import asyncio, zen_utils, json
from struct import *
class ChatClient(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.data = b''
print('Accepted connection from {}'.format(self.address))
self.username = b'jacksonm'
json_name = b'{"USERNAME": "'+self.username+b'"}'
length = len(json_name)
code_len = pack(b'!I', length)
message = code_len + json_name
self.json_loaded = False
self.next_length = -1
self.transport.write(message)
def data_received(self, data):
self.data += data
if (self.json_loaded == False):
self.compile_server_data(data)
elif(self.json_loaded):
if (len(self.data) > 4 and self.next_length == -1):
self.next_length = self.data[:4]
self.next_length = unpack(b'!I', self.next_length)[0]
print("LENGTH: ", self.next_length)
elif (len(self.data) >= self.next_length):
self.data = self.data[4:]
print("MESSAGE: ", self.data)
self.next_length = -1
def compile_server_data(self, data):
if (self.data.find(b'}') != -1):
start_index = self.data.find(b'{')
end_index = self.data.find(b'}')
self.json_data = self.data[start_index:end_index + 1]
self.data = self.data[end_index + 1:]
self.json_data = self.json_data.decode('ascii')
self.json_data = json.loads(self.json_data)
self.json_loaded = True
self.print_server_status()
def send_message(self, message):
message = message.encode('ascii')
length = len(message)
code_len = pack(b'!I', length)
message = code_len + message
def parse_message(self, raw_message):
message = {}
message['SRC'] = self.username
message['DEST'] = b'ALL'
message['TIMESTAMP'] = int(time.time())
message['CONTENT'] = b'test_message'
json_message = json.loads(message)
print (json_message)
def print_server_status(self):
print ("USERS:")
for user in self.json_data["USER_LIST"]:
print(user)
print()
print("MESSAGES:")
for message in self.json_data["MESSAGES"][-10:]:
print ("From: ", message[0], " ", "To: ", message[1])
print ("Message: ", message[3])
print()
def get_inital_data(self):
pass
def connection_lost(self, exc):
if exc:
print('Client {} error: {}'.format(self.address, exc))
elif self.data:
print('Client {} sent {} but then closed'
.format(self.address, self.data))
else:
print('Client {} closed socket'.format(self.address))
#asyncio.coroutine
def handle_user_input(loop):
"""reads from stdin in separate thread
if user inputs 'quit' stops the event loop
otherwise just echos user input
"""
while True:
message = yield from loop.run_in_executor(None, input, "> ")
if message == "quit":
loop.stop()
return
print(message)
if __name__ == '__main__':
address = zen_utils.parse_command_line('asyncio server using callbacks')
loop = asyncio.get_event_loop()
coro = loop.create_connection(ChatClient, *address)
server = loop.run_until_complete(coro)
# Start a task which reads from standard input
asyncio.async(handle_user_input(loop))
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
server.close()
loop.close()
This is working in my tests by changing one line, using Python 3.4:
Change the run_until_complete() call to receive a (transport,protocol) pair instead of a single value.
Specifically, change server = loop.run_until_complete(coro) to transport, protocol = loop.run_until_complete(coro), because the call executes the coroutine and returns its value which is a pair of items. The first item is the server object, and the second entry is a protocol object which is an instance of ChatClient.
Run the server then the client in different command windows.
Client code:
import asyncio, json #, zen_utils
from struct import *
class ChatClient(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.data = b''
print('Accepted connection from {}'.format(self.address))
def data_received(self, data):
pass
def compile_server_data(self, data):
pass
def send_message(self, message):
message = message.encode('ascii')
self.transport.write(message)
def parse_message(self, raw_message):
pass
def print_server_status(self):
pass
def get_inital_data(self):
pass
def connection_lost(self, exc):
if exc:
print('Client {} error: {}'.format(self.address, exc))
elif self.data:
print('Client {} sent {} but then closed'
.format(self.address, self.data))
else:
print('Client {} closed socket'.format(self.address))
#asyncio.coroutine
def handle_user_input(loop, protocol):
"""reads from stdin in separate thread
if user inputs 'quit' stops the event loop
otherwise just echos user input
"""
while True:
message = yield from loop.run_in_executor(None, input, "> ")
if message == "quit":
loop.stop()
return
print(message)
protocol.send_message(message)
if __name__ == '__main__':
address = "127.0.0.1"
port = 82
loop = asyncio.get_event_loop()
coro = loop.create_connection(ChatClient, address, port) #*address
transport, protocol = loop.run_until_complete(coro)
# Start a task which reads from standard input
asyncio.async(handle_user_input(loop,protocol))
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
transport.close()
loop.close()
Server code, credit goes to this site:
import socket
host = '' # Symbolic name meaning all available interfaces
port = 82 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(1)
conn, addr = s.accept()
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data: break
print( 'Received ', repr(data) )
if repr(data)=="stop": break
conn.sendall(data)
conn.close()
I would like to make a ReconnectingClientFactory with asyncio. In particular to handle the case that the server is not available when the client is started in which case the ReconnectingClientFactory will keep trying. That is something that the asyncio.events.create_connection does not do.
Concretely:
The EchoClient example would be fine.
The crux is how the connection is made.
factory = EchoClientFactory('ws://127.0.0.1:5678')
connectWS(factory)
in the case of the twisted version with ReconnectingClientFactory.
Vs
factory = EchoClientFactory(u"ws://127.0.0.1:5678")
factory.protocol = SecureServerClientProtocol
loop = asyncio.get_event_loop()
# coro = loop.create_connection(factory, 'ws_server', 5678)
coro = loop.create_connection(factory, '127.0.0.1', 5678)
loop.run_until_complete(asyncio.wait([
alive(), coro
]))
loop.run_forever()
loop.close()
Or similar with the asycnio version.
The problem is that in the asyncio version the connection is established by asyncio.events.create_connection which simply fails if the server is not available.
How can I reconcile the two?
Many thanks
I think I get what you want. Here's the code and example based on asyncio TCP echo client protocol example.
import asyncio
import random
class ReconnectingTCPClientProtocol(asyncio.Protocol):
max_delay = 3600
initial_delay = 1.0
factor = 2.7182818284590451
jitter = 0.119626565582
max_retries = None
def __init__(self, *args, loop=None, **kwargs):
if loop is None:
loop = asyncio.get_event_loop()
self._loop = loop
self._args = args
self._kwargs = kwargs
self._retries = 0
self._delay = self.initial_delay
self._continue_trying = True
self._call_handle = None
self._connector = None
def connection_lost(self, exc):
if self._continue_trying:
self.retry()
def connection_failed(self, exc):
if self._continue_trying:
self.retry()
def retry(self):
if not self._continue_trying:
return
self._retries += 1
if self.max_retries is not None and (self._retries > self.max_retries):
return
self._delay = min(self._delay * self.factor, self.max_delay)
if self.jitter:
self._delay = random.normalvariate(self._delay,
self._delay * self.jitter)
self._call_handle = self._loop.call_later(self._delay, self.connect)
def connect(self):
if self._connector is None:
self._connector = self._loop.create_task(self._connect())
async def _connect(self):
try:
await self._loop.create_connection(lambda: self,
*self._args, **self._kwargs)
except Exception as exc:
self._loop.call_soon(self.connection_failed, exc)
finally:
self._connector = None
def stop_trying(self):
if self._call_handle:
self._call_handle.cancel()
self._call_handle = None
self._continue_trying = False
if self._connector is not None:
self._connector.cancel()
self._connector = None
if __name__ == '__main__':
class EchoClientProtocol(ReconnectingTCPClientProtocol):
def __init__(self, message, *args, **kwargs):
super().__init__(*args, **kwargs)
self.message = message
def connection_made(self, transport):
transport.write(self.message.encode())
print('Data sent: {!r}'.format(self.message))
def data_received(self, data):
print('Data received: {!r}'.format(data.decode()))
def connection_lost(self, exc):
print('The server closed the connection')
print('Stop the event loop')
self._loop.stop()
loop = asyncio.get_event_loop()
client = EchoClientProtocol('Hello, world!', '127.0.0.1', 8888, loop=loop)
client.connect()
loop.run_forever()
loop.close()
I'm on Python 3.4 making a practice app to learn Python's asyncio module.
I've got several modules in my application:
app.py
import asyncio
import logging
class Client(asyncio.Protocol):
def __init__(self, loop):
self.loop = loop
def connection_made(self, transport):
print('Connection with server established')
self.transport = transport
def data_received(self, data):
data = data.decode()
print('Received: ', data)
if not int(data):
print('Stopping loop')
self.loop.stop()
else:
message = 'remove one'.encode()
self.transport.write(message)
print('Sent:', message)
def connection_lost(self, exc):
print('Connection with server lost.')
self.loop.stop()
loop = asyncio.get_event_loop()
fn = loop.create_connection(
lambda: Client(loop), '127.0.0.1', 9999
)
logging.basicConfig(level=logging.DEBUG)
client = loop.run_until_complete(fn)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
print('Loop ended')
loop.close()
counter.py
import asyncio
class Counter:
def __init__(self, maxcount):
self.maxcount = maxcount
#asyncio.coroutine
def count(self):
yield from asyncio.sleep(self.maxcount)
print('Done counting')
serie.py
import asyncio
class Serie:
def __init__(self, loop, items=None):
self.loop = loop
self.items = items or []
#asyncio.coroutine
def remove_one(self, counter):
if len(self.items) is not 0:
yield from counter.count()
item = self.items.pop(0)
print('Removed', item)
else:
print('Serie is empty')
#asyncio.coroutine
def start_removing(self, counter):
while self.items:
yield from self.remove_one(counter)
print('Serie.start_removing() has finished')
mission_control.py
import asyncio
import logging
from counter import Counter
from serie import Serie
class MissionControl(asyncio.Protocol):
def __init__(self, loop, counter, serie):
self.loop = loop
self.counter = counter
self.serie = serie
def connection_made(self, transport):
print('Connection established with', transport.get_extra_info('peername'))
self.transport = transport
self.transport.write(str(len(self.serie.items)).encode())
def data_received(self, data):
data = data.decode()
print('Received:', data)
if data == 'remove one':
yield from self.serie.remove_one()
print('Removed one: {}'.format(self.serie.items))
self.transport.write(str(len(self.serie.items)).encode())
else:
print('Done')
def connection_lost(self, exc):
print('Connection with {} ended'.format(self.transport.get_extra_info('peername')))
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
counter = Counter(2)
planets = Serie(loop, ['Mercúrio', 'Vênus', 'Terra', 'Marte',
'Júpiter', 'Saturno', 'Urano', 'Netuno'])
fn = loop.create_server(
lambda: MissionControl(loop, counter, planets), '127.0.0.1', 9999
)
server = loop.run_until_complete(fn)
print('Server started')
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.stop()
loop.close()
You can also find the source at this Github gist.
In mission_control.py, the method data_received() appears not to be called when the client (app.py) sends data via its self.transport property.
Where is the implementation error and how can I fix it?
The problem is that data_received is not (and cannot be) a coroutine, but you're using yield from inside it. Internally asyncio is just calling self.data_received(data) without any yield from call, which means the body of the method isn't being executed at all - instead a generator object is immediately returned. You need to refactor your implementation to not use yield from, which requires using callbacks instead:
class MissionControl(asyncio.Protocol):
def __init__(self, loop, counter, serie):
self.loop = loop
self.counter = counter
self.serie = serie
def connection_made(self, transport):
print('Connection established with', transport.get_extra_info('peername'))
self.transport = transport
self.transport.write(str(len(self.serie.items)).encode())
def data_received(self, data):
data = data.decode()
print('Received:', data)
if data == 'remove one':
fut = asyncio.async(self.serie.remove_one(self.counter))
fut.add_done_callback(self.on_removed_one)
else:
print('Done')
def on_removed_one(self, result):
print('Removed one: {}'.format(self.serie.items))
self.transport.write(str(len(self.serie.items)).encode())
def connection_lost(self, exc):
print('Connection with {} ended'.format(self.transport.get_extra_info('peername')))
Another option would be to use the asyncio Streams API instead of asyncio.Protocol, which will allow you to use coroutines:
import asyncio
import logging
from counter import Counter
from serie import Serie
#asyncio.coroutine
def mission_control(reader, writer):
counter = Counter(2)
serie = Serie(loop, ['Mercúrio', 'Vênus', 'Terra', 'Marte',
'Júpiter', 'Saturno', 'Urano', 'Netuno'])
writer.write(str(len(serie.items)).encode())
while True:
data = (yield from reader.read(100)).decode()
print('Received:', data)
if data == 'remove one':
result = yield from serie.remove_one(counter)
else:
print('Done')
return
print('Removed one: {}'.format(serie.items))
writer.write(str(len(serie.items)).encode())
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
coro = asyncio.start_server(mission_control, '127.0.0.1', 9999, loop=loop)
server = loop.run_until_complete(coro)
# The rest is the same