如何在Tornado异步TCP中同时处理多个命令以从套接字读取?

时间:2015-07-10 15:01:56

标签: python tcp tornado

我的TCP服务器是使用Tornado的异步TCP制作的。客户端是用C语言编写的。

服务器代码:

#! /usr/bin/env python
#coding=utf-8

from tornado.tcpserver import TCPServer
from tornado.ioloop import IOLoop

class TcpConnection(object):
    def __init__(self,stream,address):
        self._stream=stream
        self._address=address
        self._stream.set_close_callback(self.on_close)
        self.send_message(b'hello \n')
        self.send_message(b'world \n')

    def read_message(self):
        self._stream.read_until(b'\n', self.handle_message)

    def handle_message(self,data):
        print(data)

    def send_message(self,data):
        self._stream.write(data)
        self.read_message()

    def on_close(self):
        print("the monitored %d has left",self._address)

class MonitorServer(TCPServer):
    def handle_stream(self,stream,address):
        print("new connection",address,stream)
        TcpConnection(stream,address)

if  __name__=='__main__':
    print('server start .....')
    server=MonitorServer()
    server.listen(20000)
    IOLoop.instance().start()

客户代码:

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

typedef struct SytemInit
{
    char computer[32];      
    char user[32];              
    char os[256];               
    char processor[256];    
    char mem[128];          
    char disk[128];             
}SYSTEMINIT;

typedef struct Command
{
    int  commandType;                             
    char commandInfo[256];                 
}COMMAND;

void main()
{
    int err;
    SYSTEMINIT message;
    COMMAND recvBuf;

    SOCKET sockClient; 
    SOCKADDR_IN addrServer; 

    WSADATA wsaData;
    WORD wVersionRequested;

    wVersionRequested = MAKEWORD( 2, 2 );

    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 )
    {
        return;
    }

    if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
    {
        WSACleanup( );
        return;
    }

    sockClient = socket(AF_INET, SOCK_STREAM, 0);

    addrServer.sin_addr.S_un.S_addr = inet_addr("172.16.110.1");  
    addrServer.sin_family = AF_INET;                           
    addrServer.sin_port = htons(20000);                         

    connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

    recv(sockClient, (char*)&recvBuf, 100, 0);

    strcpy(message.computer,"zz-pc");
    strcpy(message.disk,"zz-disk");
    strcpy(message.mem,"zz-men");
    strcpy(message.os,"zz-os");
    strcpy(message.processor,"zz-processor");
    strcpy(message.user,"zz-user");

    send(sockClient, (char*)&message, sizeof(message) + 1, 0);

    closesocket(sockClient);
    WSACleanup();
}

执行时出现以下错误:

ERROR:tornado.application:Error in connection callback
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/tornado/tcpserver.py", line 269, in _handle_connection
    future = self.handle_stream(stream, address)
  File "/home/zz/PycharmProjects/monitor/test.py", line 34, in handle_stream
    TcpConnection(stream,address)
  File "/home/zz/PycharmProjects/monitor/test.py", line 15, in __init__
    self.send_message(b'world \n')
  File "/home/zz/PycharmProjects/monitor/test.py", line 25, in send_message
    self.read_message()
  File "/home/zz/PycharmProjects/monitor/test.py", line 18, in read_message
    self._stream.read_until(b'\n', self.handle_message)
  File "/usr/local/lib/python3.4/dist-packages/tornado/iostream.py", line 270, in read_until
    future = self._set_read_callback(callback)
  File "/usr/local/lib/python3.4/dist-packages/tornado/iostream.py", line 658, in _set_read_callback
    assert self._read_callback is None, "Already reading"
AssertionError: Already reading

我想这个错误是因为self.send_message(b'hello \n')self.send_message(b'world \n')同时从套接字读取。 我该如何解决这个问题?

1 个答案:

答案 0 :(得分:4)

您的此代码:

date_histogram

导致以下结果:

self.send_message(b'hello \n')
self.send_message(b'world \n')

由于您使用回调呼叫self._stream.write(b'hello \n') self._stream.read_until(b'\n', self.handle_message) self._stream.write(b'world \n') self._stream.read_until(b'\n', self.handle_message) ,因此您尝试同时并行执行两个read_until。但这是无稽之谈,因为它们是通过TCP连接一个接一个地来的。您必须先阅读一条消息,然后阅读另一条消息。

我觉得使用read_until会让这更容易。你也可以用回调来做;我稍后会说明。

使用gen.coroutine

以下是我如何使用协同程序更改gen.coroutine类:

TcpConnection

通过使用协同程序,您可以按照希望它执行的顺序编写代码,并且可以将响应读取为返回值到局部变量中,而不必使用处理程序方法。每当你class TcpConnection(object): def __init__(self,stream,address): self._stream=stream self._address=address self._stream.set_close_callback(self.on_close) @gen.coroutine def send_messages(self): yield self.send_message(b'hello \n') response1 = yield self.read_message() print(response1) yield self.send_message(b'world \n') # You can receive the result in-line, but you need to wrap with ( ): print((yield self.read_message())) def read_message(self): return self._stream.read_until(b'\n') def send_message(self,data): return self._stream.write(data) def on_close(self): print("the monitored %d has left",self._address) class MonitorServer(TCPServer): @gen.coroutine def handle_stream(self,stream,address): print("new connection",address,stream) conn = TcpConnection(stream,address) yield conn.send_messages() 某事时,你就会暂停等待它完成。

我还将yieldsend_message()分开了,因为我觉得它更清晰了。如果你认为最好将它们放在receive_message()中,你就可以用这样的方法做到这一点:

send_message()

如果你想首先发送这两个消息,然后然后等待接收这两个消息,你也可以这样做:

@gen.coroutine
def send_message(self,data):
    yield self._stream.write(data)
    return (yield self.receive_message())

使用回调

你可以使用协同程序编写任何代码,你可以使用回调进行编码。但是,您需要做的是跟踪回调之间的状态(您所处的位置)。这可以通过在不同的回调之间跳转来完成。例如:

@gen.coroutine
def send_messages(self):
    yield self.send_message(b'hello \n')
    yield self.send_message(b'world \n')
    print((yield self.read_message()))
    print((yield self.read_message()))

或通过其他方式跟踪您在通信中的位置,例如将某些内容存储在类实例的字段中。