Python:SocketServer意外关闭TCP连接

时间:2015-04-10 22:42:55

标签: python sockets tcp network-programming twisted

我想实现一个TCP / IP网络客户端应用程序,它将请求发送到Python SocketServer,并期望得到响应。我已经开始使用官方Python SocketServer sample code

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 won't write after reading

的后续问题

更新(abarnert's answer之后):

我从这里得到的是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 XmlStream: How to connect to events?

继续

2 个答案:

答案 0 :(得分:4)

问题在于TCPHandler,"请求"实际上是一个完整的连接,从头到尾。*你的处理程序在accept被调用,当你从它返回时,套接字被关闭。

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

例如,您可以在handle函数中使用循环。当然你可能想在这里添加一个异常处理程序和/或处理来自rfile的EOF(注意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])

这将经常与您现有的客户端一起工作,至少在卸载的计算机上的本地主机上工作,但它实际上并不正确,并且通常可以正常工作。很少够好。请参阅TCP sockets are byte streams, not message streams进行更长时间的讨论,但简单地说,您需要执行David Schwarz's answer中提到的内容:在服务器上写下的内容附加换行符(我已经做过了)以上),让客户端逐行读取,而不是一次只读取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。对于简单的任务,它们既简单又简单,对复杂的任务更灵活/更强大(更高效)。

答案 1 :(得分:-3)

客户端坏了。它只调用recv一次,然后关闭连接,而不确保它已收到服务器必须发送的所有内容。

解决此问题的正确方法取决于您使用的协议,您尚未解释。从服务器代码来看,服务器到客户端“消息”的协议是什么并不明显。客户端应该从服务器获得什么?例如,服务器需要来自客户端的,标记有换行符的消息表示行的结束。因此,服务器通过检查换行符来知道它何时收到了消息。客户应该如何做到这一点?该代码在哪里?

为了论证,我假设您的协议始终使用消息并将消息定义为换行符终止。

    request  = self.rfile.readline().strip()

这会读取消息,因为它会调用readline

sent = sock.sendall(data + '\n')

这会发送消息,因为它会发送一个由换行符终止的一系列字节。

received = sock.recv(1024)

糟糕,这只是接收一些字节,而不是消息。需要有代码来检查是否收到了换行符,如果没有,再次调用recv,否则,调用close将强制关闭套接字异常(因为消息不是,并且可以永远不会被收到,这正是你所看到的。