2016-09-21 113 views
0

我想創建處理在同一時間大量客戶的服務器(處理:從客戶端接收數據,並在同一時間將數據發送到所有客戶端!)Python的插座,高級聊天框

其實我試圖創建一個聊天框。該程序將這樣工作:

1)將有一個服務器來處理客戶端。

2)多個客戶端可以加入服務器。

3)客戶端發送消息(字符串)到服務器。

4)服務器從客戶端收到消息,然後發送給除客戶端以外的客戶端的所有 客戶端。

這就是客戶之間如何溝通。沒有私人信息可用。當有人點擊輸入時,所有的客戶端都會在他們的屏幕上看到消息。

客戶端模塊很容易製作,因爲客戶端只與一個套接字(服務器)通信。

從另一方面的服務器模塊是非常複雜的,我不知道該怎麼做(我也知道線程)。

這是我atempt:

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尚未完成。但如果我混合方法recv發送然後問題變得更加複雜。

那麼做到這一點的最佳方法是什麼? 我也想給我一個例子。

回答

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

你肯定會需要實現一種機制來讓服務器停止,並測試所有,但它應該是一個起點

+0

我真的很感謝你!我無法通過我自己找到答案。我在Python中學習套接字超過一年,而且我的水平還很低。現在我繼續前進! 我會用結果發佈答案。我想告訴我是否有問題。 – babaliaris

0

這是我最終的節目,它的作品就像一個魅力。

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)

ř是包含所有準備套接字的列表。 但我沒有什麼不妥,wx是。

第一參數是插座列表中,第二接受客戶 和第三參數是什麼呢?爲什麼我再次給套接字列表?