Как предотвратить ошибку Brokenpipe в моем скрипте сервера - PullRequest
0 голосов
/ 19 апреля 2020

Это мой серверный скрипт для получения и отправки сообщений всем клиентам

import socket
import threading

HEADER = 64
PORT = 5050
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)

clients = set()
clients_lock = threading.Lock()

def handle_client(conn, addr):
    name = conn.recv(HEADER).decode(FORMAT)
    if name:
        name = int(name)
        msg_name = conn.recv(name).decode(FORMAT)
    print(f"[NEW CONNECTION] {msg_name} connected.")
    connection_message = f"{msg_name} connected."
    with clients_lock:
        for c in clients:
            if c != conn:
                message = connection_message.encode(FORMAT)
                msg_length = len(message)
                send_length = str(msg_length).encode(FORMAT)
                send_length += b' ' * (HEADER - len(send_length))
                c.sendall(send_length)
                c.sendall(message)

    with clients_lock:
        clients.add(conn)

    connected = True
    try:
        while connected:
            msg_length = conn.recv(HEADER).decode(FORMAT)
            if msg_length:
                msg_length = int(msg_length)
                msg1 = conn.recv(msg_length).decode(FORMAT)
                msg = f"{msg_name}: {msg1}"
                if msg1 == DISCONNECT_MESSAGE:
                    connected = False
                print(f"{msg}")
                with clients_lock:
                    for c in clients:
                        if c != conn:
                            message = msg.encode(FORMAT)
                            msg_length = len(message)
                            send_length = str(msg_length).encode(FORMAT)
                            send_length += b' ' * (HEADER - len(send_length))
                            c.sendall(send_length)
                            c.sendall(message)
                msg = f"You: {msg1}"
                message = msg.encode(FORMAT)
                msg_length = len(message)
                send_length = str(msg_length).encode(FORMAT)
                send_length += b' ' * (HEADER - len(send_length))
                conn.send(send_length)
                conn.send(message)

    finally:
        with clients_lock:
            clients.remove(conn)
            conn.close()

def start():
    server.listen()
    print(f"[LISTENING] Server is listening on {SERVER}")
    while True:
        conn, addr = server.accept()
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        thread.daemon = True
        thread.start()
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server is starting...")
start()

Проблема в том, что если сервер отключается без отправки DISCONNECT_MESSAGE, т.е. я принудительно закрыл программу до ее завершения, я получаю Ошибка разбитой трубы.

Это клиентский скрипт:

import socket
import threading
import tkinter as tk

def returnname():
    def receiving():
        receiving = True
        while receiving:
            msg_length = client.recv(HEADER).decode(FORMAT)
            if msg_length:
                msg_length = int(msg_length)
                msg = client.recv(msg_length).decode(FORMAT)
                TEXTAREA.insert("end", msg)
                TEXTAREA.see("end")

    def send(msg):
        message = msg.encode(FORMAT)
        msg_length = len(message)
        send_length = str(msg_length).encode(FORMAT)
        send_length += b' ' * (HEADER - len(send_length))
        client.send(send_length)
        client.send(message)

    def sendmessage():
        mess = MESSAGEFIELD.get()
        MESSAGEFIELD.delete(0, "end")
        send(mess)

    def quitmessage():
        send(DISCONNECT_MESSAGE)
        exit()

    name = FIELD.get()
    FIELD.pack_forget()
    BUTTON.pack_forget()
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(ADDR)
    message = name.encode(FORMAT)
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    send_length += b' ' * (HEADER - len(send_length))
    client.send(send_length)
    client.send(message)
    thread = threading.Thread(target=receiving)
    thread.daemon = True
    thread.start()
    MESSAGEFIELD = tk.Entry(TOP)
    SENDBUTTON = tk.Button(TOP, text="Send", command=sendmessage)
    QUITBUTTON = tk.Button(TOP, text="Quit", command=quitmessage)
    TEXTAREA = tk.Listbox(TOP)
    SCROLLBAR = tk.Scrollbar(TOP)
    MESSAGEFIELD.pack()
    SENDBUTTON.pack()
    QUITBUTTON.pack()
    TEXTAREA.pack(side="left", expand=True, fill="both")
    SCROLLBAR.pack(side="right", fill="both")
    SCROLLBAR.config(command=TEXTAREA.yview)

HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
SERVER = "IP ADDRESS OF SERVER"
ADDR = (SERVER, PORT)
DISCONNECT_MESSAGE = "!DISCONNECT"

TOP = tk.Tk()
FIELD = tk.Entry(TOP)
FIELD.insert(0, "Enter Name Here")
BUTTON = tk.Button(TOP, text="Send", command=returnname)
FIELD.pack(expand=True)
BUTTON.pack(expand=True)
TOP.mainloop()

Может кто-нибудь сказать мне, если есть какой-нибудь способ, которым я могу решить эту проблему?

