如何在GNU无线电中重新连接TCP源?

时间:2016-01-05 17:30:17

标签: python sockets gnuradio

如何在GNU无线电中重新连接TCP源服务器?连接客户端一次后,它就无法接受新连接。研究tcp.py之后很明显这是因为python部分只调用accept一次。

此问题之前曾在邮件列表中被问到:https://lists.gnu.org/archive/html/discuss-gnuradio/2010-04/msg00501.html但答案有点不尽如人意,因为据说对某些" dup magic"很容易。

我设法通过以下方式使用文件描述符作为Python中的标识符来重新连接套接字:

#!/usr/bin/env python

import os
import socket
import sys
import threading

def get_server_socket_and_fd():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 9999))
    s.listen(1)
    print "waiting for client connection on port 9999"
    cs, address = s.accept()
    return s, os.dup(cs.fileno())

class StoppableThread(threading.Thread):
    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop = threading.Event()

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()

class SocketWatcher(StoppableThread):
    def __init__(self, server_socket, fd):
        super(SocketWatcher, self).__init__()
        self.setDaemon(True)
        self._fd = fd
        self._server_socket = server_socket

    def run(self):
        print "WWW: starting watcher on fd = {}".format(self._fd)
        while not self.stopped():
            #print "WWW: creating socket object from fd = {}".format(self._fd)
            s = socket.fromfd(self._fd, socket.AF_INET, socket.SOCK_STREAM)
            while not self.stopped():
                print "WWW: trying to peek for new data"
                data = s.recv(1024, socket.MSG_PEEK)
                print "WWW: msg peek returned: {} bytes".format(len(data))
                if len(data) == 0:
                    print "WWW: EOF? closing socket"
                    s.close()
                    print "WWW: waiting for new connection..."
                    cs, address = self._server_socket.accept()
                    print "WWW: new connection! fileno = {}".format(cs.fileno())
                    print "WWW: duplicating this client socket fd into the old one"
                    os.dup2(cs.fileno(), self._fd)
                    break
        print "WWW: thread stopped, exiting run method"

server_socket, client_fd = get_server_socket_and_fd()

watcher = SocketWatcher(server_socket, client_fd)
watcher.start()

try:
    while True:
        s = socket.fromfd(client_fd, socket.AF_INET, socket.SOCK_STREAM)
        data = s.recv(1024)
        if len(data) > 0:
            print "    data received: {} bytes".format(len(data))
            print repr(data)            

except (KeyboardInterrupt, SystemExit):
    print "stopping program..."
    watcher.stop()
    sys.exit()

工作方式与预期一样:启动python脚本,连接到端口,写东西,关闭连接,打开另一个连接,写更多东西,并注意它们继续打印。

然而,当我尝试将其集成到GNU无线电中时:它不起作用。这是我最好的尝试:

#
# Copyright 2009 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from gnuradio import gr, blocks
import socket
import os
import threading

class StoppableThread(threading.Thread):
    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop = threading.Event()

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()

class SocketWatcher(StoppableThread):
    def __init__(self, server_socket, fd):
        super(SocketWatcher, self).__init__()
        self.setDaemon(True)
        self._fd = fd
        self._server_socket = server_socket

    def run(self):
        while not self.stopped():
            s = socket.fromfd(self._fd, socket.AF_INET, socket.SOCK_STREAM)
            while not self.stopped():
                data = s.recv(1024, socket.MSG_PEEK)
                if len(data) == 0:
                    print "EOF detected. Closing socket and waiting for new connection..."
                    s.close()
                    cs, address = self._server_socket.accept()
                    print "got new connection!"
                    os.dup2(cs.fileno(), self._fd)
                    break


def _get_sock_fd(addr, port, server):
    """
    Get the file descriptor for the socket.
    As a client, block on connect, dup the socket descriptor.
    As a server, block on accept, dup the client descriptor.

    Args:
        addr: the ip address string
        port: the tcp port number
        server: true for server mode, false for client mode

    Returns:
        the file descriptor number
    """
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if server:
        sock.bind((addr, port))
        sock.listen(1)
        clientsock, address = sock.accept()
        return os.dup(clientsock.fileno()), sock
    else:
        sock.connect((addr, port))
        return os.dup(sock.fileno()), sock

