【问题标题】:Message size varies TCPServer Ruby消息大小不同 TCPServer Ruby
【发布时间】:2017-09-14 08:49:17
【问题描述】:

我正在使用 AVL (Skypatrol TT8750+),它发送的消息(使用 TCP)应该是 59 字节长,但它总是发送第一条消息(消息包含一些关于 AVL 的信息,所以用户可以识别)33字节。

所以问题是,如何在 ruby​​ 上处理这些不同大小的消息?

require 'socket'

portnumber = 12050
socketServer = TCPServer.open(portnumber)

while true
  Thread.new(socketServer.accept) do |connection|
  puts "Accepting connection from: #{connection.peeraddr[2]}"
  t = Time.now.strftime("%d-%m-%Y %H%M")
  file_name = t + '.txt'
  out_file = File.new(file_name, "w+")
  begin
    while connection
      incomingData = connection.gets()
      if incomingData != nil
        incomingData = incomingData
      end
      hex_line = incomingData.unpack('H*')[0]
      out_file.puts(hex_line)
      puts "Incoming: #{hex_line}"
    end
    rescue Exception => e
      # Displays Error Message
      puts "#{ e } (#{ e.class })"
    ensure
      connection.close
      puts "ensure: Closing"
    end
  end
end

这是我正在使用的实验代码。

【问题讨论】:

    标签: ruby sockets messages tcpserver


    【解决方案1】:

    我发布此答案是为了解释我对安德森的回答所做的评论。大部分代码不是我的。


    if 移出循环

    if 语句在循环中时,每次循环运行时都会对其进行评估,从而增加 CPU 指令的数量和每个循环的复杂度。

    您可以通过将条件语句移出循环来提高性能,如下所示:

    require 'socket'
    require 'celluloid/io'
    
    portnumber = 12050
    socketServer = TCPServer.open(portnumber)
    incomingData = nil
    
    while true
      Thread.new(socketServer.accept) do |connection|
      puts "Accepting connection from: #{connection.peeraddr[2]}"
      # this should probably be changed,
      # it ignores the possibility of two connections arriving at the same timestamp.
      t = Time.now.strftime("%d-%m-%Y %H%M")
      file_name = t + '.txt'
      out_file = File.new(file_name, "w+")
    
      begin
        if connection
          incomingData = conection.recv(33)
          if incomingData != nil
            incomingData = incomingData.unpack('H*')[0]
            out_file.puts(incomingData)
            puts "Incoming: #{incomingData}"
          end
        end
        while connection
          incomingData = connection.recv(59)
          if incomingData != nil
            incomingData = incomingData.unpack('H*')[0]
            out_file.puts(incomingData)
            puts "Incoming: #{incomingData}"
          end
        end
        rescue Exception => e
          # Displays Error Message
          puts "#{ e } (#{ e.class })"
        ensure
          connection.close
          out_file.close
          puts "ensure: Closing"
        end
      end
    end
    

    优化recv方法

    我可能应该提到的另一个优化(但不会在这里实现)是recv 方法调用。

    这既是一种优化,也是应该解决的错误的可能来源。

    recv 是一个系统调用,由于网络消息可能会跨 TCP/IP 数据包组合(或分段),调用recv 可能比处理解决分段和溢出的内部数据缓冲区更昂贵州。

    重新考虑每个客户端的线程设计

    我还建议避免使用每个客户端的线程设计。

    一般来说,对于少数客户来说,这可能并不重要。

    但是,随着客户端数量的增加和线程变得越来越繁忙,您可能会发现系统在上下文切换上花费的资源比实际任务要多。

    另一个问题可能是每个线程需要分配的堆栈(1Mb 或 2Mb 用于 Ruby 线程,如果我没记错的话)...在最好的情况下,1000 个客户端将需要超过 1 GB 的内存分配仅用于堆栈(我忽略了内核结构数据表和其他资源)。

    我会考虑使用 EventMachine 或 Iodine(我是 iodine 的作者,所以我有偏见)。

    事件设计可以为您节省很多资源。

    例如(未经测试):

    require 'iodine'
    # define the protocol for our service
    class ExampleProtocol
      @timeout = 10
      def on_open
        puts "New Connection Accepted."
        # this should probably be changed,
        # it ignores the possibility of two connections arriving at the same timestamp.
        t = Time.now.strftime("%d-%m-%Y %H%M")
        file_name = t + '.txt'
        @out_file = File.new(file_name, "w+")
        # a rolling buffer for fragmented messages
        @expecting = 33
        @msg = ""
      end
    
      def on_message buffer
        length = buffer.length
        pos = 0
        while length >= @expecting
            @msg << (buffer[pos, @expecting])
            out_file.puts(msg.unpack('H*')[0])
            length -= @expecting
            pos += @expecting
            @expecting = 59
            @msg.clear
        end
        if(length > 0)
            @msg << (buffer[pos, length])
            @expecting = 59-length
        end
      end
    
      def on_close
        @out_file.close
      end
    end
    # create the service instance
    Iodine.listen 12050, ExampleProtocol
    # start the service
    Iodine.start
    

    【讨论】:

    • 你的回答真的比我期待的好,谢谢你我用碘酒
    • @AndersonAlbertoOchoaEstupia 谢谢。我很乐意提供帮助。随意投票;-)
    • @AndersonAlbertoOchoaEstupia 我以为你可以为自己的问题投票答案,但不用担心,你会到达那里 ??
    • 如何与您取得联系?我有几个关于宝石@Myst 的问题
    • @AndersonAlbertoOchoaEstupia 可以通过opening an issue on GitHub 留下有关 gem 的问题,或者在此网站上发布问题并向我发送链接(电子邮件、GitHub 或评论)。我的电子邮件不是很有条理,而且我在回复方面很落后,所以这些选项更好。
    【解决方案2】:

    解决方法很简单

    require 'socket'
    require 'celluloid/io'
    
    portnumber = 12050
    socketServer = TCPServer.open(portnumber)
    
    while true
      Thread.new(socketServer.accept) do |connection|
      puts "Accepting connection from: #{connection.peeraddr[2]}"
      t = Time.now.strftime("%d-%m-%Y %H%M")
      file_name = t + '.txt'
      out_file = File.new(file_name, "w+")
      messagecounter = 1
    
      begin
        while connection
          if messagecounter == 1
            incomingData = conection.recv(33)
            messagecounter += 1
          else
            incomingData = connection.recv(59)
          end
          if incomingData != nil
            incomingData = incomingData.unpack('H*')[0]
          end
          out_file.puts(incomingData)
          puts "Incoming: #{incomingData}"
        end
        rescue Exception => e
          # Displays Error Message
          puts "#{ e } (#{ e.class })"
        ensure
          connection.close
          puts "ensure: Closing"
        end
      end
    end
    

    我只需要一个额外的变量和一个 if 来自动增加变量,就是这样。

    【讨论】:

    • 如果将if 移出循环,它的性能会更好。也许改用双步(if connection ... while connection ...)。这将允许您摆脱循环中的额外变量和额外代码,从而提高性能。
    • @Myst 我明白你的想法,但我不知道如何应用
    • 我发布了一个答案,其中包含一些优化指针和可能的错误问题来解释我的评论。祝你好运!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-06
    • 2011-10-28
    • 2014-06-06
    • 2010-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多