1
votes

so i am learning how to make a simple chat application , but its a part of the assignment and I must use asyncio for it. The problem is that there is a runtime error raised when the client presses Ctrl-C and I dont know how to fix it and the second problem is that When the server terminates, the client hangs, waiting for users input; in contrast, I would like the client to terminate as well, just after printing The server closed the connection. here is the code for the server:

import sys, asyncio

all_clients = set([])

@asyncio.coroutine
def handle_connection(reader, writer):
    all_clients.add(writer)
    client_addr = writer.get_extra_info('peername')
    print('New client {}'.format(client_addr))
    while True:
        data = yield from reader.read(100)
        if data == None or len(data) == 0:
            break
        message = data.decode()
        print("Received {} from {}".format(message, client_addr))
        for other_writer in all_clients:
            if other_writer != writer:
                new_message = '{} says: {}'.format(client_addr,data)
                other_writer.write(new_message.encode())
                yield from other_writer.drain()
    all_clients.remove(writer)
    print("Close the client socket")
    yield from writer.drain()
    writer.close()

def run():
    loop = asyncio.get_event_loop()
    coro = asyncio.start_server(handle_connection,'127.0.0.1',
                                                8888,loop=loop)
    server = loop.run_until_complete(coro)

    # Serve requests until Ctrl+C is pressed
    print('Serving on {}'.format(server.sockets[0].getsockname()))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        print('\nGot keyboard interrupt, shutting down',file=sys.stderr)
        sys.exit()
    for task in asyncio.Task.all_tasks():
        task.cancel()
    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.close()    

if __name__ == '__main__':
    run()

here is the code for the client:

import sys, asyncio
import aioconsole

class NoneException(Exception):
    pass

class ClosingException(Exception):
    pass

@asyncio.coroutine
def open_connection(loop):
    reader, writer = yield from asyncio.open_connection('127.0.0.1',
                                                     8888,loop=loop)
    return reader, writer

@asyncio.coroutine
def use_connection(reader, writer):
    yield from asyncio.gather(read_from_network(reader,writer),
                                        send_to_server(writer))

@asyncio.coroutine
def read_from_network(reader,writer):
    while True:
        net_message = yield from reader.read(100)
        if writer.transport.is_closing():
            print('Terminating read from network.')
            break
        elif net_message == None:
            continue
        elif len(net_message) == 0:
            print('The server closed the connection.')
            writer.close()
            break
        print('\nReceived: %r' % net_message.decode())
        print('>> ',end='',flush=True)

@asyncio.coroutine
def send_to_server(writer):
    try:
        while True:
            original_message = yield from aioconsole.ainput('>> ')
            if original_message != None:
                console_message = original_message.strip()
                if console_message == '':
                    continue
                if console_message == 'close()' or \
                            writer.transport.is_closing():
                    raise ClosingException()
                writer.write(console_message.encode())
    except ClosingException:
        print('Got close() from user.')
    finally:
        if not writer.transport.is_closing():
            writer.close()

def run():
    try:
        loop = asyncio.get_event_loop()
        reader,writer=loop.run_until_complete(open_connection(loop))
        loop.run_until_complete(use_connection(reader,writer))
    except KeyboardInterrupt:
        print('Got Ctrl-C from user.')
    except Exception as e:
        print(e,file=sys.stderr)
    finally:
        loop.close()


if __name__ == '__main__':
    run()

here are the details or the runtime error raised when the client presses Ctrl-C

^CGot Ctrl-C from user. Exception ignored in: Traceback (most recent call last): File "client.py", line 56, in send_to_server File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/streams.py", line 306, in close File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 622, in close File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 573, in call_soon File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 357, in _check_closed RuntimeError: Event loop is closed

1

1 Answers

1
votes

User writer.close() before closing the event loop. No need to close it in your send_to_server method.

def run():
   try:
      loop = asyncio.get_event_loop()
      reader,writer=loop.run_until_complete(open_connection(loop))
      loop.run_until_complete(use_connection(reader,writer))
   except KeyboardInterrupt:
       print('Got Ctrl-C from user.')

       # closing writer before the event loop
       writer.close()
   except Exception as e:
       print(e,file=sys.stderr)
   finally:
       loop.close()