3

파이썬 asyncore를 사용하여 비동기 클라이언트를 작성하고 몇 가지 문제를 만났습니다.asyncore 클라이언트에서 쓰기 가능한 메소드를 정의하면 클라이언트가 데이터를 매우 느리게 전송합니다.

Asyncore client in thread makes the whole program crash when sending data immediately

을하지만 지금은 다른 문제를 충족 :이의 도움으로이 문제를 해결했다.

내 클라이언트 프로그램 :

import asyncore, threading, socket 
class Client(threading.Thread, asyncore.dispatcher): 
    def __init__(self, host, port): 
     threading.Thread.__init__(self) 
     self.daemon = True 
     self._thread_sockets = dict() 
     asyncore.dispatcher.__init__(self, map=self._thread_sockets) 
     self.host = host 
     self.port = port 
     self.output_buffer = [] 
     self.start() 

    def send(self, msg): 
     self.output_buffer.append(msg) 
    def writable(self): 
     return len("".join(self.output_buffer)) > 0 
    def handle_write(self): 
     all_data = "".join(self.output_buffer) 
     bytes_sent = self.socket.send(all_data) 
     remaining_data = all_data[bytes_sent:] 
     self.output_buffer = [remaining_data] 
    def handle_close(self): 
     self.close() 
    def handle_error(self): 
     print("error") 
    def handle_read(self): 
     print(self.recv(10)) 
    def run(self): 
     self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 
     self.connect((self.host, self.port)) 
     asyncore.loop(map = self._thread_sockets) 

mysocket = Client("127.0.0.1",8400) 
while True: 
    a=str(input("input")) 
    mysocket.send("popo") 

그리고 내 서버 프로그램 :

import socket 
HOST="127.0.0.1" 
PORT=8400 
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
print("socket created") 
s.bind((HOST, PORT)) 
s.listen(1) 
print("listen") 
conn,addr = s.accept() 
print("Accepted. Receiving") 
while True: 
    data = conn.recv(20) 
    print("Received: ") 
    print(data) 
    data = input("Please input reply message:\n").encode("utf-8") 
    conn.send(data) 
    print("Data sended. Receiving") 

내 문제는 클라이언트에서 서버로 데이터를 전송하는 것은 매우 느린 약 20 ~ 30 초입니다! 그러나 항상 데이터를 성공적으로 보낼 수 있습니다. 클라이언트의 쓰기 가능한 메서드를 주석 처리하면 보내는 프로세스가 매우 빨라집니다. 왜 이런 행동을합니까? 쓰기 가능한 메소드를 사용하려면 어떻게 수정해야합니까? 감사!

저는 파이썬 3으로 서버를 시작하고 파이썬 2로 클라이언트를 시작합니다. 우분투 14.04를 사용합니다.

답변

3

asyncore 루프는 소켓으로 무엇인가를 할 준비가되었을 때 writable()을 호출합니다. 메서드 writable()에 쓸 내용이 있다면 handle_write()이 호출됩니다. writable() 기본값은 항상 True을 반환하므로이 경우에는 handle_write()writable()을 호출하는 통화 중 루프가 있습니다.

위의 구현에서 클라이언트 루프가 시작될 때 writable() 메서드가 즉시 호출됩니다. 그 순간 버퍼에 아무 것도 없으므로 writable()은 쓸 것이 없다고 알려줍니다.

asyncore 루프는 select()입니다. 이제 루프가 "대기"상태가됩니다. 일부 데이터가 소켓에서 수신되거나 시간 초과 이벤트에 의해서만 깨어날 수 있습니다. 이러한 이벤트가 발생한 후 루프는 다시 writable()을 확인합니다.

서버가 클라이언트에 아무 것도 보내지 않고 클라이언트가 시간 초과를 기다립니다. 기본값 인 timeout은 30 초이므로 무언가를 보내기 전에 최대 30 초 기다려야합니다. asyncore.loop()을 시작하는 동안 제한 시간을 줄일 수있다 : 여기에 올 수

asyncore.loop(map = self._thread_sockets, timeout = 0.5) 

또 다른 아이디어는 버퍼가 send()에서 비어 있는지 확인하는 것입니다 비어 있으면 즉시 보낼 수 있습니다. 그러나 그것은 나쁜 생각입니다. send()은 주 스레드에서 호출되지만 소켓은 다른 스레드의 asyncore 루프로 관리됩니다.

동일한 이유로 다른 스레드에서 동시 액세스를 위해 output_buffer의 사용을 보호하는 것이 좋습니다. 잠금 객체 threading.Lock() 여기에 사용할 수 있습니다

def __init__(self, host, port): 
    #... 
    self.lock = threading.Lock() 

def send(self, msg): 
    self.lock.acquire() 
    try: 
     self.output_buffer.append(msg) 
    finally: 
     self.lock.release() 

def writable(self): 
    is_writable = False; 
    self.lock.acquire() 
    try: 
     is_writable = len("".join(self.output_buffer)) > 0 
    finally: 
     self.lock.release() 

    return is_writable 

def handle_write(self): 
    self.lock.acquire() 
    try: 
     all_data = "".join(self.output_buffer) 
     bytes_sent = self.socket.send(all_data) 
     remaining_data = all_data[bytes_sent:] 
     self.output_buffer = [remaining_data] 
    finally: 
     self.lock.release() 

다른 스레드에서 asyncore을 깨울 할 스레드 안전 메커니즘이 없습니다. 유일한 해결책은 루프 타임 아웃을 줄이는 것입니다. 너무 작은 시간 초과로 인해 CPU 사용량이 증가합니다.