BlockingIOError: WinError 10035 Неблокирующая операция сокета не может быть завершена немедленно - PullRequest
0 голосов
/ 15 января 2020

Мой сценарий:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import multiprocessing
import pickle
import socket
import ssl
import sys
import time
from Logs.cryptography import symmetric_crypto
from Logs import sign_up_log_in as log


class Servidor():
    """
    Se creara un servidor en local (127.0.0.1), en el puerto 7000.
    """

    def __init__(self,host = "192.168.0.16", port = 7000):
        """
        En self.clientes se almacenaran todos los usuarios que se conecten al servidor

        setblocking(False or True) -> si este es True (por defecto), sera un socket bloqueante,
        lo que quiere decir que el servidor solo atendera a una sola peticion y las demas deberan
        esperar, en cambio si es False, este atendera todas las peticiones que lleguen de manera simultanea con ayuda de los hilos.

        threading.Thread(target = metodo())
        * La clase Thread cuenta con un parametro llamado target, el cual ejecutara un metodo o funcionalidad de forma simultanea a
          otros procesos, tareas (metodos - funcionalidades) que tambien deseen ejecutarse al tiempo, a esto se le conoce como hilos.

        El parametro daemon sirve para darle fin a los hilos, una vez que el programa haya terminado, esto es util cuando no queremos que estos procesos
        queden andando sin motivo alguno.

        El metodo start(), da inicio al hilo hijo, el hilo principal es el de la ejecucion del propio programa en si.
        Este metodo se coloca despues del parametro daemon, siempre y cuando hagamos uso de el.

        El metodo join(), sirve para indicarle al programa que no debe ejecutar mas procesos (hilos), hasta que no termine con el actual(proceso-hilo),
        este metodo se coloca despues del start() metodo.
        """

        self.load_clients()

        self.clientes_registrados = []
        self.clientes_conectados = []
        self.staging_area = []

        self.nickname = None

        self.transmitted_message = None

        self.server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.server.bind((str(host),int(port)))
        self.server.listen(10)
        self.server.setblocking(False)

        # se ejecutan de inmediato al iniciar el programa
        recv_logging = multiprocessing.Process(target = self.recv_logging)
        aceptar_conn = multiprocessing.Process(target = self.aceptar_conn)
        recv_messages = multiprocessing.Process(target = self.recv_messages)
        lista_send_conn = multiprocessing.Process(target = self.lista_send_conn)

        # se anclan al hilo principal, los hilos hijos, para que estos no queden como procesos andantes aun despues de haberse cerrado el programa,
        # seguido de esto se inicia la ejecucion de los metodos.
        recv_logging.daemon = True
        recv_logging.start()

        aceptar_conn.daemon = True
        aceptar_conn.start()

        lista_send_conn.daemon = True
        lista_send_conn.start()

        recv_messages.daemon = True
        recv_messages.start()

    def lista_send_conn(self):

        if bool( self.clientes_conectados ):
            package = (self.clientes_conectados,"lista_conn")
            self.conn_cliente.send( pickle.dumps(package) )


    def save_clients(self):
        """
        Metodo que guarda los cambios producidos en el archivo clientes.pckl
        """

        with open("clientes.pckl","wb") as clients:
            pickle.dump(self.clientes_registrados,clients)

    def load_clients(self):
        """
        Metodo que carga el archivo clientes.pckl en una variable para su posterior uso.
        """

        with open("clientes.pckl","ab+") as clients:
            clients.seek(0)

            try:
                self.clientes_registrados = pickle.load(clients)

            except:
                pass



    def recv_logging(self):

        # El error esta en este metodo, que no hace el recv de lo que le manda el cliente (obj_usuario -> (user/password) )


        """
        Este metodo recibira una variable por parte del metodo login(), del modulo cliente.py, el cual tendra un dict que tiene en la clave la ip del usuario,
        y en su valor, el objeto de la clase Usuario del cliente.

        Se recorre la lista (con los diccionarios "self.clientes del modulo sign_up_log_in.py" dentro), para asi poder verificar la existencia de un usuario
        que dice registrarse o logearse dentro del servidor.

        A cada cliente de la lista de registrados, se le saca su valor a la variable obj_usuario, recordemos que la clave era la ip, y su valor el objeto de la clase Usuario del respectivo
        usuario.
        """

        while True:




            # Desencripta el archivo .pckl para su posterior uso.

            if self.transmitted_message != None:
                self.decrypt_data_logging(self.transmitted_message)

            # cliente[ip] devuelve un objeto Usuario (nickname, password del usuario), objeto que se almcanera en obj_usuario,
            # para posteriormente comparar los nicknames de los clientes en base de datos, con los que se acaban de pronunciar (sign or log),
            # y sus contraseñas.

            # Este for lo uso para saber si esa ip entrante ya esta registrada, de modo que si se levanta un error, no esta registrada,
            # en cambio si no se levanta un error, quiere decir que el usuario si esta, pero que si no ingreso es por un error de autenticacion

            manager = log.Controller()

            # recibir una tupla (obj_user, id, ip), el id puedes ser "log-in" o "sign-up"



            if bool(self.staging_area):

                for conexion in self.staging_area:

                    try:
                        received_data = conexion.recv(2048) # ERROR (pero aca se caga todoooo)

                    except Exception as e:
                        print(e)


                    else:
                        received_data = pickle.loads(received_data)

                        # desempaquetamos el paquete
                        #received_id = received_data[0][1]
                        received_id = received_data[1]



                        # el id sirve especificamente para redireccionar en este condicional, a sus respectivos metodos
                        if received_id == "sign_up":
                            #received_sign_up = received_data[0][0]
                            received_sign_up = received_data[0]
                            status_ok = manager.add_user(received_sign_up, self.clientes_registrados)

                        else:
                            #received_log_in = received_data[0][0]
                            received_log_in = received_data[0]
                            status_ok = manager.verify_user(received_log_in, self.clientes_registrados)



                            # de acuerdo a lo que se retorna en los metodos verify_user() y add_user(), se ejecutan ciertas tareas.

                            # Errores al momento de realizar el proceso de logueo
                            if status_ok == "user_exist_signup":
                                if bool(self.staging_area):
                                    for conexion in self.staging_area:
                                        conexion.send("user_exist_sign-up".encode("utf-8"))

                            elif status_ok == "invalid_nickname":
                                if bool(self.staging_area):
                                    for conexion in self.staging_area:
                                        conexion.send("invalid_nickname".encode("utf-8"))

                            elif status_ok == "user_exist_log-in":
                                if bool(self.staging_area):
                                    for conexion in self.staging_area:
                                        conexion.send("user_exist_log-in".encode("utf-8"))

                            elif status_ok == "not_found":
                                if bool(self.staging_area):
                                    for conexion in self.staging_area:
                                        conexion.send("not_found".encode("utf-8"))

                            else:
                                # si retorna una tupla (obj_usuario, id), entonces este se pasa por el else
                                data_decode = pickle.loads(status_ok)

                                # si el id es igual a sign-up, se redireccionara para que inicie sesion
                                if data_decode[1] == "sign_up":
                                    # solo se agrega el obj_usuario
                                    self.clientes_registrados.append((data_decode[0]))

                                    if bool(self.staging_area):
                                        for conexion in self.staging_area:
                                            conexion.send("log-in_go".encode("utf-8"))

                                else:
                                    # si el id es igual a log-in, se redireccionara para que inicie el chat
                                    if bool(self.staging_area):
                                        for conexion in self.staging_area:
                                            conexion.send("chat".encode("utf-8"))

                                    # devolver un objeto usuario, con la ip que manda el mismo
                                    for client in self.clientes_registrados:
                                        obj_usuario = client[received_data[2]]

                                    self.nickname = obj_usuario.usuario

                                    self.clientes_conectados.append( {self.nickname:[conexion for conexion in self.staging_area if bool(self.staging_area)]} )

                                    return self.nickname

                            # Se encriptan los datos de los usuarios y se guarda (confirma) dicho cambio, posteriormente se le envia una señal al cliente,
                            # para avisarle de que dicho usuario ya esta registrado y que solo debera iniciar seison, no registrarse, de no ser por este aviso,
                            # el usuario podria volver accidentalmente a registrarse, y eso no es lo que queremos, verdad?
                            self.encrypt_data_logging()
                            self.save_clients()



    def encrypt_data_logging(self):
        """
        Este metodo cifrara el archivo.pckl
        """

        manager = symmetric_crypto.Transmitter()
        key = manager.random_key()
        msg = manager.extract_message("clientes.pckl")

        # self.transmitted_message, es un atributo que se usara para el decrypt_data_logging(self,transmitted_message) metodo.

        # cipher_text, solo guarda la copia del mensaje cifrado, sin adjuntos. Util para encriptar el fichero .pckl
        self.transmitted_message,cipher_text = manager.encrypt(key,msg.encode("utf-8"))

        with open("clientes.pckl",'wb') as file:
            file.write(cipher_text)

    def decrypt_data_logging(self,transmitted_message):
        """
        Este metodo descifrara el archivo.pckl
        """

        manager = symmetric_crypto.Receiver()
        cipher_text, tag_mac, salt, nonce = manager.unpacking(transmitted_message)
        plain_text = manager.decrypt(cipher_text, tag_mac, salt, nonce)

        with open("clientes.pckl",'wb') as file:
            file.write(plain_text)


    def main(self):
        """
        Este metodo solo reaccionara ante el input "salir", lo cual causara el cierre del servidor, y de la terminal.
        """
        self.aceptar_conn()
        while True:
            opcion = input("\n-> ").strip().lower()

            if opcion == "salir":
                self.server.close()
                sys.exit()

            else:
                pass


    def aceptar_conn(self):
        """
        Aceptaremos todas las conexiones entrantes, (maximo 10 coneixones) y a cada usuario que se conecte, le aplicaremos el metodo
        setblocking = False, para que estos puedan ejecutar multiples tareas.
        """

        while True:

            try:
                conn_cliente, data_cliente = self.server.accept()
                conn_cliente.setblocking(False)

            except Exception as e:
                print(e)

            else:
                self.staging_area.append(conn_cliente)



    def recv_messages(self):
        """
        Si self.clientes_conectados es mayor que 0, es decir, si aun hay usuarios conectados, y ademas, si han enviado algun mensaje,
        se recibiran los mensajes de cada uno de ellos, y se enviaran al metodo msg_to_whoami().
        """

        while True:
            # mientras hayan clientes en linea
            if bool(self.clientes_conectados):
                # A cada cliente conectado se le recibiran sus dichas peticiones (mensajes)
                for cliente in self.clientes_conectados:


                    # se captura el nombre del usuario (whoami), al cual se le quiere hacer llegar el mensaje, y el mensaje_anidado,
                    # digo anidado porque ademas del mensaje, este tiene otros adjuntos que ayudan al cifrado del mismo.
                    whoami, msg = cliente.recv(4096)
                    whoami,msg = pickle.loads(whoami), pickle.loads(msg)

                    # verificar que haya un msg y un usuario existente
                    if cliente[whoami]:
                        # # ip_user = obj_usuario[whoami]
                        if msg:
                            self.msg_to_whoami(cliente, msg)



    def msg_to_whoami(self,cliente,msg):
        """
        El mensaje se envia a todos los usuarios conectados, menos al que envio el mensaje propiamente.
        En caso de que halla un error, ese usuario sera sacado de la red, ya que este estaba inactivo y le
        esta quitando el espacio a un usuario activo que desee entrar en el chat.
        """
        for client in self.clientes_conectados:

            # generamos una lista con las claves de todos los usuarios (nicknames), y su valor es (conn_objeto_socket)
            keys = client.keys()
            for key in keys:
                # para cada clave en la lista, si alguna es igual al nickname parametro, se obtiene su objeto para poder enviarle el
                # mensaje solo a dicha persona.
                if key == cliente:
                    conn_obj = client[key]
                    conn_obj.send(pickle.dumps(msg))

            # Si hay algun tipo de error, se removera el usuario del servidor para darle lugar a otros, ya que dicho usuario esta inactivo.
            self.clientes_conectados.remove(client)

