Python中的多线程多客户端服务器

0 投票
3 回答
16069 浏览
提问于 2025-04-16 15:04

我正在用Python写一个多线程的多客户端服务器。多个用户可以通过telnet连接到这个服务器,基本上可以把它当作一个聊天服务器。我可以通过telnet连接两个客户端,但遇到了以下两个问题:

  1. 第一个发送消息的客户端会立即断开连接。
  2. 另一个客户端收不到第一个客户端发送的消息。

服务器代码:

import os
import sys
import socket
import thread

port = 1941
global message
global lock
global file

def handler(connection):
    while 1:
            file = connection.makefile()
            file.flush()
            temp = file.readline()
            if temp == 'quit':
                break
            lock.acquire()
            message += temp
            lock.release()
            file.write(message)
    file.close()

acceptor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
acceptor.bind(('', port))
acceptor.listen(10)
lock = thread.allocate_lock()

while 1:
    connection, addr = acceptor.accept()
    thread.start_new_thread(handler, (connection,))

好的,我听取了unholysampler的建议,现在我有了这个。我现在可以同时连接两个客户端并输入消息,但消息没有被发送或接收(我不确定是哪种情况)。

import os
import sys
import socket
import thread

port = 1953

def handler(connection):
    global message
    global filelist
    filelist = []
    file = connection.makefile()
    file.flush()
    filelist.append(file)
    message = ''
    while 1:
        i = 0
        while i < (len(filelist)):
            filelist[i].flush()
            temp = filelist[i].readline()

            if temp == 'quit':
                break

            with lock:
                message += temp

            i = i + 1
    file.close()

global lock
acceptor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
acceptor.bind(('', port))
acceptor.listen(10)
lock = thread.allocate_lock()

while 1:
    connection, addr = acceptor.accept()
    thread.start_new_thread(handler, (connection,))

3 个回答

0

我觉得你需要在每次连接之前都调用一次 s.listen。也就是说,把它放在一个无限循环里面。

while True: acceptor.listen(1) #...
0

你这样使用 global 是不对的。当你定义一个方法时,在这个方法的范围内,你需要用 global 命令来引用更高范围的变量。

message = 1
def globalTest():
  global message
  message += 1
  print message

print message
globalTest()
print message

你每次在循环中都在为连接创建一个新的文件对象。你应该在循环开始之前就创建这个对象,这样就只需要做一次。

你在同一个文件对象上进行读取和写入。这就意味着它只是一个回声服务器。你从来没有给线程1一个指向线程2文件的引用。试图用一个全局变量来表示套接字文件是行不通的,因为你永远不知道它实际上指向的是哪个套接字。(问题 #2)

你从来没有初始化 message,所以 message += temp 会抛出一个 UnboundLocalError 错误,提示你在赋值之前就引用了它。(这可能是问题 #1 的原因)另外,你为什么要拼接这个字符串呢?这意味着每次发送时,整个对话都会被发送出去。

另外,不要手动获取和释放锁,使用 with 语句会更简洁。

with lock:
  message += temp
2

Twisted来实现这种功能要简单得多,而且效果更好。Twisted可以让你在一个线程里同时处理多个客户端,还提供了更友好的接口。

下面是用Twisted写一个聊天服务器的方式(完整示例在chatserver.py):

class MyChat(basic.LineReceiver):
    def connectionMade(self):
        print "Got new client!"
        self.factory.clients.append(self)

    def connectionLost(self, reason):
        print "Lost a client!"
        self.factory.clients.remove(self)

    def lineReceived(self, line):
        print "received", repr(line)
        for c in self.factory.clients:
            c.message(line)

    def message(self, message):
        self.transport.write(message + '\n')

对于每个用户,都会创建一个MyChat对象,事件循环会调用它的方法来处理开始/停止事件,以及接收到客户端发来的信息。在这个例子中,它会把收到的每一行信息都发送给系统里的所有客户端。因为它是在一个线程里运行,所以不需要使用锁。

撰写回答