2012-07-09 54 views
2

我一直在試圖編寫Python中一個簡單的聊天服務器,我的代碼如下:Python的套接字錯誤 - 的recv()函數

import socket 
import select 

port = 11222 
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1024) 
serverSocket.bind(('',port)) 
serverSocket.listen(5) 

sockets=[serverSocket] 
print 'Server is started on port' , port,'\n' 

def acceptConn(): 
    newsock, addr = serverSocket.accept() 
    sockets.append(newsock) 
    newsock.send('You are now connected to the chat server\n') 
    msg = 'Client joined',addr.__str__(), 
    broadcast(msg, newsock) 

def broadcast(msg, sourceSocket): 
    for s in sockets: 
     if (s != serverSocket and s != sourceSocket): 
      s.send(msg) 
    print msg, 


while True: 
    (sread, swrite, sexec)=select.select(sockets,[],[]) 
    for s in sread: 
     if s == serverSocket: 
      acceptConn() 
     else: 
      msg=s.recv(100) 
      if msg.rstrip() == "quit": 
       host,port=socket.getpeername() 
       msg = 'Client left' , (host,port) 
       broadcast(msg,s) 
       s.close() 
       sockets.remove(s) 
       del s 
      else: 
       host,port=s.getpeername() 
       msg = s.recv(1024) 
       broadcast(msg,s) 
       continue 

運行的服務器,並通過Telnet連接後,服務器讀取單個字符並跳過下一個字符。例如,如果我在telnet中鍵入Hello,服務器將讀取H l o。 有什麼幫助嗎?! :)

+0

這裏不是你的實際問題,但是你需要在這裏改變socket.getpeername()爲s.getpeername(),並且處理掉連接而不先發送「quit」的客戶端。 – abarnert 2012-07-09 18:56:08

+0

我實際上使用套接字而不是s來獲取套接字成員列表,並忘記將其更改。 – 2012-07-09 19:09:38

回答

4

您調用recv兩次。

第一:

msg=s.recv(100) 

然後,如果這不是 「退出」,你閱讀和廣播另一條消息:

msg = s.recv(1024) 
broadcast(msg,s) 

所以原始郵件丟失。

因爲您使用telnet作爲客戶端,所以您每次只能看到一個字符,所以您會看到其他每個字符。如果你使用nc代替,你會得到不同的結果 - 但仍然是所有其他閱讀被扔掉的基本問題。

這裏還有一些其他問題:

  • 你希望客戶退出,你應該處理來自的recv EOF或錯誤和/或在x傳遞插槽以及前發出「退出」作爲r。
  • 你假定「退出」將總是出現在單個消息中,並且整個消息都會出現在它自己之中。對於TCP來說這不是一個合理的假設。您可能會得到四個1字節的「q」,「u」,「i」和「t」讀數,或者您可能會看到「OK,再見所有人\ nquit \ n」,兩者都不匹配。
  • 「客戶端留下的」和「客戶端加入的」消息是元組而不是字符串,它們形成的方式不同,因此您會看到('Client joined','('127.0.0.1',56564) 「)('客戶端左',('127.0.0.1',56564))。
  • 你依賴於客戶在他們的消息之間發送換行符。首先,如上所述,即使他們這樣做了,也不能保證你會得到完整/離散的消息。其次,你的「系統」消息沒有換行符。

這是你的樣品的修改版本,修復的問題最多,除了需要「跳槽」到孤獨和依靠客戶端發送換行符在單一消息:

#!/usr/bin/python 

import socket 
import select 
import sys 

port = 11222 
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1024) 
serverSocket.bind(('',port)) 
serverSocket.listen(5) 

sockets=[serverSocket] 
print 'Server is started on port' , port,'\n' 

def acceptConn(): 
    newsock, addr = serverSocket.accept() 
    sockets.append(newsock) 
    newsock.send('You are now connected to the chat server\n') 
    msg = 'Client joined: %s:%d\n' % addr 
    broadcast(msg, newsock) 

def broadcast(msg, sourceSocket): 
    for s in sockets: 
     if (s != serverSocket and s != sourceSocket): 
      s.send(msg) 
    sys.stdout.write(msg) 
    sys.stdout.flush() 


while True: 
    (sread, swrite, sexec)=select.select(sockets,[],[]) 
    for s in sread: 
     if s == serverSocket: 
      acceptConn() 
     else: 
      msg=s.recv(100) 
      if not msg or msg.rstrip() == "quit": 
       host,port=s.getpeername() 
       msg = 'Client left: %s:%d\n' % (host,port) 
       broadcast(msg,s) 
       s.close() 
       sockets.remove(s) 
       del s 
      else: 
       host,port=s.getpeername() 
       broadcast(msg,s) 
       continue 

要解決「退出」的問題,你將不得不保持一個緩衝區爲每個客戶端,並且做這樣的事情:

buffers[s] += msg 
if '\nquit\n' in buffers[s]: 
    # do quit stuff 
lines = buffers[s].split('\n')[-1] 
buffers[s] = ('\n' if len(lines) > 1 else '') + lines[-1] 

但你仍然有換行符問題。想象一下,當user2登錄並鍵入「def \ n」時,user1登錄並鍵入「abc \ n」;你可能會得到類似「abClient加入:127.0.0.1:56881 \ ndec \ nf \ n」。

如果你想要一個基於行的協議,你必須重寫你的代碼來逐行執行回顯,而不是逐個讀取。

+0

謝謝,我刪除了第二個recv並且它可以工作,但仍然按照我鍵入的方式輸入(不按輸入),每個字符後面都有空格。任何幫助? - 感謝您閱讀您的編輯。如果你可以提供一些代碼,我將不勝感激:) – 2012-07-09 19:09:04

+0

似乎我會重新設計代碼,保持你的點在我的腦海!感謝您的優雅和全面的答案:) – 2012-07-09 19:27:36

+1

它會在您鍵入時讀取,因爲您的telnet一次只發送一個字節,因此每次讀取都是一個字節。 (在真正的網絡中,數據包可能會或可能不會合並,但是使用本地主機時,每個數據包總是顯示爲單獨的讀取。)每個字符後面的空格是因爲逗號顯式地打印空格而不是換行符;如果您不想打印空格或換行符,請使用sys.stdout.write而不是打印。 – abarnert 2012-07-09 19:31:59