2

我一直在尝试用 Python 编写一个简单的聊天服务器,我的代码如下:

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。请问有什么帮助吗?!:)

4

1 回答 1

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,bye everyone\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]

但是您仍然遇到换行问题。假设 user1 登录并输入“abc\n”,而 user2 登录并输入“def\n”;您可能会得到类似“abClient 加入:127.0.0.1:56881\ndec\nf\n”的信息。

If you want a line-based protocol, you have to rewrite your code to do the echoing on a line-by-line instead of read-by-read basis.

于 2012-07-09T18:58:56.273 回答