Python访问BaseRequestHandler

1 投票
2 回答
3106 浏览
提问于 2025-04-15 15:07

我的代码基本上是要启动一个简单的聊天服务器和客户端,让服务器和客户端可以互相交流。我已经把所有的东西都实现得差不多了,但我就是不知道怎么在完成后关闭服务器。(我知道是 ss.shutdown())。

我想根据双方共享的一个关键词(比如 "bye")来结束聊天,但我不知道是否可以从 BaseRequestHandler 向我的 SocketServer 发送消息,以便在收到这个消息时调用 shutdown()

最终,我的目标是加入 Tkinter 来做一个图形界面,但我想先把其他的功能搞定,这是我第一次在 Python 中处理 sockets。

from sys import argv, stderr
from threading import Thread
import socket
import SocketServer
import threading
import sys

class ThreadedRecv(Thread):
    def __init__(self,socket):
        Thread.__init__(self)
        self.__socket = socket
        self.__message = ''
        self.__done = False
    def recv(self):
        while self.__message.strip() != "bye" and not self.getStatus():
            self.__message = self.__socket.recv(4096)
            print 'received',self.__message
        self.setStatus(True)

    def run(self):
        self.recv()

    def setStatus(self,status):
        self.__done = status

    def getStatus(self):
        return self.__done

class ThreadedSend(Thread):
    def __init__(self,socket):
        Thread.__init__(self)
        self.__socket = socket
        self.__message = ''
        self.__done = False
    def send(self):
        while self.__message != "bye" and not self.getStatus():
            self.__message = raw_input()
            self.__socket.send(self.__message)
        self.setStatus(True)

    def run(self):
        self.send()

    def setStatus(self,status):
        self.__done = status

    def getStatus(self):
        return self.__done



class HostException(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

class EchoServer(SocketServer.BaseRequestHandler):
    def setup(self):
        print self.client_address, 'is connected!'
        self.request.send('Hello ' + str(self.client_address) + '\n')
        self.__done = False
    def handle(self):

        sender = ThreadedSend(self.request)
        recver = ThreadedRecv(self.request)
        sender.start()
        recver.start()
        while 1:
            if recver.getStatus():
                sender.setStatus(True)
                break
            if sender.getStatus():
                recver.setStatus(True)
                break         

    def finish(self):
        print self.client_address, 'disconnected'
        self.request.send('bye client %s\n' % str(self.client_address))
        self.setDone(True)

    def setDone(self,done):
        self.__done = done

    def getDone(self):
        return self.__done



def setup(arg1, arg2, arg3):
    server = False
    defaultPort,defaultHost = 2358,"localhost"
    hosts = []
    port = defaultPort
    serverNames = ["TRUE","SERVER","S","YES"]
    arg1 = arg1.upper()
    arg2 = arg2.upper()
    arg3 = arg3.upper()
    if arg1 in serverNames or arg2 in serverNames or arg3 in serverNames:
    server = True
    try:
        port = int(arg1)
        if arg2 != '':
            hosts.append(arg2)
    except ValueError:
        if arg1 != '':
            hosts.append(arg1)
        try:
            port = int(arg2)
            if arg3 != '':
                hosts.append(arg3)
        except ValueError:
            if arg2 != '':
                hosts.append(arg2)
            try:
                port = int(arg3)
            except ValueError:
                if arg3 != '':
                    hosts.append(arg3)
                port = defaultPort

    for sn in serverNames:
        if sn in hosts:
            hosts.remove(sn)

    try:
        if len(hosts) != 1:
            raise HostException("Either more than one or no host "+ \
                                "declared.  Setting host to localhost.")
    except HostException as error:
        print error.value, "Setting hosts to default"
        return (server,defaultHost,port)

    return (server,hosts[0].lower(),port)

def main():
    bufsize = 4096
    while len(argv[1:4]) < 3:
        argv.append('')
    settings = setup(*argv[1:4])
    connections = (settings[1],settings[2])
    print connections
    if not settings[0]:
        try:
            mySocket = socket.socket(socket.AF_INET,\
                                     socket.SOCK_STREAM)
        except socket.error, msg:
            stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(1)
        try:
            mySocket.connect(connections)
        except socket.error, msg:
            stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(2)

        message = ""
        print "Enter a message to send to the server. "+\
              "Enter \"bye\" to quit."
        sender = ThreadedSend(mySocket)
        recver = ThreadedRecv(mySocket)
        sender.start()
        recver.start()
        while 1:
            if sender.getStatus():
                recver.setStatus(True)
                break
            if recver.getStatus():
                sender.setStatus(True)
                break    

    else:
        xserverhandler = EchoServer
        serversocket = SocketServer.ThreadedTCPServer(\
            connections,xserverhandler)
        server_thread = Thread(target = serversocket.serve_forever)
        server_thread.setDaemon(True)
        server_thread.start()
        # I would like to shut down this server whenever 
        # I get done talking to it.
        """while 1:
            if xserverhandler.getDone():
                print 'This is now true!'
                serversocket.shutdown()
                break"""

if __name__ == '__main__':
    main()

是的,我知道现在的 setup() 函数写得很糟糕,有很多 try 和 catch,但目前能用,所以我打算以后再修。

我的问题基本上是:我怎么才能让服务器在收到某个消息后真正结束?如果可以的话,有没有办法在它启动后访问请求处理器?

2 个回答

1

每当有新的请求时,都会创建一个请求处理对象。所以你需要把“完成”标志存储在服务器上,而不是存储在处理器里。可以像下面这样做:

class EchoServer(SocketServer.BaseRequestHandler):
    ...
    def setDone(self):
        self.server.setDone() # or even better directly self.server.shutdown()
2

请修正你的代码,让它能正常工作,并提供一些使用方法。你需要添加

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

因为SocketServer实际上并不包含那个类(至少在我用的2.6或2.7版本中没有)。相反,这是来自SocketServer定义的一个示例。

请提供一个如何启动/使用代码的示例。在这种情况下,要启动服务器,你需要这样做:

ss.py SERVER localhost 8001

而客户端则是这样

ss.py localhost 8001

如果你这样做了,那么你就不能使用server_thread.setDaemon(True),因为没有其他线程在运行,这意味着服务器会立即退出。

完成这些后,解决方案是在你的EchoServer.handle方法中添加一次(或两次)对self.server.shutdown()的调用,比如:

    while 1:
        if recver.getStatus():
            sender.setStatus(True)
            self.server.shutdown()
            break

不过,我无法让这个工作,我觉得可能是因为我继承的东西不对,或者对你做的事情猜错了。

你应该去找其他人用Python做的聊天服务器。用谷歌搜索,我找到了一些资料,比如http://www.slideshare.net/didip/socket-programming-in-python,肯定还有其他的。

另外,如果你打算混合使用图形界面和多线程编程,那么你应该查看一些基于这个的示例。当我搜索“tkinter聊天”时,有很多相关的结果。此外,你可能还想了解一下twisted,它已经解决了很多这些问题。

什么问题呢?比如,你可能想要一个SO_REUSEADDR的套接字选项。

撰写回答