2

I created a messenger using Python Socket, when I use two clients, for example, when one user leaves the chat, the other user can send 1-2 more messages and then the server stops receiving messages from other users, ie there is a known error BrokenpipeError. I understand the terminology of the error, perhaps the error lies on my server in the While True loop (a loop that includes all the actions that users perform with each other) because there is fabulous code in the form of:

if not data:
    print(f'User {name1} leave')
    break

How can I make the server work under any circumstances and can constantly receive messages from users who leave, log in, stay, etc.? How to make a stream work synchronously without failures? Code below:

server:

import socket
import threading
import time

HOST = '127.0.0.1'
PORT = 8888

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))

server.listen(15)
print(f'Server {HOST}:{PORT} start.')

users = [] 
sort = []


def crypto(text, key):
    encrypt = ''

    for i in text:
        encrypt += chr(ord(i) + key)

    return encrypt   


def listen_decode(user, addr):
    print(f'User IP-address {addr[0]} login..')
    sort.append(user) 
    user.send('Encode'.encode('utf-8'))
    user.send('Name'.encode('utf-8'))
    name1 = user.recv(1024).decode('utf-8')
    users.append(name1)

    
    while True:
        data = user.recv(1024).decode('utf-8')
        b1 = time.ctime()
        atribute = ' | '
        data_crypto = crypto(data, 4)
        print(f'{name1} sent message: {data_crypto} ' + atribute + '' + b1 + ' ')

        for i in sort:
            if(i != server and i != user):
                i.sendall(f'{name1} > {data}'.encode('utf-8'))
    
        if not data:
            print(f'User {name1} leave')
            break


def start_server():
    
    while True:
       user_socket, addr = server.accept()
       potok_info = threading.Thread(target=listen_decode, args=(user_socket, addr))
       potok_info.start()


if __name__ == '__main__':
    start_server()

Client (this is for server access):

from tkinter import messagebox
from tkinter import *
import _tkinter 
import socket
import threading
import os


window = Tk()

window.title('Login')
window.geometry('320x200')
window.resizable(True, True)

HOST = '127.0.0.1'
PORT = 8888

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))

name = StringVar()
password = StringVar()

def encrypt(text, key):
    encrypt1 = ''

    for i in text:
        encrypt1 += chr(ord(i) - key)

    return encrypt1

def send_message():

    while True:
        data = client.recv(1024)
        print('\r\r' + data.decode('utf-8') + '\n' + f'you: ', end='')


def chat():

    string_name = name.get()

    if('Name' in client.recv(1024).decode('utf-8')):
        name1 = string_name
        client.send(name1.encode('utf-8'))

        potok = threading.Thread(target=send_message)
        potok.start()


        while True:
            msg = input('you: ')
            client.send(msg.encode('utf-8'))



def crypt():     
    
    string_name = name.get()
    string_password = password.get()

    try:
        user_encryption_selection = (encryption_listbox.get(encryption_listbox.curselection()))
    except _tkinter.TclError:
        messagebox.showerror('Error', 'Enter type message')



    if string_name == 'John':
        if string_password == '5555':
            if user_encryption_selection == 'Use Encrypted':
                window.after(1000, lambda: window.destroy())

                menu = Tk()

                menu.title('Menu Chat')
                menu.geometry('500x350')
                menu.resizable(False, False)

                menu_button = Button(menu, text='Global chat', command=chat, height=1, width=18)
                menu_button.grid(padx=150)

                menu.mainloop()
        else:
            messagebox.showerror('Error', 'Error password')
    else:
        messagebox.showerror('Error', 'Error name')

    
entry = Entry(window, textvariable=name, width=10)
entry.grid(column=1, pady=7, padx=4)

label = Label(window, text='Enter name: ')
label.grid(row=0, padx=1)

entry1 = Entry(window, textvariable=password, width=10)
entry1.grid(column=1, pady=7, padx=2)

label1 = Label(window, text='Enter password: ')
label1.grid(row=1, padx=1)

listbox = Listbox(window, selectmode=SINGLE, width=12, height=2)
listbox.grid(column=1, row=2, pady=7, padx=2)



encryption_options = ['Use Encrypted']
encryption_listbox = Listbox(window, selectmode=SINGLE, width=10, height=1)
encryption_listbox.grid(column=1, row=2, pady=7, padx=2)
for i in encryption_options:
    encryption_listbox.insert(END, i)  

label_crypto = Label(window, text='Type message: ', bg='black', fg='red')
label_crypto.grid(row=2)

button = Button(window, text='Enter', command=crypt)
button.grid(pady=30)



window.mainloop()
John West
  • 43
  • 4
  • Just for the record: You should have extracted and provide a [mcve], along with the full error message you get, so that everyone can reliably reproduce the problem. – Ulrich Eckhardt Nov 12 '21 at 17:04

1 Answers1

0

You're putting each client's handling code into a separate thread. If a client disconnects, that thread will notice this (gets an exception, if I understand you right). What you'd do then is to simply stop servicing the client, i.e. terminate the thread. All you need to do is to catch the exception and return. Note that an exception while sending to the other clients must be ignored, only when communicating with the specifically served clients you need to act upon errors!

Notes:

  • This style of servicing clients works, but it doesn't scale well. Using a select-based approach is much better.
  • Using threads gives you another issue: Race conditions. In short, it's problematic to access the same resource from multiple threads, because one might modify something that the other is modifying which then leads to inconsistencies.
  • I wouldn't ever bother with plain sockets again unless I really need them to e.g. implement an existing protocol. Instead, I'd use tools like ZeroMQ or Protobuf.
Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • Ulrich, could you help me change certain information in the code, I'm just a beginner who wants to learn as much as possible and understand how certain mechanisms work in their own code. – John West Nov 12 '21 at 18:32