【问题标题】:Python Sockets, Advanced Chat BoxPython 套接字,高级聊天框
【发布时间】:2016-09-21 13:51:30
【问题描述】:

我想创建一个同时处理很多客户端的服务器(handles:从客户端接收数据,同时向所有客户端发送数据!!!)

实际上我正在尝试创建一个聊天框。该程序将像这样运行:

1) 将有一个处理客户端的服务器。

2) 多个客户端可以加入服务器。

3) 客户端向服务器发送消息(字符串)。

4) 服务器从客户端接收消息,然后将其发送给所有人 除了他得到它的客户之外的客户。

这就是客户端相互通信的方式。没有可用的私人消息。当有人按下回车键时,所有客户都会在他们的屏幕上看到该消息。

客户端模块很容易制作,因为客户端只与一个套接字(服务器)通信。

另一方面,服务器模块真的很复杂,我不知道怎么做(我也知道线程)。

这是我的尝试:

import socket, threading


class Server:

def __init__(self, ip = "", port = 5050):

    '''Server Constructor. If __init__ return None, then you can use
       self.error to print the specified error message.'''

    #Error message.
    self.error  = ""

    #Creating a socket object.
    self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    #Trying to bind it.
    try:
        self.server.bind( (ip, port) )
        pass


    #Failed, because socket has been shuted down.
    except OSError :
        self.error = "The server socket has been shuted down."
        return None

    #Failed, because socket has been forcibly reseted.
    except ConnectionResetError:
         self.error = "The server socket has been forcibly reseted."
         return None


    #Start Listening.
    self.server.listen()


    #_____Other Variables_____#

    #A flag to know when to shut down thread loops.
    self.running = True

    #Store clients here.
    self.clients = []

    #_____Other Variables_____#


    #Start accepting clients.
    thread = threading.thread(target = self.acceptClients)
    thread.start()

    #Start handling the client.
    self.clientHandler()





#Accept Clients.
def acceptClients(self):

    while self.running:
        self.clients.append( self.server.accept() )

    #Close the server.
    self.server.close()




#Handle clients.
def clientHandler(self):

    while self.running:

        for client in self.clients:

            sock = client[0]
            addr = client[1]

            #Receive at most 1 mb of data.
            #The problem is that recv will block the loop!!!
            data = sock.recv(1024 ** 2)

如您所见,我使用线程接受客户端,因此 server.accept() 不会阻塞程序。然后我将客户存储到一个列表中。

但问题出在 clientHandler 上。我将如何从所有人中恢复 客户同时?第一个recv会阻塞循环!!!

我还尝试为每个新客户端启动新线程(clientHandlers) 但问题在于同步。

那么发送呢?服务器必须向所有客户端发送数据,因此 clientHandler 尚未完成。但是如果我混合使用 recvsend 方法,那么问题就会变得更加复杂。

那么正确和最好的方法是什么? 我也想举个例子。

