我已经请代码气候去生成度量那里买ftpd红宝石了。它正确地识别了上帝类,我知道该怎么做。但其中一个较小的班级让我很困惑。这是telnet.rb:
# -*- ruby encoding: us-ascii -*-
module Ftpd
# Handle the limited processing of Telnet sequences required by the
# FTP RFCs.
#
# Telnet option processing is quite complex, but we need do only a
# simple subset of it, since we can disagree with any request by the
# client to turn on an option (RFC-1123 4.1.2.12). Adhering to
# RFC-1143 ("The Q Method of Implementing TELNET Option Negiation"),
# and supporting only what's needed to keep all options turned off:
#
# * Reply to WILL sequence with DONT sequence
# * Reply to DO sequence with WONT sequence
# * Ignore WONT sequence
# * Ignore DONT sequence
#
# We also handle the "interrupt process" and "data mark" sequences,
# which the client sends before the ABORT command, by ignoring them.
#
# All Telnet sequence start with an IAC, followed by at least one
# character. Here are the sequences we care about:
#
# SEQUENCE CODES
# ----------------- --------------------
# WILL IAC WILL option-code
# WONT IAC WONT option-code
# DO IAC DO option-code
# DONT IAC DONT option-code
# escaped 255 IAC IAC
# interrupt process IAC IP
# data mark IAC DM
#
# Any pathalogical sequence (e.g. IAC + \x01), or any sequence we
# don't recognize, we pass through.
class Telnet
# The command with recognized Telnet sequences removed
attr_reader :plain
# Any Telnet sequences to send
attr_reader :reply
# Create a new instance with a command that may contain Telnet
# sequences.
# @param command [String]
def initialize(command)
telnet_state_machine command
end
private
module Codes
IAC = 255.chr # 0xff
DONT = 254.chr # 0xfe
DO = 253.chr # 0xfd
WONT = 252.chr # 0xfc
WILL = 251.chr # 0xfb
IP = 244.chr # 0xf4
DM = 242.chr # 0xf2
end
include Codes
def telnet_state_machine (command)
@plain = ''
@reply = ''
state = :idle
command.each_char do |c|
case state
when :idle
if c == IAC
state = :iac
else
@plain << c
end
when :iac
case c
when IAC
@plain << c
state = :idle
when WILL
state = :will
when WONT
state = :wont
when DO
state = :do
when DONT
state = :dont
when IP
state = :idle
when DM
state = :idle
else
@plain << IAC + c
state = :idle
end
when :will
@reply << IAC + DONT + c
state = :idle
when :wont
state = :idle
when :do
@reply << IAC + WONT + c
state = :idle
when :dont
state = :idle
else
raise "Unknown state #{state.inspect}"
end
end
end
end
endCode不喜欢#telnet_state_machine的复杂性。我同意,但我不知道如何降低复杂性,同时也使状态机更难跟踪。状态机似乎从来都不是那么可读的。你有什么建议?
注意:如果您想尝试一些重构,这个类具有rspec测试覆盖率。只需对ftpd项目执行"git克隆“即可。
发布于 2013-10-02 20:17:10
我越多地思考这个问题,就越明显解析器比状态机更能适应这个问题。不幸的是,我对写解析器知之甚少。
也许您可以尝试在每个州创建一个类:
class TelnetState
include Telnet::Codes
attr_reader :plain, :reply
def initialize( plain = '', reply ='' )
@plain = plain
@reply = reply
end
def accept_char( char )
raise "Abstract Method - not implemented"
end
end
class TelnetIDLEState < TelnetState
def accept_char( char )
if char == IAC
TelnetIACState.new( plain, reply )
else
@plain << char
self
end
end
end
class TelnetIACState < TelnetState
def accept_char( char )
update_plain!( char )
next_state( char )
end
private
def update_plain!( char )
return if [WILL, WONT, DO, DONT, IP,DM].include? char
@plain << char == IAC ? char : IAC + char
end
def next_state( char )
next = case char
when WILL then TelnetWILLState
when WONT then TelnetWONTState
when DO then TelnetDOState
when WONT then TelnetDONTState
end
next ? next.new( plain, reply ) : self
end
end..。等等,然后:
class Telnet
def telnet_state_machine( command )
state = command.each_char.inject( TelnetIDLEState.new ) do |state, char|
state.accept_char( char )
end
@plain, @reply = state.plain, state.reply
end
end当然,肯定有办法改善.只是个主意。(我对telnet协议也不太熟悉,所以我做了一些蒙住眼睛的工作)
发布于 2013-10-05 03:13:16
正如@m_x所建议的那样,这个解决方案使用了一个由StringScanner内置类驱动的解析器。这非常紧凑、可读性很强,并且完全摆脱了状态机:
一些处理操作的方法:
def accept(scanner)
@plain << scanner[1]
end
def reply_dont(scanner)
@reply << IAC + DONT + scanner[1]
end
def reply_wont(scanner)
@reply << IAC + WONT + scanner[1]
end
def ignore(scanner)
endtelnet序列的列表:
# Telnet sequences to handle, and how to handle them
SEQUENCES = [
[/#{IAC}(#{IAC})/, :accept],
[/#{IAC}#{WILL}(.)/m, :reply_dont],
[/#{IAC}#{WONT}(.)/m, :ignore],
[/#{IAC}#{DO}(.)/m, :reply_wont],
[/#{IAC}#{DONT}(.)/m, :ignore],
[/#{IAC}#{IP}/, :ignore],
[/#{IAC}#{DM}/, :ignore],
[/(.)/m, :accept],
]以及使用它们的解析器:
# Parse the the command. Sets @plain and @reply
def parse_command(command)
@plain = ''
@reply = ''
scanner = StringScanner.new(command)
while !scanner.eos?
SEQUENCES.each do |regexp, method|
if scanner.scan(regexp)
send method, scanner
break
end
end
end
end发布于 2013-10-05 02:29:51
正如建议的那样,我尝试使用类似于状态_机器的gem。嗯,不是像state_machine这样的宝石,而是特别的宝石。状态机定义是:
state_machine :state, :initial => :idle do
event :iac do
transition :idle => :iac
transition :iac => :idle
transition [:will, :wont, :do, :dont] => :idle
end
event :will do
transition :iac => :will
transition :will => :idle
end
event :dm do
transition :iac => :idle
end
event :ip do
transition :iac => :idle
end
event :dont do
transition :iac => :dont
end
event :other do
transition all => :idle
end
event :do do
transition :iac => :do
end
event :wont do
transition :iac => :wont
end
before_transition :from => :do, :to => :idle, :do => :send_wont
before_transition :from => :will, :to => :idle, :do => :send_dont
before_transition :from => :idle, :to => :idle, :do => :accept_plain
before_transition :from => :iac, :to => :idle, :on => :other, :do => :accept_unknown_iac
before_transition :from => :iac, :to => :idle, :on => :iac, :do => :accept_plain
endstate_machine DSL是干净和相对紧凑的。不幸的是,它是围绕导致转换的事件来组织的,而不是以具有转换的状态为中心的。这还不错,但这完全超出了我对状态机的看法。它还使您可以将操作与转换分开定义。最终的结果是,我无法从这个定义中看到状态机。
但是,state_machine gem有一个rake任务,可以直接从代码生成状态图。下面是它为上述代码生成的内容:

除了缺少操作之外,state_machine生成的状态图非常好。
https://codereview.stackexchange.com/questions/32144
复制相似问题