Извините, если мое объяснение проблема была плохой Я плохо объясняю вещи.

1 Ответ

0 голосов
/ 20 апреля 2020

Было несколько вещей, которые я должен был сделать, чтобы сделать эту работу.

Во-первых, Сломанный канал может возникнуть в двух разных местах в коде сервера. Первый - когда подключается новый клиент, а сервер пытается отправить сообщение «Новый клиент подключен» всем клиентам, а второй - когда существующий клиент отправляет сообщение. Итак, нам нужно обработать исключение в обоих местах.

Итак, мы помещаем try / кроме обоих блоков. где он говорит, что если c! = conn.

Теперь о том, как обрабатывать исключение.

Как я сначала подумал, простое удаление клиента c из списка клиентов приведет к работают, но для l oop, для c в клиентах будет выдаваться ошибка времени выполнения, когда мы пытаемся изменить набор клиентов во время итерации.

Я пробовал разные методы, чтобы обойти эту проблему, но это это самый эффективный метод работы, который я получил.

Я изменил клиентов из set () в пустой список []

Затем я изменил clients.add на clients.append

Затем я изменил для l oop на c в диапазоне (len (клиенты)) и использовал клиентов [c] для доступа к клиентам.

Но когда я попробовал это, я увидел, что если утверждение, что клиенты [c]! = conn могут выбросить индекс списка из-за ошибки ошибки, если программа попытается go поверх несуществующего клиента после удаления. Поэтому я тоже поместил его в блок try / исключением и разрешил программе продолжить работу при исключении.

for c in range(len(clients)):
        try:
            if clients[c] != conn:
                try:
                    message = connection_message.encode(FORMAT)
                    msg_length = len(message)
                    send_length = str(msg_length).encode(FORMAT)
                    send_length += b' ' * (HEADER - len(send_length))
                    clients[c].sendall(send_length)
                    clients[c].sendall(message)
                except:
                    clients.remove(clients[c])
        except:
            continue

Последняя проблема заключалась в том, что даже после удаления клиента поток все еще жив, поэтому активный поток count возвращает больше, чем количество подключенных клиентов. Таким образом, вместо вывода числа активных соединений в виде числа активных потоков - 1, я печатаю len (клиентов) + 1, + 1, поскольку при подключении нового клиента эта строка печатается перед добавлением клиента в список.

print(f"[ACTIVE CONNECTIONS] {len(clients) + 1}")

Итак, вся программа теперь выглядит так:

import socket
import threading

HEADER = 64
PORT = 5050
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER, PORT)
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)

clients = []
clients_lock = threading.Lock()

def handle_client(conn, addr):
    name = conn.recv(HEADER).decode(FORMAT)
    if name:
        name = int(name)
        msg_name = conn.recv(name).decode(FORMAT)
    print(f"[NEW CONNECTION] {msg_name} connected.")
    connection_message = f"{msg_name} connected."
    with clients_lock:
        for c in range(len(clients)):
            try:
                if clients[c] != conn:
                    try:
                        message = connection_message.encode(FORMAT)
                        msg_length = len(message)
                        send_length = str(msg_length).encode(FORMAT)
                        send_length += b' ' * (HEADER - len(send_length))
                        clients[c].sendall(send_length)
                        clients[c].sendall(message)
                    except:
                        clients.remove(clients[c])

            except:
                continue

    with clients_lock:
        clients.append(conn)

    connected = True
    try:
        while connected:
            msg_length = conn.recv(HEADER).decode(FORMAT)
            if msg_length:
                msg_length = int(msg_length)
                msg1 = conn.recv(msg_length).decode(FORMAT)
                msg = f"{msg_name}: {msg1}"
                if msg1 == DISCONNECT_MESSAGE:
                    connected = False
                print(f"{msg}")
                with clients_lock:
                    for c in range(len(clients)):
                        try:
                            if clients[c] != conn:
                                try:
                                    message = msg.encode(FORMAT)
                                    msg_length = len(message)
                                    send_length = str(msg_length).encode(FORMAT)
                                    send_length += b' ' * (HEADER - len(send_length))
                                    clients[c].sendall(send_length)
                                    clients[c].sendall(message)
                                except:
                                    clients.remove(clients[c])
                        except:
                            continue
                msg = f"You: {msg1}"
                message = msg.encode(FORMAT)
                msg_length = len(message)
                send_length = str(msg_length).encode(FORMAT)
                send_length += b' ' * (HEADER - len(send_length))
                conn.send(send_length)
                conn.send(message)

    finally:
        with clients_lock:
            clients.remove(conn)
            conn.close()

def start():
    server.listen()
    print(f"[LISTENING] Server is listening on {SERVER}")
    while True:
        conn, addr = server.accept()
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        thread.daemon = True
        thread.start()
        print(f"[ACTIVE CONNECTIONS] {len(clients) + 1}")

print("[STARTING] server is starting...")
start()
...