为什么我的 python 聊天客户端不显示服务器发送给它的数据?

Why doesn't my python chat client show the data that the server is sending to it?

这是服务器:

import socket
from threading import Thread

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 4444))

def get_data(s, conn): 
    data = conn.recv(1024) 
    print conn, data, "\n" 
    return data; 

def send_data(s, conn, data):
    conn.sendall(data) 
    print "Data sent to clients \n" 

def listen():

    s.listen(5)
    conn, addr = s.accept()
    print addr, " connected \n"
    while True:
        data = get_data(s, conn)
        send_data(s, conn, data)


for i in range(5):
    Thread(target = listen).start()

和客户:

import socket
import time

HOST = '127.0.0.1'
PORT = 4444
NICKNAME = 'NICKNAME_HERE' + ' >'


def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        message = raw_input("> ")
        nickandmessage = NICKNAME + message
        s.send(nickandmessage)
        data = s.recv(1024)
        if NICKNAME not in data:
            print data

connect()

一切正常,多个客户端可以连接到服务器,但是如果我在客户端中写一条消息,服务器会收到它,但它似乎并没有发送到其他客户端...我想我错过了一些非常简单的东西,我认为我太笨了,无法弄清楚。

这里有几个问题,我建议您也通读答案 here。我将在下面向您展示如何使您的解决方案发挥作用。但这不是理想的解决方案。

使用 asyncore 或上面答案中建议的其他方法可能是更好的解决方案。下面还有一些我没有处理的事情——比如优雅地杀死服务器线程,在写入时填充套接字缓冲区等等……它仍然有一次处理 5 个连接的限制。

服务器

首先,你应该处理套接字的关闭。在关闭时,读取调用将 return 一个空字符串。如果您断开客户端与服务器的连接,它将在 recv() 调用中无限循环。

您作为 conns.accept() 收到的套接字对象是一个套接字,它允许您讨论刚刚从客户端接受的连接。如果您想将消息发送给其他客户端,您需要在他们的连接上调用 send

为此,您可以在共享列表中存储和删除连接对象。然后,您可以在 send_data 中遍历这些连接,将接收到的消息发送到其他连接。您也可以避免将消息发回给发件人。

服务器已更新以执行此操作,您可以使用 nc / netcat

进行测试

nc localhost 4444

而不是使用您的客户端 python 程序,因为那也有问题。

#!/usr/bin/python
import socket
from threading import Thread, Lock

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 4444))

# CHANGED:
# threading.Lock() to lock access to the connections list
lock = Lock()
# hold open connections in a list
connections = []

def get_data(s, conn):
    data = conn.recv(1024)
    print conn, data, "\n"
    return data;

def send_data(sender, data):
    with lock:
        count = 0
        for conn in connections:
            if conn == sender:
                # don't echo back to the originator
                continue
            try:
                print(data)
                conn.send(data)
                count += 1
            except socket.error:
                # TODO: add better error handling
                pass

    # CHANGED: show num clients sent to
    print "Data sent to %d clients \n" % count

def listen():
    while True:
        conn, addr = s.accept()

        # CHANGED: add connection to connections list
        with lock:
            connections.append(conn)

        print addr, " connected \n"
        while True:
            data = get_data(s, conn)
            # CHANGED: handle close
            if not data:
                break
            send_data(conn, data)

        # CHANGED: remove connection from connections list and close it
        with lock:
            connections.remove(conn)

        conn.close()

s.listen(5)
for i in range(5):
    Thread(target = listen).start()

客户

如果服务器将数据异步发送到您的客户端套接字,您的客户端将不会读取数据,因为它们会阻塞在 raw_input() 调用处等待用户输入。


更新: 根据下面的评论,使用 select.select() 等待标准输入将无法在 Windows 作为 select() 系统调用在 Win 上仅支持套接字。

来自 select.select() 的文档:

Note File objects on Windows are not acceptable, but sockets are. On Windows, the underlying select() function is provided by the WinSock library, and does not handle file descriptors that don’t originate from WinSock.

因此,另一种涉及线程的解决方案可能更适合 Windows。


您可以通过让套接字在其自己的线程上通信并通过队列或其他方式与主线程通信来解决这个问题。我选择使用 select.select() 在单个线程中执行此操作。

select.select() 允许您等待 I/O 的多个类文件对象。它接受三个列表,returns 三个列表。第一个列表参数包含您有兴趣从中读取的类文件对象。第一个 returned 列表将包含其中的一个子集,其中包含要读取的数据。第二个列表 argument/return 列表对于可写文件的行为类似,第三个列表用于错误条件。 select.select() 的第四个参数是超时,如果达到此超时并且没有文件为 read/write/error 准备好,那么调用将 return 包含三个空列表。

我用它来处理来自标准输入的用户输入和到达套接字的数据。

虽然在这个答案中“>”的插入有点不正确。

import socket
import time
import sys
import select

HOST = '127.0.0.1'
PORT = 4444
NICKNAME = 'NICKNAME_HERE' + ' >'

def connect():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))


    while True:
        sys.stdout.write(">")
        sys.stdout.flush()
        readable, writable, errors = select.select([sys.stdin, s], [], [])
        if sys.stdin in readable:
            message = sys.stdin.readline()
            nickandmessage = NICKNAME + message
            s.send(nickandmessage)

        if s in readable:
            data = s.recv(1024)
            # TODO: check if socket was closed and exit (data == "")
            print(data)
            # CHANGED: this was to remove echos? the server fixes that
            # if NICKNAME not in data:
            #     print data

connect()