class tcp_source(gr.hier_block2):
    def __init__(self, itemsize, addr, port, server=True):
        #init hier block
        gr.hier_block2.__init__(
            self, 'tcp_source',
            gr.io_signature(0, 0, 0),
            gr.io_signature(1, 1, itemsize),
        )
        if not server:
            raise NotImplementedError
        fd, server_socket = _get_sock_fd(addr, port, server)
        watcher = SocketWatcher(server_socket, fd)
        watcher.start()
        self.connect(blocks.file_descriptor_source(itemsize, fd), self)

class tcp_sink(gr.hier_block2):
    def __init__(self, itemsize, addr, port, server=False):
        #init hier block
        gr.hier_block2.__init__(
            self, 'tcp_sink',
            gr.io_signature(1, 1, itemsize),
            gr.io_signature(0, 0, 0),
        )
        fd, _ = _get_sock_fd(addr, port, server)
        self.connect(self, blocks.file_descriptor_sink(itemsize, fd))

我在命令行上看到了:

EOF detected. Closing socket and waiting for new connection...
got new connection!

这表明检测到EOF是成功的,但是,一旦我重新连接连接被接受,但是我写入套接字的任何内容都不会出现在另一端。我使用一个简单的GNU无线电程序来测试它,包括一个TCP源(我的版本),一个节流阀和一个TCP接收器。

<?xml version='1.0' encoding='utf-8'?>
<?grc format='1' created='3.7.8'?>
<flow_graph>
  <timestamp>Fri Dec 18 14:18:32 2015</timestamp>
  <block>
    <key>options</key>
    <param>
      <key>author</key>
      <value></value>
    </param>
    <param>
      <key>window_size</key>
      <value></value>
    </param>
    <param>
      <key>category</key>
      <value>Custom</value>
    </param>
    <param>
      <key>comment</key>
      <value></value>
    </param>
    <param>
      <key>description</key>
      <value></value>
    </param>
    <param>
      <key>_enabled</key>
      <value>True</value>
    </param>
    <param>
      <key>_coordinate</key>
      <value>(8, 8)</value>
    </param>
    <param>
      <key>_rotation</key>
      <value>0</value>
    </param>
    <param>
      <key>generate_options</key>
      <value>qt_gui</value>
    </param>
    <param>
      <key>id</key>
      <value>top_block</value>
    </param>
    <param>
      <key>max_nouts</key>
      <value>0</value>
    </param>
    <param>
      <key>realtime_scheduling</key>
      <value></value>
    </param>
    <param>
      <key>run_options</key>
      <value>prompt</value>
    </param>
    <param>
      <key>run</key>
      <value>True</value>
    </param>
    <param>
      <key>thread_safe_setters</key>
      <value></value>
    </param>
    <param>
      <key>title</key>
      <value></value>
    </param>
  </block>
  <block>
    <key>variable</key>
    <param>
      <key>comment</key>
      <value></value>
    </param>
    <param>
      <key>_enabled</key>
      <value>True</value>
    </param>
    <param>
      <key>_coordinate</key>
      <value>(8, 160)</value>
    </param>
    <param>
      <key>_rotation</key>
      <value>0</value>
    </param>
    <param>
      <key>id</key>
      <value>samp_rate</value>
    </param>
    <param>
      <key>value</key>
      <value>32000</value>
    </param>
  </block>
  <block>
    <key>blks2_tcp_sink</key>
    <param>
      <key>addr</key>
      <value>127.0.0.1</value>
    </param>
    <param>
      <key>alias</key>
      <value></value>
    </param>
    <param>
      <key>comment</key>
      <value></value>
    </param>
    <param>
      <key>affinity</key>
      <value></value>
    </param>
    <param>
      <key>_enabled</key>
      <value>True</value>
    </param>
    <param>
      <key>_coordinate</key>
      <value>(696, 125)</value>
    </param>
    <param>
      <key>_rotation</key>
      <value>0</value>
    </param>
    <param>
      <key>id</key>
      <value>blks2_tcp_sink_0</value>
    </param>
    <param>
      <key>type</key>
      <value>complex</value>
    </param>
    <param>
      <key>server</key>
      <value>True</value>
    </param>
    <param>
      <key>port</key>
      <value>9001</value>
    </param>
    <param>
      <key>vlen</key>
      <value>1</value>
    </param>
  </block>
  <block>
    <key>blks2_tcp_source</key>
    <param>
      <key>addr</key>
      <value>127.0.0.1</value>
    </param>
    <param>
      <key>alias</key>
      <value></value>
    </param>
    <param>
      <key>comment</key>
      <value></value>
    </param>
    <param>
      <key>affinity</key>
      <value></value>
    </param>
    <param>
      <key>_enabled</key>
      <value>True</value>
    </param>
    <param>
      <key>_coordinate</key>
      <value>(344, 136)</value>
    </param>
    <param>
      <key>_rotation</key>
      <value>0</value>
    </param>
    <param>
      <key>id</key>
      <value>blks2_tcp_source_0</value>
    </param>
    <param>
      <key>maxoutbuf</key>
      <value>0</value>
    </param>
    <param>
      <key>minoutbuf</key>
      <value>0</value>
    </param>
    <param>
      <key>server</key>
      <value>True</value>
    </param>
    <param>
      <key>type</key>
      <value>complex</value>
    </param>
    <param>
      <key>port</key>
      <value>9000</value>
    </param>
    <param>
      <key>vlen</key>
      <value>1</value>
    </param>
  </block>
  <block>
    <key>blocks_throttle</key>
    <param>
      <key>alias</key>
      <value></value>
    </param>
    <param>
      <key>comment</key>
      <value></value>
    </param>
    <param>
      <key>affinity</key>
      <value></value>
    </param>
    <param>
      <key>_enabled</key>
      <value>True</value>
    </param>
    <param>
      <key>_coordinate</key>
      <value>(536, 112)</value>
    </param>
    <param>
      <key>_rotation</key>
      <value>0</value>
    </param>
    <param>
      <key>id</key>
      <value>blocks_throttle_0</value>
    </param>
    <param>
      <key>ignoretag</key>
      <value>True</value>
    </param>
    <param>
      <key>maxoutbuf</key>
      <value>0</value>
    </param>
    <param>
      <key>minoutbuf</key>
      <value>0</value>
    </param>
    <param>
      <key>samples_per_second</key>
      <value>samp_rate</value>
    </param>
    <param>
      <key>type</key>
      <value>complex</value>
    </param>
    <param>
      <key>vlen</key>
      <value>1</value>
    </param>
  </block>
  <connection>
    <source_block_id>blks2_tcp_source_0</source_block_id>
    <sink_block_id>blocks_throttle_0</sink_block_id>
    <source_key>0</source_key>
    <sink_key>0</sink_key>
  </connection>
  <connection>
    <source_block_id>blocks_throttle_0</source_block_id>
    <sink_block_id>blks2_tcp_sink_0</sink_block_id>
    <source_key>0</source_key>
    <sink_key>0</sink_key>
  </connection>