if __name__ == '__main__':

    init = Servidor()
    init.main()

Мой код был в порядке, пока вдруг я не получил эту ошибку.

Код ошибки:

 Traceback (most recent call last):
  File ".\servidor.py", line 355, in <module>
    init.main()
  File ".\servidor.py", line 274, in main
    self.aceptar_conn()
  File ".\servidor.py", line 295, in aceptar_conn
    conn_cliente, data_cliente = self.server.accept()
  File "C:\Users\EQUIPO\AppData\Local\Programs\Python\Python37-32\lib\socket.py", line 212, in accept
    fd, addr = self._accept()
BlockingIOError: [WinError 10035] A non-blocking socket operation could not be completed immediately 

Кто-нибудь знает, почему это происходит , и как я могу это исправить?

Я искал inte rnet, но я действительно не могу много понять об этой ошибке. По сути, я делаю подключения в списке и, просматривая список (в порядке поступления), я выполняю определенные задачи с каждым соединением, потому что соединение теряется, когда оно установлено (потому что метод всегда ожидает подключите и настройте 10 в очереди, в self.server.listen (10) )

PDTA: если эта информация имеет какое-либо применение, я делаю небольшой приватный чат, поэтому мне нужно, чтобы conn_cliente.setblocking (False)

1 Ответ

1 голос
/ 15 января 2020

BlockingIOError: [WinError 10035] Неблокирующая операция сокета не может быть завершена немедленно

accept обычно ожидает подключения клиента и только после этого возвращается успешно. Но вы явно установили сокет неблокирующим, что означает, что он не будет ждать. Вместо этого он вернет ошибку, которую вы видите, если не был подключен ни один клиент в течение (короткого времени) accept.

... поэтому мне нужен этот conn_cliente.setblocking (False)

Ни один из ваших кодов не касается конкретно неблокирующих сокетов, поэтому я сомневаюсь, что вам это действительно нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...