如何在套接字上不断接收用户的消息?

Posted

技术标签:

【中文标题】如何在套接字上不断接收用户的消息?【英文标题】:How to constantly receive messages from users on socket? 【发布时间】:2021-12-24 22:55:34 【问题描述】:

我使用Python Socket创建了一个Messenger,当我使用两个客户端时,例如,当一个用户离开聊天时,另一个用户可以再发送1-2条消息,然后服务器停止接收来自其他用户的消息,即那里是一个已知错误 BrokenpipeError。我理解错误的术语,也许错误在于我的服务器上的 While True 循环(一个包含用户彼此执行的所有操作的循环),因为有以下形式的精彩代码:

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

如何让服务器在任何情况下都能正常工作,并且可以不断收到离开、登录、停留等用户的消息?如何使流同步工作而不会失败?代码如下:

服务器:

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()

客户端(用于服务器访问):

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()

【问题讨论】:

仅作记录:您应该提取并提供minimal reproducible example,以及您收到的完整错误消息,以便每个人都能可靠地重现问题。 【参考方案1】:

您将每个客户端的处理代码放入一个单独的线程中。如果客户端断开连接,该线程会注意到这一点(如果我理解正确,则会出现异常)。然后你要做的就是停止为客户端提供服务,即终止线程。您需要做的就是捕获异常并返回。请注意,必须忽略发送给其他客户端的异常,只有在与专门服务的客户端通信时,您才需要对错误采取行动!

注意事项:

这种为客户提供服务的方式很有效,但不能很好地扩展。使用基于select 的方法要好得多。 使用线程会给您带来另一个问题:竞争条件。简而言之,从多个线程访问同一资源是有问题的,因为一个线程可能会修改另一个线程正在修改的内容,从而导致不一致。 除非我真的需要它们,否则我再也不会使用普通套接字了。实现现有协议。相反,我会使用 ZeroMQ 或 Protobuf 等工具。

【讨论】:

Ulrich,你能帮我更改代码中的某些信息吗,我只是一个初学者,想尽可能多地学习并了解某些机制在他们自己的代码中是如何工作的。

以上是关于如何在套接字上不断接收用户的消息?的主要内容,如果未能解决你的问题,请参考以下文章

在 ICMP 套接字上接收数据

如何从套接字连续接收数据?

Laravel接收来自Pusher的消息

套接字 - 接收/发送消息 (php)

erlang:如何从套接字接收 HTTP/RTSP 消息?

如何充当系统日志接收器?