Python:SocketServer意外关闭TCP连接


问题内容

我想实现一个TCP / IP网络客户端应用程序,该应用程序将请求发送到Python
SocketServer,并期望得到响应。我从正式的Python
SocketServer示例代码开始

server.py:

#!/usr/bin/env python
# encoding: utf-8

import SocketServer

class MyTCPHandler(SocketServer.StreamRequestHandler):

    def handle(self):
        request  = self.rfile.readline().strip()
        print "RX [%s]: %s" % (self.client_address[0], request)

        response = self.processRequest(request)

        print "TX [%s]: %s" % (self.client_address[0], response)
        self.wfile.write(response)

    def processRequest(self, message):
        if   message == 'request type 01':
            return 'response type 01'
        elif message == 'request type 02':
            return 'response type 02'

if __name__ == "__main__":
    server = SocketServer.TCPServer(('localhost', 12345), MyTCPHandler)
    server.serve_forever()

client.py:

#!/usr/bin/env python
# encoding: utf-8

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    sock.connect(('127.0.0.1', 12345))

    data = 'request type 01'
    sent = sock.sendall(data + '\n')
    if sent == 0:
        raise RuntimeError("socket connection broken")

    received = sock.recv(1024)
    print "Sent:     {}".format(data)
    print "Received: {}".format(received)

    data = 'request type 02'
    sent = sock.sendall(data + '\n')
    if sent == 0:
        raise RuntimeError("socket connection broken")

    received = sock.recv(1024)
    print "Sent:     {}".format(data)
    print "Received: {}".format(received)

except Exception as e:
    print e

finally:
    sock.close()

server.py 输出:

RX [127.0.0.1]: request type 01
TX [127.0.0.1]: response type 01

client.py 输出:

Sent:     request type 01
Received: response type 01
[Errno 54] Connection reset by peer

怎么了?似乎服务器正在关闭连接。我如何使其保持打开状态?

注意:这是C ++ /
Qt
的后续问题:QTcpSocket在读取后不会写入

更新(在 abarnert的回答之后):

我从中SocketServer.StreamRequestHandler得到的不是最新的设计,虽然它允许我通过网络进行连接,但是它并不能真正为我提供所有与TCP
/ IP相关的方面的支持,而这些方面需要我付诸实施。通讯。

这已在Python
3中使用asyncio解决,但由于该项目在Python
2中进行,因此这不是一个选择。因此,我已经在Twisted中实现了上述服务器和客户端:

server.py:

#!/usr/bin/env python
# encoding: utf-8

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor

class SimpleProtocol(LineReceiver):

    def connectionMade(self):
        print 'connectionMade'

    # NOTE: lineReceived(...) doesn't seem to get called

    def dataReceived(self, data):
        print 'dataReceived'
        print 'RX: %s' % data

        if   data == 'request type 01':
            response = 'response type 01'
        elif data == 'request type 02':
            response = 'response type 02'
        else:
            response = 'unsupported request'

        print 'TX: %s' % response
        self.sendLine(response)

class SimpleProtocolFactory(Factory):

    def buildProtocol(self, addr):
        return SimpleProtocol()

reactor.listenTCP(12345, SimpleProtocolFactory(), interface='127.0.0.1')
reactor.run()

client.py:

#!/usr/bin/env python
# encoding: utf-8

from twisted.internet import reactor
from twisted.internet.protocol import Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol

class SimpleClientProtocol(Protocol):
    def sendMessage(self, msg):
        print "[TX]: %s" % msg
        self.transport.write(msg)

def gotProtocol(p):
    p.sendMessage('request type 01')
    reactor.callLater(1, p.sendMessage, 'request type 02')
    reactor.callLater(2, p.transport.loseConnection)

point = TCP4ClientEndpoint(reactor, '127.0.0.1', 12345)
d = connectProtocol(point, SimpleClientProtocol())
d.addCallback(gotProtocol)
reactor.run()

客户端不会关闭,但是直到CTRL+闲置C。Twisted可能要花点时间才能动手,但对于手头的工作,显然使用一个经过测试和尝试的框架比亲自完成所有这些基础工作更为合理。

注意: 这在Twisted
XmlStream中
继续:如何连接到事件?


问题答案:

这里的问题是,在中TCPHandler,“请求”实际上是从头到尾的完整连接。*您的处理程序被调用accept,从中返回时,套接字被关闭。

如果要在此之上构建一个请求-响应-
协议处理程序,该处理程序在单个套接字级请求上处理多个协议级请求,则您必须自己执行此操作(或使用更高级别的框架)。(类似的子类BaseHTTPServer演示了如何执行此操作。)

例如,您可以在handle函数中使用循环。当然,您可能想要在此处添加一个异常处理程序和/或处理EOF from
rfile(注释self.rfile.readline()将返回''EOF,但返回'\n'空行,因此您必须在调用前进行检查,strip除非您希望空行表示“退出”在您的协议中)。例如:

def handle(self):
    try:
        while True:
            request  = self.rfile.readline()
            if not request:
                break
            request = request.rstrip()
            print "RX [%s]: %s" % (self.client_address[0], request)

            response = self.processRequest(request)

            print "TX [%s]: %s" % (self.client_address[0], response)
            self.wfile.write(response + '\n')
    except Exception as e:
        print "ER [%s]: %r" % (self.client_address[0], e)
    print "DC [%s]: disconnected" % (self.client_address[0])

通常
可以与您现有的客户端一起使用,至少可以在已卸载的计算机上通过localhost进行,但这实际上是不正确的,而且“经常工作”通常还不够好。请参阅TCP套接字是字节流,而不是消息流,以进行更长时间的讨论,但简要地说,您 需要执行David
Schwarz的回答中
提到的内容:将新行添加到从服务器写入的内容(我已经在上面做过)了,并具有客户端逐行读取,而不是一次尝试读取1024个字节(您可以通过编写自己的缓冲区和分割行代码或仅通过使用makefile方法来做到这一点,因此它可以rfile.readline()像服务器一样使用边做。)

不修复客户端不会引起回答索赔的问题,但是 引起如下问题:

Sent:     request type 01
Received: resp
Sent:     request type 02
Received: onse type 01
response typ

您会看到,在一个实际试图以编程方式处理响应的真实程序中,类似response type 01\nresponse typ将不会非常有用的响应…


*请注意,这SocketServer是一个没人真正喜欢的古老设计。有一个添加Python 3的原因asyncio,并且Python 2中的人通常使用Twistedgevent之类的第三方框架。对于简单任务,它们既简单,又对复杂任务更灵活/强大(并且效率更高)。