【问题标题】:Python Socket Chat: Client recv() hangingPython Socket 聊天:客户端 recv() 挂起
【发布时间】:2017-09-26 14:50:02
【问题描述】:

我正在尝试构建的聊天程序有一个小问题: 有 2 个客户端和一个服务器,一个客户端发送一条消息,服务器接收它,并将其广播给每个人(通常是两个客户端,但可以有更多)。问题是,当一个客户端(通过服务器)向另一个客户端发送数据时,接收客户端必须自己发送一些东西才能看到消息。

这是我的 server.py:

import socket
from thread import start_new_thread
import threading


def thread(c,clients, c_lock,buf=1024,):
    c.send("You are connected! \n")
    while True:
        try:
            data = c.recv(buf)
            print data
            if not data:
                break
            broadcast(clients,c_lock,data)
        except socket.error as e:
            exit(0)
    c.close()

def broadcast(clients,c_lock,data):
    with c_lock:
        for c in clients:
            c.sendall("- " + data)

def main():
    host = ''
    port = 10000
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clients = set()
    clients_lock = threading.Lock()
    s.bind((host, port))
    s.listen(5)
    print "%s listening on %s" % (host, str(port))
    while True:
            c, addr = s.accept()
            print "got connection from ", addr
            with clients_lock:
                clients.add(c)
            start_new_thread(thread, (c, clients, clients_lock, ))
    s.close()

if __name__ == "__main__":
    main()

还有client.py:

import socket
import time
from sys import exit

def main():
    host = socket.gethostname()
    port = 10000
    name = str(raw_input("Enter your name: "))


    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('192.168.1.12', port))
    except Exception as e:
        print 'Connection not working... %s' % (e)
        exit()

    print s.recv(1024)  # greeting msg
    print "Send q to stop the connection \n"
    out = ''
    while out != 'q':
        out = str(raw_input('->'))
        recv =  s.recv(1024)
        s.send(name + ": " + out)
        time.sleep(0.1)
        print recv
    s.close()
if __name__ == '__main__':
    main()

感谢您的帮助 :) (请原谅我的混乱:/)

【问题讨论】:

  • 您的客户大部分时间都花在raw_input() 上,不可能收到任何东西。如果您在单独的线程中进行输入/发送和接收,事情会起作用 - 但如果在用户键入时收到消息,结果将是屏幕上的混乱。您确实需要一个 GUI,或者像 curses 这样的文本界面,以便用户输入和传入消息位于屏幕的不同区域,不会相互干扰。
  • 好吧,你告诉我的是我应该等待其他客户的回复?
  • @LiorDahan 不,他是说你应该使用一个可以在显示输出的同时处理输入的界面。诅咒或gui。或者您可以在数据可用时一次读取一个字符sys.stdin,打印收到的数据并重新打印当前用户输入状态。但实际上,如果您使用的是 linux,请使用 curses。如果您在 Windows 上,只需选择 tkinter/wxPython/qt/gtk+ 或 your poison 的任何其他框架。或者只是使用线程并暂时使用打乱的输入/输出。

标签: python sockets networking


【解决方案1】:

你有两个问题。

recv() 是阻塞调用。
raw_input() / input() 也是阻塞调用。

意味着客户端将“挂起”这两个调用(recv() 是一个例外如果管道中有数据,则不会阻塞)。

为了解决这个问题,就像你的例子一样。您可以像在服务器中所做的那样使用threading 来拥有一个类/实例,该类/实例负责从套接字检索数据并读取用户输入。或者在 Linux 上,如果数据在管道中,您可以使用select.selectselect.epoll 轮询套接字,然后才调用recv()。对于输入,您最好在sys.stdin 上使用select/epoll 或使用线程。

但如果没有可读取的数据,recv() 在 Python(和许多其他语言)中将始终是一个阻塞调用。 raw_input() / input() 也是如此。

要使用epoll,下面是一个简短的示例:

from select import epoll, EPOLLIN
import sys

polly = epoll()
polly.register(s.fileno(), EPOLLIN)
polly.register(sys.stdin.fileno(), EPOLLIN)

for fid, eid in polly.poll(1):
    # s is your socket, s.fileno() on linux is the file number that socket has been assigned to.
    # And epoll returns a list of all filenumbers that has data waiting.
    # So we can check if the fid from epoll == the sockets filenumber.
    if fid == s.fileno(): 
        data = s.recv(8192)
    # of if it's sys.stdin's filenumber
    # (stdin is user input, this is where raw_input normally gets its data from)
    elif fid == sys.stdin.fileno():
        user_input = sys.stdin.readline()
        print('User wrote:', user_input)

或者如果你想使用线程:

from threading import Thread, enumerate as t_enum
class userInput(Thread):
    def __init__(self, socket, username):
        Thread.__init__(self)
        self.socket = socket
        self.username = username
        self.start()

    def run(self):
        mainThread = None
        for thread in t_enum():
            if thread.name == 'MainThread':
                mainThread = thread
                break

        while mainThread and mainThread.isAlive():
            out = raw_input('->') # str() is redundant, it's type(str) already.
            self.socket.send(self.username + ": " + out)

再往下一点你的代码:

name = str(raw_input("Enter your name: "))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.connect(('192.168.1.12', port))
except Exception as e:
    print 'Connection not working... %s' % (e)
    exit()

userInput(s, name)

请注意,这不是正确的线程处理。但这是一个最小的示例,可以向您展示如何解决该问题的一般要点。

最后,fcntl 也是一个不错的选择。

为未来的技巧进一步编程

使用print(...) 代替print ...
使用'Connection not working... {}'.format(e) 而不是'Connection not working... %s' % (e)
总的来说,尝试使用 Python3 而不是 Python2。只有当你有一个充满旧东西的旧环境(阅读:debian、旧开发的应用程序等)时,才应该真正使用 Python2。

【讨论】:

    猜你喜欢
    • 2021-03-10
    • 2020-12-19
    • 1970-01-01
    • 1970-01-01
    • 2014-04-18
    • 2020-03-20
    • 2015-06-27
    • 2015-10-12
    • 2011-01-04
    相关资源
    最近更新 更多