chat_killer_server.py
#!/usr/bin/env python3
import os, sys, socket, select, signal, atexit
MAXBYTES = 4096
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
if len(sys.argv) < 2:
os.write(2,'utilisation : ./chat_killer_server.py PORT'.encode())
sys.exit(1)
HOST = '127.0.0.1' #changer l'HOST si on veut jouer en multijoueur
try:
port = int(sys.argv[1])
except ValueError:
os.write(2,'veuillez entrer pour port un entier ()>1043 de préférence)...'.encode())
sys.exit(1)
try: #finir
s.bind((HOST,port))
print(f"server {HOST}:{port} created.\n")
except OSError:
temp = input("[Errno 48] : Veuillez libérer le port via cette commande : `sudo lsof -i :2317` ou choisir un autre port en tapant `1`> ") #penser à traduire
if temp == '1':
port = int(input('Nouveau port (>1043)> '))
s.bind((HOST,port))
print(f"server {HOST}:{port} created.\n")
else:
sys.exit(1)
socketlist= [s,sys.stdin]
class Client():
def __init__(self,socket,pseudo,cookie=None):
self.socket = socket
self.pseudo:str = pseudo
self.state = 1
self.cookie = cookie
def currentState(self)->str:
if self.state == 0:
return "DISCONNECTED/CRASHED"
if self.state == 1:
return "ALIVE"
if self.state == 2:
return "SUSPENDED"
if self.state == 3:
return "BANNED/DEAD"
def detectPseudo(phrase:str,L:list)->bool:
pseudo = phrase.split()[0]
for i in L:
if i.pseudo == pseudo:
return True
return False
def givePseudo(pseudo:str,L:list):
for i in L:
if i.pseudo == pseudo:
return i
return None
def findPseudo(socket,L:list)->str:
for i in L:
if i.socket == socket:
return i.pseudo
return None
clients = []
cookies = {}
def handlerSIGINT(sig,_frame):
kill()
def end(L:list):
for i in L:
if i.state == 1 or i.state == 2:
i.socket.sendall("DEL+".encode())
def kill():
os.write(1,"Vous venez de tuer le serveur.\n".encode())
end(clients)
sys.exit(0)
s.listen()
commands = 'Commandes : !start ; !list ; @PSEUDO message ; @PSEUDO1 @PSEUDO2 ... @PSEUDOn message ; @PSEUDO !ban ; @PSEUDO !suspend ; @PSEUDO !forgive\n'
commandesCLIENT= 'Commandes : !list ; @Admin message ; @PSEUDO message ; @PSEUDO1 @PSEUDO2 message ; message vide (ENTER) pour se déconnecter.\n'
if __name__ == '__main__':
signal.signal(signal.SIGINT,handlerSIGINT)
os.write(1,commands.encode())
START=False
x=0
while len(socketlist)>1:
x+=2
(rlist,_,_) = select.select(socketlist,[],[])
for i in rlist:
if i == s:
new_cookie = 'C+'+ str(os.getpid()*999+x) #pour faire un truc un peu pseudo random
if not START:
Xfd,(Xaddr,Xport) = s.accept()
print("\nconnection from: {}:{} \n".format(Xaddr,Xport))
socketlist.append(Xfd) #on ajoute Xfd à la liste des sockets (le socket client)
data = Xfd.recv(MAXBYTES)
PSEUDO = False
print(data.decode())
try:
if data.decode()[0:2] != 'C+':
print(data.decode())
Xfd.sendall("\nServer: Please enter your pseudo in the following format '@pseudo'> \n".encode())
while not PSEUDO:
#print("1")
#print("2")
data = Xfd.recv(MAXBYTES)
temp = data.decode().rstrip(' '+'\n') #pour pas que ça retourne à la ligne après et que y'ait pas d'espace dans la clé
#si le début n'est pas un @ (donc c'est pas un pseudo) ou si c'est que un @ ou si y'a un espace dans le nom
if not temp[0] == '@' or temp == '@' or ' ' in temp:
Xfd.sendall("\nTHE CORRECT FORMAT IS '@pseudo'> ".encode())
elif temp == '@Admin':
Xfd.sendall('\n YOU ARE NOT THE ADMIN... PLEASE CHOOSE ANOTHER PSEUDO >'.encode())
elif not detectPseudo(temp, clients):
os.write(1,(temp +' HAS JUST CONNECTED\n').encode())
pseudo = Client(Xfd,temp,new_cookie)
Xfd.sendall((new_cookie+' ' + temp + ' '+ '\n'+ commandesCLIENT + f'\nServer: Welcome {temp}\n').encode())
clients.append(pseudo)
print(f'{pseudo.cookie=},{pseudo.pseudo=}')
Xfd.sendall(('\nServer: Welcome {}\n'.format(temp)).encode()) #sendall est non bloquante normalement
PSEUDO = True
Xfd.sendall(commandesCLIENT.encode())
else:
Xfd.sendall('\n THIS PSEUDO HAS ALREADY BEEN CHOSEN... PLEASE CHOOSE ANOTHER >'.encode())
else:
tempcookie = data.decode().rstrip(' '+'\n')
print(f'{tempcookie=}')
for j in clients:
print(j)
if j.cookie == tempcookie:
print(j.cookie)
print(j.state)
if j.state == 3:
Xfd.sendall("231723172317".encode())
socketlist.remove(Xfd)
Xfd.close()
break
if j.state != 3:
PSEUDO = True
j.state = 1
j.socket = Xfd
Xfd.sendall(('\nServer: Welcome back {}\n'.format(temp)).encode())
Xfd.sendall(commandesCLIENT.encode())
break
Xfd.sendall("DEL+".encode()) #car le cookie donné ne correspond à aucun
break
except IndexError:
print("1")
Xfd.close()
socketlist.remove(Xfd)
os.write(2,('{}:{} was shutdown.\n'.format(Xaddr,Xport)).encode())
break #(?)
except ConnectionResetError:
print("2")
os.write(2,('{}:{} was shutdown.\n'.format(Xaddr,Xport)).encode())
break
except OSError:
#on attend les autres clients, donc ce n'est pas un problème que la fifo soit cassée vu qu'elle l'est parce que le client est mort
os.write(2,('{}:{} was shutdown.\n'.format(Xaddr,Xport)).encode())
break
else:
os.write(1,"THE GAME HAS STARTED. NO NEW CONNECTIONS WILL BE ACCEPTED.\n".encode())
Xfd, (t1,t2)= s.accept()
Xfd.sendall("THE GAME HAS STARTED. NO NEW CONNECTIONS WILL BE ACCEPTED.\n".encode())
os.write(1,('{}:{} was refused.\n'.format(t1,t2).encode()))
Xfd.close()
elif i == sys.stdin:
line = sys.stdin.readline()
if len(line) == 0:
break
elif line[0] == '@':
if detectPseudo(line,clients):
client = givePseudo(line.split()[0],clients)
if client.state == 1 or client.state == 2:
if len(line.split()) > 1:
if line.split()[1] == '!ban':
os.write(1,f"{client.pseudo} a été banni\n".encode())
try:
client.socket.sendall("231723172317 \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYOU HAVE BEEN BANNED\n".encode())
except OSError:
os.write(1,"Ce joueur n'est pas connecté\n".encode())
for j in clients:
if j.state == 1 or j.state == 2:
j.socket.sendall(f"{client.pseudo} A ETE BANNI\n".encode())
client.state = 3
client.socket.close()
socketlist.remove(client.socket)
elif line.split()[1] == "!suspend":
os.write(1,f"{client.pseudo} est suspendu de parler.\n".encode())
try:
client.socket.sendall("2317\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYOU HAVE BEEN SUSPENDED\n".encode())
except OSError:
os.write(1,"Ce joueur n'est pas connecté\n".encode())
for j in clients: #si dans les règles on ne veut pas que les gens le sache, supprimer ça.
if j.state == 1 or j.state == 2:
j.socket.sendall(f"{client.pseudo} HAS BEEN SUSPENDED.\n".encode())
client.state = 2
elif line.split()[1] == "!forgive":
os.write(1,f"{client.pseudo} HAS BEEN FORGIVEN\n".encode())
try:
client.socket.sendall("23172317\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYOU HAVE BEEN FORGIVEN\n".encode())
except OSError:
os.write(1,"Ce joueur n'est pas connecté\n".encode())
for j in clients: #si dans les règles on ne veut pas que les gens le sache, supprimer ça.
if j.state == 1 or j.state == 2:
j.socket.sendall(f"{client.pseudo} HAS BEEN FORGIVEN\n".encode())
client.state = 1
else:
line = line.lstrip(client.pseudo) #on supprime le pseudo à gauche (au cas où user écrit le pseudo dans la suite de son message)
#je ne connais pas bien les règles du jeu, si la personne visée n'est pas censée savoir que c'est un msg privé: retirer le "to @pseudo"
client.socket.sendall(('Server to ' + client.pseudo + ':'+ line).encode())
break #sinon ça affiche à tout le monde quand même
else:
os.write(1,"Ecrivez un message, pas que le pseudo...".encode())
else:
os.write(1,"Ce client n'est pas connecté.\n".encode())
else:
os.write(2,"Le pseudo choisi n'existe pas.\n".encode())
elif line == '!list\n':
os.write(1,"Liste des joueurs : \n".encode())
if len(clients) == 0:
os.write(1,"Il n'y a pas encore de joueur connecté.\n".encode())
else:
for j in clients:
os.write(1,(j.pseudo+': '+j.currentState()+"\n").encode())
elif line == "!start\n":
os.write(1,"THE GAME HAS STARTED.\n".encode())
for j in clients:
if j.state == 1 or j.state == 2:
j.socket.sendall("THE GAME HAS STARTED.\n".encode())
START=True
else:
line = 'Server: '+line
for client in clients:
if client.state == 2 or client.state == 1:
client.socket.sendall(line.encode())
else: #socket client
data = i.recv(MAXBYTES)
pseudo = findPseudo(i,clients)
client = givePseudo(pseudo,clients)
if data.decode()=='\n' or len(data)==0 or data.decode()=="": #on gère le ^D (entrée vide) en supprimant tout
if findPseudo(i,clients)!=None: #pour contrer bug quand un utilisateur est ban
os.write(1,("{} HAS DISCONNECTED\n".format(pseudo)).encode())
i.close()
socketlist.remove(i)
client.state = 0
break
elif data.decode().split()[0] == '@Admin':
os.write(1,data)
i.sendall(f'{pseudo} to @Admin: {data.decode().lstrip("@Admin")}'.encode())
elif data.decode() == "!list\n": #sans "\n" ça marche pas
i.sendall("Liste des joueurs : \n".encode())
for j in clients:
i.sendall((j.pseudo+': '+j.currentState()+"\n").encode())
elif data.decode()[0] == '@': #messages privés client à client
data = data.decode()
pseudoListe = []
if detectPseudo(data,clients):
if len(data.rstrip('\n').split())>1:
for w in data.split():
if detectPseudo(w,clients):
pseudoListe.append(w)
w = w #sinon ça effaçait pas les autres pseudos (le lstrip)
data = data.lstrip(w)
data= data.lstrip(' ') # pour supprimer du message le pseudo au début
print(f"{data=}")
else: #si les pseudos ne s'enchainent pas au début du message, alors l'user veut peut-être parler d'un joueur dans son message
break
for t in pseudoListe:
destination = givePseudo(t,clients)
if destination.state != 0 and destination.state != 3:
os.write(1,(pseudo + ' to '+ destination.pseudo + ": " + data +"\n").encode())
destination.socket.sendall((pseudo + ' to '+ destination.pseudo + ": " + data+"\n").encode())
else:
i.sendall("Serveur: Ce joueur n'est pas connecté\n".encode())
else:
i.sendall("Serveur : Ecrivez un message derrière le pseudo..\n")
else:
i.sendall("Serveur : Ce joueur n'existe pas\n".encode())
break
else:
os.write(1,(f'{pseudo}: {data.decode()}').encode())
for j in clients:
if j.state == 1 or j.state == 2:
try:
j.socket.sendall((pseudo+ ": "+data.decode()).encode())
except TypeError: #impossible normalement mais on est jamais trop sûr
os.write(2,"tu peux pas écrire hihi ".encode())
chat_killer_client.py
#!/usr/bin/env python3
import os, socket, sys, select, atexit, signal
gpid = os.fork()
if gpid==0:
MAXBYTES = 4096
if len(sys.argv) != 3:
print('Usage:', sys.argv[0], 'hote port')
sys.exit(1)
HOST = sys.argv[1]
PORT = int(sys.argv[2])
sockaddr = (HOST, PORT)
try:
os.mkdir('./tmp/')
except FileExistsError:
pass
cookie = None
pseudo = None
for d in os.listdir('./tmp'):
if os.path.exists(f'./tmp/{d}/cookie'):
fd = os.open(f'./tmp/{d}/cookie', os.O_RDONLY)
pseudo = d
cookie = os.read(fd,MAXBYTES)
print(f'{cookie.decode()=}')
print(f'{d=}')
os.close(fd)
break
PATHFIFO= './tmp/killer.fifo'
PATHLOG = './tmp/killer.log'
def end(fifo,fifofd,log):
try:
os.unlink(fifo)
os.close(log)
os.close(fifofd)
except FileNotFoundError:
pass
#os.remove(PATHLOG)
try:
os.mkfifo(PATHFIFO)
except FileExistsError:
try:
os.remove(PATHFIFO) #INUTILE?
except PermissionError: #INUTILE MTN DEADLOCK REGLE
pid = os.fork()
if pid == 0:
os.execvp('sudo',['sudo','rm',PATHFIFO])
else:
os.waitpid(pid,0)
os.mkfifo(PATHFIFO)
logfd = os.open(PATHLOG, os.O_CREAT | os.O_WRONLY) #on le crée s'il existe pas déjà
fifofd = os.open(PATHFIFO, os.O_RDWR) #obligé d'ouvrir en RDWR pour éviter deadlock
#print("on a open les deux trucs")
atexit.register(end,PATHFIFO,fifofd,logfd)
XTERM = "xterm"
SAISIE = [XTERM,"-e",f"cat >{PATHFIFO}"]
AFFICHAGE = [XTERM,"-e","tail", "-f", PATHLOG]
#print("test avant fork")
global pidaffichage, pidsaisie #pour pouvoir les utiliser dans le handler
def CHLDAffichage():
global pidaffichage
pidaffichage = os.fork()
if pidaffichage == 0:
try:
#print("Je suis dans l'enfant affichage")
os.execvp(XTERM, AFFICHAGE)
except:
os.write(2,"affichage n'a pas marché/télécharger xterm ".encode())
sys.exit(1)
CHLDAffichage()
def CHLDSaisie():
global pidsaisie
pidsaisie = os.fork()
if pidsaisie == 0: #faire SIGCHILD et tout au cas où ça bug etc
try:
#print("Je suis dans l'enfant saisie")
os.execvp(XTERM, SAISIE)
except:
os.write(2,"saisie n'a pas marché/télécharger xterm ".encode())
sys.exit(1)
CHLDSaisie()
s_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4, TCP
def kill():
os.kill(pidaffichage,signal.SIGKILL)
os.kill(pidaffichage,signal.SIGKILL) #pour être sûr que xterm affichage se ferme bien
os.kill(pidsaisie,signal.SIGKILL)
try:
os.unlink(PATHFIFO)
os.close(logfd)
except: #le fichier a déjà été géré par le end (peut-être redondant...)
pass
s_client.close()
os.kill(os.getpid(),signal.SIGKILL)
try:
s_client.connect(sockaddr)
except ConnectionRefusedError:
os.write(2,"Le serveur n'est pas encore lancé...\n".encode())
kill()
sys.exit(1)
#print('connected to server {}:{}'.format(HOST,PORT))
socketlist = [s_client,fifofd] #penser à gérer les SIGCONT/SIGSTOP
global TG
TG = False #autrement dit, on peut parler
def suspend():
global TG
TG = True
os.kill(pidsaisie,signal.SIGSTOP) #freeze la saisie (on peut quand même écrire fin y'a un buffer jpense et tout s'affiche quand SIGCONT mais ça marche)
os.write(logfd,"YOU GOT SUSPENDED (hihi)\n".encode())
def handlerSIGINT(sig,_frame):
kill()
def handlerSIGALARM(sig,_frame):
global TG
if not TG: #inutile?
pass
TG = False
#print("HANDLER HANDLEDEDEDEDEDDDDD asyyy là")
def handlerSIGCHLD(sig,_frame):
global pidaffichage, pidsaisie
'''ici on cherche à savoir si pid sera égal à celui de la saisie ou de l'affichage
donc on met -1 pour attendre l'arrêt de l'importe quel enfant et WNOHANG permet de ne pas être bloquant'''
(pid,_) = os.waitpid(-1,os.WNOHANG)
if pid == pidsaisie:
#os.write(1,"Relance du terminal de saisie\n".encode())
CHLDSaisie()
elif pid == pidaffichage:
#os.write(1,"Relance du terminal d'affichage\n".encode())
CHLDAffichage()
else:
#print("TTTTTTTTTTTTTTTTTTT ")
pass
def forgive():
os.kill(pidsaisie,signal.SIGCONT) #ça change rien de recevoir un SIGCONT si on est pas SIGSTOP avant
signal.alarm(1) #pour faire fonctionner le signal.signal
if cookie != None:
temp = s_client.sendall(cookie)
#print(f'{temp=}')
os.write(logfd,"VEUILLEZ ENTRER UNE TOUCHE POUR CONFIRMER VOTRE ENTREE DANS LE SERVEUR.".encode())
while True:
signal.signal(signal.SIGCHLD,handlerSIGCHLD)
signal.signal(signal.SIGINT,handlerSIGINT)
try:
(rlist,_,_) = select.select(socketlist,[],[])
for i in rlist:
signal.signal(signal.SIGALRM,handlerSIGALARM)
#print(f'{TG=}')
if TG and i==fifofd:
#print("ok on est dans le TG,")
os.read(fifofd,MAXBYTES)
#print("ça bloque PAS visiblement")
#signal.signal(signal.SIGALRM,handlerSIGALARM)
elif not TG and i==fifofd:
#print("ok on est PAS dans le TG,")
line = os.read(fifofd,MAXBYTES) #on lit depuis le descripteur du fifo ouvert en LECTURE
#print(line,line.decode())
if len(line) == 0:
s_client.shutdown(socket.SHUT_WR)
kill()
#print(line.decode())
s_client.sendall(line)
elif i==s_client:
data = i.recv(MAXBYTES)
#print(f'{data.decode()=}')
if data.decode() == "":
kill()
elif data.decode() == "DEL+":
try:
os.remove(f'./tmp/{pseudo}/cookie')
os.rmdir(f'./tmp/{pseudo}/')
os.remove(PATHFIFO)
os.remove(PATHLOG) #même ici ça marche pas
os.rmdir('./tmp/') #SI ça marche :)
except FileNotFoundError:
pass
#print("Tous les fichiers ont été supprimés.")
kill()
elif data.decode().split()[0][0:2] == 'C+':
cookie = data.decode().split()[0]
pseudo = data.decode().split()[1]
#print("AVANT MKDDIRRR")
os.mkdir(f'./tmp/{pseudo}')
#print("APRES MKDIR")
fd = os.open(f'./tmp/{pseudo}/cookie',os.O_WRONLY | os.O_CREAT)
#print("APRES OPEN")
os.write(fd,cookie.encode())
temp = data.decode().lstrip(str(cookie))
temp = temp.lstrip(str(pseudo))
#print(f'{temp=}')
os.close(fd)
os.write(logfd,temp.encode())
elif data.decode() == "THE GAME HAS STARTED. NO NEW CONNECTIONS WILL BE ACCEPTED.\n":
kill()
elif len(data) == 0 or data.decode() == "": #serveur mort on supprime tout
os.remove(f'./tmp/{pseudo}/cookie')
os.rmdir(f'./tmp/{pseudo}/')
os.remove(PATHFIFO)
os.remove(PATHLOG)
os.rmdir('./tmp/')
kill()
sys.exit(0)
elif data.decode().split()[0] == "231723172317":
print('VOUS ETES banni')
os.write(logfd, data)
kill()
os.write(1,"YOU GOT BANNED!".encode())
sys.exit(0)
elif data.decode().split()[0] == "2317":
TG = True
os.kill(pidsaisie,signal.SIGSTOP)
os.write(logfd, data)
break
elif data.decode().split()[0] == "23172317":
os.kill(pidsaisie,signal.SIGCONT) #ça change rien de recevoir un SIGCONT si on est pas SIGSTOP avant
signal.alarm(1) #pour faire fonctionner le signal.signal
os.write(logfd, data)
else:
os.write(logfd, data)
except ValueError: #la partie a commencé, ValueError: file descriptor cannot be a negative integer (-1) si le serveur démarre
os.kill(pidaffichage,signal.SIGKILL)
os.kill(pidaffichage,signal.SIGKILL) #pour être sûr que xterm affichage se ferme bien
os.kill(pidsaisie,signal.SIGKILL)
os.write(1,"THE GAME HAS STARTED\n".encode())
kill()
sys.exit(0)
except :
s_client.sendall("".encode())
os.remove(PATHFIFO)
kill()
sys.exit(1)
else:
sys.exit(0)