【问题讨论】:

    标签: python multithreading sockets


    【解决方案1】:

    当不同的客户端相互独立时,多线程非常有用:您编写代码时就像只存在一个客户端一样,并为每个客户端启动一个线程。

    但是在这里,来自一个客户的东西必须发送给其他客户。每个客户端一个线程肯定会导致同步噩梦。因此,让我们致电select 进行救援吧! select.select 允许轮询套接字列表并在准备好后立即返回。在这里,您可以构建一个包含侦听套接字和所有接受的套接字的列表(该部分最初是空的...):

    • 当监听套接字准备好读取时,接受一个新的套接字并将其添加到列表中
    • 当另一个套接字准备好读取时,从中读取一些数据。如果读取 0 个字节,则其对等体已关闭或关闭:关闭它并将其从列表中删除
    • 如果您从一个已接受的套接字中读取了某些内容,则在列表中循环,跳过侦听套接字以及您已从中读取的套接字并将数据发送到任何其他套接字

    代码可能(或多或少):

        main = socket.socket()  # create the listening socket
        main.bind((addr, port))
        main.listen(5)
        socks = [main]   # initialize the list and optionaly count the accepted sockets
    
        count = 0
        while True:
            r, w, x = select.select(socks, [], socks)
            if main in r:     # a new client
                s, addr = main.accept()
                if count == mx:  # reject (optionaly) if max number of clients reached
                    s.close()
                else:
                    socks.append(s) # appends the new socket to the list
            elif len(r) > 0:
                data = r[0].recv(1024)  # an accepted socket is ready: read
                if len(data) == 0:      # nothing to read: close it
                    r[0].close()
                    socks.remove(r[0])
                else:
                    for s in socks[1:]:  # send the data to any other socket
                        if s != r[0]:
                            s.send(data)
            elif main in x:  # close if exceptional condition met (optional)
                break
            elif len(x) > 0:
                x[0].close()
                socks.remove(x[0])
        # if the loop ends, close everything
        for s in socks[1:]:
            s.close()
        main.close()
    

    您当然需要实现一种机制来要求服务器停止,并测试所有这些,但它应该是一个起点

    【讨论】:

    • 真的非常感谢!我自己无法找到这个答案。我在python中学习sockets一年多了,水平还是很低。现在我继续前进!我将发布结果的答案。如果有什么问题,我想告诉我。
    【解决方案2】:

    这是我的最后一个程序,效果很好。

    Server.py

    导入套接字,选择

    类服务器:

    def __init__(self, ip = "", port = 5050):
    
        '''Server Constructor. If __init__ return None, then you can use
           self.error to print the specified error message.'''
    
        #Error message.
        self.error  = ""
    
        #Creating a socket object.
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        #Trying to bind it.
        try:
            self.server.bind( (ip, port) )
            pass
    
    
        #Failed, because socket has been shuted down.
        except OSError :
            self.error = "The server socket has been shuted down."
    
        #Failed, because socket has been forcibly reseted.
        except ConnectionResetError:
             self.error = "The server socket has been forcibly reseted."
    
    
        #Start Listening.
        self.server.listen()
    
    
        #_____Other Variables_____#
    
        #A flag to know when to shut down thread loops.
        self.running = True
    
        #Store clients here.
        self.sockets   = [self.server]
    
        #_____Other Variables_____#
    
    
        #Start Handling the sockets.
        self.handleSockets()
    
    
    
    
    
    
    #Handle Sockets.
    def handleSockets(self):
    
        while True:
            r, w, x = select.select(self.sockets, [], self.sockets)
    
            #If server is ready to accept.
            if self.server in r:
    
                client, address = self.server.accept()
                self.sockets.append(client)
    
    
            #Elif a client send data.
            elif len(r) > 0:
    
                #Receive data.
                try:
                    data = r[0].recv( 1024 )
    
    
                #If the client disconnects suddenly.
                except ConnectionResetError:
                    r[0].close()
                    self.sockets.remove( r[0] )
                    print("A user has been disconnected forcible.")
                    continue
    
                #Connection has been closed or lost.
                if len(data) == 0:
    
                    r[0].close()
                    self.sockets.remove( r[0] )
                    print("A user has been disconnected.")
    
    
                #Else send the data to all users.
                else:
    
                    #For all sockets except server.
                    for client in self.sockets[1:]:
    
                        #Do not send to yourself.
                        if client != r[0]:
                            client.send(data)
    
    
    server = Server()
    print("Errors:",server.error)
    

    Client.py

    导入套接字,线程

    从 tkinter 导入 *

    类客户:

    def __init__(self, ip = "192.168.1.3", port = 5050):
    
        '''Client Constructor. If __init__ return None, then you can use
           self.error to print the specified error message.'''
    
        #Error message.
        self.error  = ""
    
        #Creating a socket object.
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        #Trying to bind it.
        try:
            self.server.connect( (ip, port) )
            pass
    
    
        #Failed, because socket has been shuted down.
        except OSError :
            self.error = "The client socket has been shuted down."
            return
    
        #Failed, because socket has been forcibly reseted.
        except ConnectionResetError:
             self.error = "The client socket has been forcibly reseted."
             return
    
        #Failed, because socket has been forcibly reseted.
        except ConnectionRefusedError:
             self.error = "The server socket refuses the connection."
             return
    
    
        #_____Other Variables_____#
    
        #A flag to know when to shut down thread loops.
        self.running = True
    
        #_____Other Variables_____#
    
    
    
    #Start the GUI Interface.
    def startGUI(self):
    
        #Initialiazing tk.
        screen = Tk()
        screen.geometry("200x100")
    
        #Tk variable.
        self.msg = StringVar()
    
        #Creating widgets.
        entry  = Entry( textvariable = self.msg )
        button = Button( text = "Send", command = self.sendMSG )
    
        #Packing widgets.
        entry.pack()
        button.pack()
    
        screen.mainloop()
    
    
    
    #Send the message.
    def sendMSG(self):
        self.server.send( str.encode( self.msg.get() ) )
    
    
    
    #Receive message.
    def recvMSG(self):
    
        while self.running:
    
            data = self.server.recv(1024)
    
            print( bytes.decode(data) )
    
    
    
    #New client.
    main = Client()
    print("Errors:", main.error)
    
    
    
    #Start a thread with the recvMSG method.
    thread = threading.Thread(target = main.recvMSG)
    thread.start()
    
    #Start the gui.
    main.startGUI()
    
    #Close the connection when the program terminates and stop threads.
    main.running = False
    main.server.close()
    

    程序完全按照我的意愿运行。

    但我还有一些问题。

    r, w, x = select.select(self.sockets, [], self.sockets)

    r 是一个包含所有就绪套接字的列表。 但我并没有忘记 wx 是什么。

    first 参数是套接字列表,second 是接受的客户端 而第三个参数又是什么?为什么我要再次给出套接字列表?

    【讨论】:

      猜你喜欢
      • 2018-03-22
      • 2019-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多