2012-07-09 38 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