</flow_graph>

1 个答案:

答案 0 :(得分:0)

说实话,我总觉得tcp_sink_source只是不完整的黑客行为,主要是为了表明如何快速地做一些简单而不是正确的事情;注意到版权年份,至少在过去的6年里,没有人真正尝试改进这种方法,这主要是因为它非常不完整。其中有quite some problems,正如您可能已经注意到的那样,构造函数只要没有连接就会阻塞,而不是让其他块也被实例化然后在其work中阻塞。直到它可以消耗样品。

我不完全确定这里出了什么问题,但这很可能是一个python多线程问题 - GNU Radio调度程序为每个信号处理块产生一个自己的线程,并且在什么情况下它对于python并不总是透明的调用块的功能。

因此,第一个问题是:您是否需要网络 TCP ?如果您需要任何运行良好的无状态服务器,请使用UDP服务器;它们使用异步IO实现,工作非常可靠。如果您需要优雅,完整性保证的网络,请尝试使用zeroMQ块。

假设你真的想要TCP:

我认为你最好的行动方案实际上是抛弃现有的基于hier_block和file_descriptor_sink的方法,只需处理你自己的python或C ++接收器块中的连接。

那不是很难;你似乎对编写GNU Radio python块足够熟练,但作为参考(以及后来的读者),我想指出你在快速介绍SDR之后的Guided Tutorials和GRC用法介绍你编写Python块。

一般来说,它归结为

  1. 生成树外模块gr_modtool newmod
  2. cd <modulename>
  3. 添加python sink block gr_modtool add
  4. 修改该块以使其具有正确的io_signature,并具有有意义的work功能,即一个
    • 检查现有的TCP连接,
    • 尝试发送调用作品的样本,
    • 如果&gt;
    • 返回已发送的商品数量0,
    • 尝试在没有连接的情况下构建新连接,并阻止至少发送一个项目