Мой сценарий:
#!/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)