简体   繁体   English

Python:SocketServer意外关闭TCP连接

[英]Python: SocketServer closes TCP connection unexpectedly

I would like to implement a TCP/IP network client application that sends requests to a Python SocketServer and expects responses in return. 我想实现一个TCP / IP网络客户端应用程序,该应用程序将请求发送到Python SocketServer,并期望得到响应。 I have started out with the official Python SocketServer sample code : 我从正式的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 output: server.py输出:

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

client.py output: client.py输出:

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

What am doing wrong ? 怎么了? It seems the server is closing the connection. 似乎服务器正在关闭连接。 How can I make it stay open ? 我如何使其保持打开状态?

Note: This is a follow-up question to C++/Qt: QTcpSocket won't write after reading 注意:这是C ++ / Qt的后续问题:QTcpSocket在读取后不会写入

Update (after abarnert's answer ): 更新(在abarnert的回答之后 ):

What I take away from this is that SocketServer.StreamRequestHandler is not the most recent design and while it allows me to connect over a network, it doesn't really support me with all the TCP/IP-related aspects I need to take care of to implement robust communication. 我从中得到的不是SocketServer.StreamRequestHandler不是最新的设计,尽管它允许我通过网络进行连接,但它并不能真正为我需要照顾的所有与TCP / IP相关的方面提供支持。实现强大的沟通。

This has been addressed in Python 3 with asyncio , but as the project lives in Python 2, that's not an option. 这已在Python 3中使用asyncio进行了解决,但是由于该项目在Python 2中进行,因此这不是一个选择。 I have therefore implemented the server and client described above in Twisted : 因此,我已经在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()

The client doesn't close, but idles until CTRL + C . 客户端不会关闭,但是直到CTRL + C才空闲。 Twisted might take a while to get my head around, but for the job at hand, it clearly seems more reasonable to employ a tested and tried framework than to do all this groundwork myself. Twisted可能要花点时间才能明白我的意思,但是对于手头的工作,显然使用一个经过测试和尝试的框架比亲自完成所有这些基础更为合理。

NOTE: This is continued at Twisted XmlStream: How to connect to events? 注意:这在Twisted XmlStream中继续:如何连接到事件?

The problem here is that in a TCPHandler , a "request" is actually a complete connection, from beginning to end.* Your handler gets called on accept , and when you return from it, the socket gets closed. 这里的问题是,在TCPHandler ,“请求”实际上是从头到尾的完整连接。*您的处理程序在accept上被调用,当您从其返回时,套接字被关闭。

If you want to build a request-response-protocol handler on top of that, which processes multiple protocol-level requests on a single socket-level request, you have to do that yourself (or use a higher-level framework). 如果要在此之上构建一个请求-响应-协议处理程序,该处理程序可在单个套接字级请求上处理多个协议级请求,则您必须自己做(或使用更高级别的框架)。 (Subclasses like BaseHTTPServer demonstrate how to do this.) (诸如BaseHTTPServer类的子类演示了如何执行此操作。)

For example, you can just use a loop within your handle function. 例如,您可以只在handle函数中使用循环。 Of course you probably want to add an exception handler here and/or deal with EOF from rfile (note self.rfile.readline() will return '' for EOF, but '\\n' for a blank line, so you have to check it before calling strip unless you want a blank line to mean "quit" in your protocol). 当然,您可能想要在此处添加异常处理程序和/或处理rfile EOF(请注意self.rfile.readline()对于EOF将返回'' ,而对于空白行则返回'\\n' ,因此您必须检查除非您希望空白行在协议中表示“退出”,否则在调用strip之前将其strip )。 For example: 例如:

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])

This will often work with your existing client, at least over localhost on an unloaded machine, but it's not actually correct, and "often works" is rarely good enough. 通常可以与您现有的客户端一起使用,至少可以在卸载的计算机上通过localhost来使用,但这实际上是不正确的,并且“经常工作”几乎不够好。 See TCP sockets are byte streams, not message streams for a longer discussion, but briefly, you also need to do the stuff mentioned in David Schwarz's answer : append newlines to what you write from the server (which I already did above), and have the client read line by line instead of just trying to read 1024 bytes at a time (which you can do by writing your own buffer-and-split-lines code, or just by using the makefile method, so it can use rfile.readline() just like the server side does.) 请参阅TCP套接字是字节流,而不是消息流 ,以进行更长时间的讨论,但简要地说,您需要执行David Schwarz的回答中提到的内容:将换行符追加到您从服务器写入的内容(我已经在上面做过)了,并且客户端逐行读取,而不是一次尝试读取1024个字节(您可以通过编写自己的缓冲和分割行代码或仅通过使用makefile方法来执行此操作,因此它可以使用rfile.readline()就像服务器端一样。)

Not fixing the client won't cause the problems that answer claims, but it will cause problems like this: 不修复客户端不会引起回答索赔的问题,但是引起如下问题:

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

And you can see that in a real program that actually tried to process the responses programmatically, a response like resp or onse type 01\\nresponse typ isn't going to be very useful… 您会看到,在实际试图以编程方式处理响应的真实程序中,诸如response type 01\\nresponse typ这样的响应将不会非常有用……


* Note that SocketServer is an ancient design that nobody really loves. *请注意, SocketServer是一个古老的设计,没有人真正喜欢过。 There's a reason Python 3 added asyncio , and that people in Python 2 usually use third-party frameworks like Twisted and gevent . Python 3添加了asyncio是有原因的,并且Python 2中的人们通常使用Twistedgevent之类的第三方框架。 They're both simpler for simple tasks, and more flexible/powerful (and a lot more efficient) for complex tasks. 对于简单的任务,它们既简单,又对复杂的任务,功能更加灵活/强大(并且效率更高)。

The client is broken. 客户端坏了。 It calls recv just once and then closes the connection without ensuring it has received everything the server had to send. 它仅调用一次recv ,然后关闭连接,而无需确保已接收到服务器必须发送的所有内容。

The correct way to fix this depends on the protocol you are using, which you haven't explained. 解决此问题的正确方法取决于您所使用的协议,您尚未解释。 It's not apparent from the server code what the protocol for server to client "messages" is. 从服务器代码中看不到服务器到客户端“消息”的协议是什么。 What is the client supposed to expect to get from the server? 客户端应该从服务器得到什么? The server, for example, expects a line from the client, a message marked with a newline to indicate the end of the line. 例如,服务器希望客户端发送一行 ,消息中标有换行符以指示该行的结尾。 So the server knows when it has received a message by checking for a newline. 因此,服务器通过检查换行符来知道何时接收到消息。 How is the client supposed to do it? 客户应该怎么做? Where is that code? 该代码在哪里?

I'll assume for the sake of argument that your protocol always uses messages and defines a message as terminated by a newline. 为了便于讨论,我假设您的协议始终使用消息并将消息定义为以换行符终止。

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

This reads a message because it calls readline . 这将读取一条消息,因为它调用readline

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

This sends a message because it sends a series of bytes terminated by a newline. 这会发送一条消息,因为它会发送一系列以换行符结尾的字节。

received = sock.recv(1024)

Oops, this just receives some bytes, not a message. 糟糕,这只是接收一些字节,而不是消息。 There needs to be code to check if a newline was received and, if not, call recv again, otherwise, calling close will force an abnormal close of the socket (because the message was not, and can never be, received), which is precisely what you are seeing. 需要有代码来检查是否接收到换行符,否则,请再次调用recv ,否则,调用close将强制套接字异常关闭(因为该消息未被接收,并且永远无法被接收到),这是正是您所看到的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM