简体   繁体   中英

Asynchronous socket server with asyncio

I want to start an while loop that can only be cancelled when another socket command breaks that loop. I've tried using asyncio but the server doesn't accept incoming messages until the while loop ends/breaks.

Here is a simplified version of my code that only runs for 5 seconds. After 2 seconds it should cancel the whileLoop function using the "endLoop" message

server.py

# python 3.7+
import socket
import asyncio

class SocketHandler():
    def __init__(self, conn):
        self.conn = conn
        self.run_loop = False

    async def recv_loop(self):
        try:
            print('client connected')
            while True:
                cmd = self.conn.recv(1024)  # receive data from client
                cmd = cmd.decode()
                print(cmd)
                if len(cmd) == 0:
                    break
                elif cmd == "startLoop":
                    self.run_loop = True
                    task2 = asyncio.create_task(self.whileLoop())
                    task3 = asyncio.create_task(test_counter())
                    await task2
                    await task3
                elif cmd == "endLoop":
                    self.run_loop = False
        finally:
            self.conn.close()

    async def whileLoop(self):
        count = 0
        while self.run_loop:
            print('self.run_loop: ' + str(self.run_loop))
            # the below line should allow for other processes to run however
            # 'cmd = self.conn.recv(1024)' only runs after the while loop breaks
            await asyncio.sleep(1)

            # break the loop manually after 5 seconds
            count += 1
            if count > 5:
                break

async def test_counter():
# just a dummy async function to see if the whileLoop func
# allows other programs to run
    for k in range(5):
        print(str(k))
        await asyncio.sleep(1)

async def main():
    # this is the main asyncio loop that initializes the socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # sock.setblocking(False)

    # Bind the socket to the address given on the command line
    server_address = ("127.0.0.1", 22000)
    print('starting up on %s port %s' % server_address)
    sock.bind(server_address)
    sock.listen(1)
    while True:
        print('waiting for a connection')
        connection, client_address = sock.accept()
        socketHandler = SocketHandler(connection)
        task1 = asyncio.create_task(socketHandler.recv_loop())  # create recv_loop as a new asyncio task
        await task1

if __name__ == '__main__':
    asyncio.run(main())

client.py

import time
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
sock.connect(('127.0.0.1', 22000))

sock.sendall(b'startLoop')
time.sleep(2)
sock.sendall(b'endLoop')

sock.close()

Expected result:

client connected
startLoop
self.run_loop: True
0
self.run_loop: True
1
self.run_loop: True
2
endLoop
3
4

Actual results:

client connected
startLoop
self.run_loop: True
0
self.run_loop: True
1
self.run_loop: True
2
self.run_loop: True
3
self.run_loop: True
4
self.run_loop: True
endLoop

It might be useful to understand why the program doesn't work. This line:

await task2

means, in English, "Until task2 is finished, suspend the execution of this task right here." Since that statement appears inside your while loop, the next cycle through your loop won't occur until task2 is finished. That will be in 5 seconds. You're frozen until then, so you don't reach the logic that detects the receipt of the "endLoop" command.

If you remove both of these lines:

await task2
await task3

then you will get back to the top of your while loop. That's a good thing, but now you will encounter the next problem: the call to recv() is blocking. You won't go to the next line until some more data arrives on the socket. That's OK but recv() isn't a async function, so your event loop will be blocked as well. Task2 and task3 can't run if the loop is blocked.

The problem with your program is that you are trying to use asyncio with blocking socket calls. The asyncio module contains tools for solving this, but you aren't using them. I can't see a simple fix for your program since your basic approach is at fault.

I suggest looking at the asyncio support for sockets, as well as the Python HOW TO for Socket Programming.

发现这个: stackoverflow.com/questions/67006225 ,它允许我在收到另一个命令时跳出 while 循环并且不需要异步或线程。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM