【演演算法】狀態之美,TCP/IP狀態轉換探索

2023-11-22 12:02:48

最近城市裡甲流肆虐,口罩已經成為了出門必備的物品。小悅也不得不開始採取防護措施,上下班過程中,將口罩戴起來以保護自己不受病毒的侵害。

每天下班後,小悅總是喜歡投入到自己的興趣愛好中,她熱衷於翻閱與IT相關的資料,希望能夠更深入地瞭解電腦科學。而她的大學同學小欣,則總是拿她開玩笑:「小悅啊,你是不是該考慮一下找男朋友?每天都在研究這些枯燥的演演算法,這可不像你啊。」 小悅總是笑笑不作迴應,她對自己的研究充滿熱情,對男朋友的事情並不著急。

最近,小悅無意中看到了一篇關於TCP/IP狀態轉換的介紹,這個演演算法細節並未詳細闡述,只在網上看到了狀態圖的介紹。這激發了她深入研究TCP/IP有限狀態機的興趣,她決定通過實現這個演演算法來更好地理解TCP/IP協定各狀態跳轉的工作原理。

在實現TCP/IP有限狀態機模擬演演算法的過程中,小悅遇到了許多困難和挑戰。她需要設計一個能夠跟蹤TCP連線狀態的演演算法,並且需要使用字典來定義狀態之間的轉換規則。每個狀態作為字典的鍵,對應的值是另一個字典,該字典包含了從當前狀態觸發的事件和下一個狀態之間的對映關係。

為了實現這個演演算法,小悅開始思考如何著手。她首先定義了一個名為TraverseStates的方法,該方法接受一個字串陣列作為輸入,該陣列包含了TCP連線中發生的事件。然後,她使用一個巢狀的字典tome來定義狀態之間的轉換規則。

在方法的主要邏輯中,小悅通過迭代給定的事件陣列,從當前狀態開始根據事件觸發狀態的轉換。如果在轉換規則中找不到與當前狀態和事件匹配的轉換關係,就返回"ERROR"。如果成功完成了所有事件的處理,最終返回最終狀態。

在實現過程中,小悅遇到了許多問題。例如,有時她會遇到事件陣列中存在不合法事件的情況,導致演演算法無法正確處理。為了解決這個問題,她在程式碼中增加了例外處理機制,對不合法事件進行了過濾和忽略。

另外,她還發現有時在轉換規則中存在冗餘的狀態轉換關係。這些冗餘的關係會導致演演算法的效能下降。為了解決這個問題,她對轉換規則進行了優化,去除了冗餘的狀態轉換關係。

在這個過程中,小欣也加入了她的研究。小欣的專業是計算機網路,對TCP/IP協定有深入的瞭解。她的加入為小悅的研究帶來了新的視角和想法。她們共同研究、探討,不斷優化、測試和完善演演算法。每當小欣看到小悅沉浸在研究中時,總會笑著說:「小悅啊,你還真是找到了你的另一半啊。」 小悅也會笑著迴應:「是啊,我對計算機情有獨鍾。」

最終,經過反覆的測試和優化,小悅和小欣成功地實現了TCP/IP有限狀態機模擬演演算法。該演演算法能夠準確地跟蹤TCP連線的狀態,並根據傳入的事件列表觸發狀態的轉換。


 演演算法實現1:

 1 public static string TraverseStates(string[] r)
 2 {
 3     // 定義一個名為tome的字典,用於儲存狀態轉換規則
 4     var tome = new Dictionary<string,Dictionary<string,string>>()
 5     {
 6         // 初始化狀態轉換規則
 7         {"CLOSED",new Dictionary<string,string>(){{"APP_PASSIVE_OPEN","LISTEN"},{"APP_ACTIVE_OPEN","SYN_SENT"}}},
 8         {"LISTEN",new Dictionary<string,string>(){{"RCV_SYN","SYN_RCVD"},{"APP_SEND","SYN_SENT"},{"APP_CLOSE","CLOSED"}}},
 9         {"SYN_SENT",new Dictionary<string,string>(){{"RCV_SYN","SYN_RCVD"},{"RCV_SYN_ACK","ESTABLISHED"},{"APP_CLOSE","CLOSED"}}},
10         {"SYN_RCVD",new Dictionary<string,string>(){{"APP_CLOSE","FIN_WAIT_1"},{"RCV_ACK","ESTABLISHED"}}},
11         {"ESTABLISHED",new Dictionary<string,string>(){{"APP_CLOSE","FIN_WAIT_1"},{"RCV_FIN","CLOSE_WAIT"}}},
12         {"CLOSE_WAIT",new Dictionary<string,string>(){{"APP_CLOSE","LAST_ACK"}}},
13         {"LAST_ACK",new Dictionary<string,string>(){{"RCV_ACK","CLOSED"}}},
14         {"FIN_WAIT_1",new Dictionary<string,string>(){{"RCV_FIN","CLOSING"},{"RCV_FIN_ACK","TIME_WAIT"},{"RCV_ACK","FIN_WAIT_2"}}},
15         {"FIN_WAIT_2",new Dictionary<string,string>(){{"RCV_FIN","TIME_WAIT"}}},
16         {"CLOSING",new Dictionary<string,string>(){{"RCV_ACK","TIME_WAIT"}}},
17         {"TIME_WAIT",new Dictionary<string,string>(){{"APP_TIMEOUT","CLOSED"}}}
18     };
19     // 初始化狀態為"CLOSED"
20     var state = "CLOSED";
21     // 遍歷輸入的字串陣列
22     foreach (var s in r){
23         // 如果當前狀態對應的字典包含當前輸入字串,則更新狀態為對應的新狀態
24         if (tome[state].ContainsKey(s)){state = tome[state][s];}
25         // 如果當前狀態對應的字典不包含當前輸入字串,則返回"ERROR"
26         else {return "ERROR";}
27     }
28     // 返回最終的狀態
29     return state;
30 }

這段程式碼是一個簡單的TCP/IP狀態機實現,它模擬了TCP連線的建立和關閉過程。這個狀態機演演算法是基於有限狀態機(Finite State Machine,FSM)的概念實現的。FSM 是一種數學模型,用於描述一些具有離散狀態的系統,這些系統在接收輸入時會發生狀態轉換。FSM 還可以用於模擬計算機程式、電路設計、自動控制系統等方面。

在這個狀態機演演算法中,我們使用了一種稱為「狀態轉換表」的資料結構來描述狀態之間的轉換關係。狀態轉換表是一個二維表格,其中每一行代表一個狀態,每一列代表一個輸入事件,每個單元格包含了從當前狀態接收到某個輸入事件後應該轉換到的下一個狀態。

下面是一個簡單的狀態轉換圖,展示了從 "CLOSED" 狀態開始,經過一系列輸入事件後可能到達的各個狀態:

              +------------+      RCV_SYN       +------------+
              |   CLOSED   |--------------------|  SYN_RCVD  |
              +------------+                    +------------+
                    | APP_ACTIVE_OPEN                 |
                    |                                 |
                    |                                 |
              RCV_SYN_ACK                       RCV_ACK
                    |                                 |
                    |                                 |
                    v                                 v
              +------------+                    +------------+
              |  SYN_SENT  |--------------------| ESTABLISHED|
              +------------+      RCV_FIN       +------------+
                    | RCV_SYN_ACK |         | RCV_FIN_ACK |
                    |             |         |             |
                    |             v         |             v
                    |       +------------+ |       +------------+
                    +-------| FIN_WAIT_1 | |-------| FIN_WAIT_2 |
                            +------------+         +------------+
                    RCV_FIN |         | RCV_FIN |         | RCV_FIN
                            |         v         |         v
                            |   +------------+ |   +------------+
                            +---|  CLOSING   | |---| TIME_WAIT |
                                +------------+     +------------+
                                      RCV_ACK           | APP_TIMEOUT
                                                        |
                                                        v
                                                  +------------+
                                                  |   CLOSED   |
                                                  +------------+

在這個狀態轉換圖中,每個圓圈代表一個狀態,每個箭頭代表一個輸入事件或狀態轉換。例如,從 "CLOSED" 狀態開始,如果接收到 "APP_ACTIVE_OPEN" 輸入事件,狀態將會轉換到 "SYN_SENT" 狀態。如果在 "SYN_SENT" 狀態接收到 "RCV_SYN_ACK" 輸入事件,狀態將會轉換到 "ESTABLISHED" 狀態。

TCP(Transmission Control Protocol)是網際網路中最重要的協定之一,它是由美國國防部高階研究計劃局(ARPA)在20世紀70年代末和80年代初開發的。TCP協定的設計者是Vinton Cerf和Bob Kahn等人,他們在設計TCP時面臨了許多挑戰,包括封包丟失、網路擁塞、資料傳輸的可靠性等問題。由於他們在TCP/IP網路協定方面作出的傑出貢獻,他們獲得了2004年的圖靈獎。

在TCP協定的設計過程中,狀態機起到了非常重要的作用。通過狀態機,TCP協定可以清晰地定義連線的不同狀態(如CLOSED、LISTEN、SYN_SENT等),以及在不同狀態下接收到不同事件時的狀態轉換規則。這種設計使得TCP協定能夠在複雜的網路環境下實現可靠的資料傳輸和連線管理。

使用測試用例 TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE" }),我們可以解釋演演算法1實現TCP/IP狀態機的狀態跳轉過程:

  1. 首先,定義了一個名為tome的字典,其中包含了每個狀態對應的可能事件以及狀態轉換規則。

  2. 初始化狀態為"CLOSED"。

  3. 接下來,遍歷輸入的字串陣列。對於每個輸入的字串,演演算法會檢查當前狀態對應的字典中是否包含該輸入字串。如果包含,則更新狀態為對應的新狀態;如果不包含,則返回"ERROR"。

  4. 根據給定的測試用例 TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE" }),演演算法將依次處理輸入的事件序列:

    • "APP_PASSIVE_OPEN":根據狀態"CLOSED"對應的字典,更新狀態為"LISTEN"。
    • "RCV_SYN":根據狀態"LISTEN"對應的字典,更新狀態為"SYN_RCVD"。
    • "RCV_ACK":根據狀態"SYN_RCVD"對應的字典,更新狀態為"ESTABLISHED"。
    • "APP_CLOSE":根據狀態"ESTABLISHED"對應的字典,更新狀態為"FIN_WAIT_1"。
  5. 最終返回狀態"FIN_WAIT_1"作為輸出。


 瞭解了狀態跳轉的基本原理後,可以給演演算法1加入委託事件,這樣的狀態機就有一定的實用價值了,範例程式碼:

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 public delegate void StateChangedEventHandler(string newState);
 5 
 6 public static string TraverseStates(string[] r, Dictionary<string, Dictionary<string, (string newState, Action eventAction)>> stateMap, StateChangedEventHandler stateChangedEvent)
 7 {
 8     var state = "CLOSED";
 9     foreach (var s in r)
10     {
11         if (stateMap.ContainsKey(state) && stateMap[state].ContainsKey(s))
12         {
13             state = stateMap[state][s].newState;
14             stateChangedEvent?.Invoke(state); // 觸發狀態改變事件
15             stateMap[state][s].eventAction?.Invoke(); // 呼叫下一個狀態委託的事件
16         }
17         else
18         {
19             return "ERROR";
20         }
21     }
22     return state;
23 }
24 
25 public static void Main()
26 {
27     var stateMap = new Dictionary<string, Dictionary<string, (string newState, Action eventAction)>>()
28     {
29         // 初始化狀態轉換規則及委託事件定義
30         {"CLOSED",new Dictionary<string, (string, Action)>(){{"APP_PASSIVE_OPEN",("LISTEN", null)},{"APP_ACTIVE_OPEN",("SYN_SENT", null)}}},
31         {"LISTEN",new Dictionary<string, (string, Action)>(){{"RCV_SYN",("SYN_RCVD", null)},{"APP_SEND",("SYN_SENT", null)},{"APP_CLOSE",("CLOSED", null)}}},
32         {"SYN_SENT",new Dictionary<string, (string, Action)>(){{"RCV_SYN",("SYN_RCVD", null)},{"RCV_SYN_ACK",("ESTABLISHED", null)},{"APP_CLOSE",("CLOSED", null)}}},
33         // 其他狀態的轉換規則...
34     };
35     
36     StateChangedEventHandler stateChangedEvent = (newState) => {
37         Console.WriteLine($"State changed to {newState}");
38     };
39 
40     string[] input = {"APP_ACTIVE_OPEN", "RCV_SYN_ACK", "APP_CLOSE"};
41     var finalState = TraverseStates(input, stateMap, stateChangedEvent);
42     Console.WriteLine($"Final state: {finalState}");
43 }

 演演算法實現2:

  1     public static string TraverseStates(string[] events)
  2     {
  3         //TraverseStates 方法接收一個字串陣列 events,表示 TCP 協定中的事件序列。首先建立一個 StateMachineBuilder 物件,並呼叫 SetInitialState 和 SetErrorState 方法設定初始狀態和錯誤狀態。然後通過呼叫 AddState 方法新增每個狀態,並使用 AddTransition 方法新增每個狀態的轉換規則。最後呼叫 Build 方法生成一個完整的狀態機物件。接著使用 foreach 迴圈遍歷事件序列,對每個事件呼叫 Process 方法進行狀態轉換,並更新當前狀態。最終返回最後一個狀態的名稱。
  4         var fsm = new StateMachineBuilder()
  5           .SetInitialState("CLOSED")
  6           .SetErrorState("ERROR")
  7           .AddState("ERROR")
  8               .Back()
  9           .AddState("CLOSED")
 10               .AddTransition("APP_PASSIVE_OPEN", "LISTEN")
 11               .AddTransition("APP_ACTIVE_OPEN", "SYN_SENT")
 12               .Back()
 13           .AddState("LISTEN")
 14               .AddTransition("RCV_SYN", "SYN_RCVD")
 15               .AddTransition("APP_SEND", "SYN_SENT")
 16               .AddTransition("APP_CLOSE", "CLOSED")
 17               .Back()
 18           .AddState("SYN_RCVD")
 19               .AddTransition("APP_CLOSE", "FIN_WAIT_1")
 20               .AddTransition("RCV_ACK", "ESTABLISHED")
 21               .Back()
 22           .AddState("SYN_SENT")
 23               .AddTransition("RCV_SYN", "SYN_RCVD")
 24               .AddTransition("RCV_SYN_ACK", "ESTABLISHED")
 25               .AddTransition("APP_CLOSE", "CLOSED")
 26               .Back()
 27           .AddState("ESTABLISHED")
 28               .AddTransition("APP_CLOSE", "FIN_WAIT_1")
 29               .AddTransition("RCV_FIN", "CLOSE_WAIT")
 30               .Back()
 31           .AddState("FIN_WAIT_1")
 32               .AddTransition("RCV_FIN", "CLOSING")
 33               .AddTransition("RCV_FIN_ACK", "TIME_WAIT")
 34               .AddTransition("RCV_ACK", "FIN_WAIT_2")
 35               .Back()
 36           .AddState("CLOSING")
 37               .AddTransition("RCV_ACK", "TIME_WAIT")
 38               .Back()
 39           .AddState("FIN_WAIT_2")
 40               .AddTransition("RCV_FIN", "TIME_WAIT")
 41               .Back()
 42           .AddState("TIME_WAIT")
 43               .AddTransition("APP_TIMEOUT", "CLOSED")
 44               .Back()
 45           .AddState("CLOSE_WAIT")
 46               .AddTransition("APP_CLOSE", "LAST_ACK")
 47               .Back()
 48           .AddState("LAST_ACK")
 49               .AddTransition("RCV_ACK", "CLOSED")
 50               .Back()
 51           .Build();
 52       
 53         var nextState = string.Empty;
 54       
 55         foreach (var @event in events) 
 56         {
 57             nextState = fsm.Process(@event);
 58         }
 59       
 60         return nextState;
 61     }
 62   
 63     //在 StateMachineBuilder 類中,使用 Dictionary 儲存所有狀態的 StateBuilder 物件,用於後續構建狀態轉換規則。通過呼叫 AddState 方法新增每個狀態,並使用 SetInitialState 和 SetErrorState 方法設定初始狀態和錯誤狀態。最後通過呼叫 Build 方法將所有狀態轉換規則組裝成一個完整的狀態機物件。
 64     class StateMachineBuilder
 65     {
 66         private readonly Dictionary<string, StateBuilder> states;
 67         private string initialState = null;
 68         private string errorState = null;
 69       
 70         public StateMachineBuilder()
 71         {
 72             states = new Dictionary<string, StateBuilder>();
 73         }
 74       
 75         public StateBuilder AddState(string name)
 76         {
 77             var state = new StateBuilder(this, name);
 78             this.states.Add(name, state);
 79             return state;
 80         }
 81       
 82         public StateMachineBuilder SetInitialState(string state)
 83         {
 84             this.initialState = state;
 85             return this;
 86         }
 87       
 88         public StateMachineBuilder SetErrorState(string state)
 89         {
 90             this.errorState = state;
 91             return this;
 92         }
 93       
 94         public StateMachine Build() 
 95         {
 96             var states = this.states.Values.Select(state => state.Build()).ToDictionary(state => state.Name);
 97             var errorState = states[this.errorState];
 98             var initialState = states[this.initialState];
 99             return new StateMachine(states, initialState, errorState);
100         }
101     }
102   
103     //在 StateBuilder 類中,使用 Dictionary 儲存當前狀態的所有轉換規則。通過呼叫 AddTransition 方法新增每個轉換規則。通過呼叫 Back 方法返回上一級的 StateMachineBuilder 物件,並通過呼叫 Build 方法生成當前狀態的 State 物件。
104     class StateBuilder 
105     {
106         private readonly Dictionary<string, string> transitions;
107         private readonly StateMachineBuilder parent;
108         private readonly string name;
109       
110         public StateBuilder(StateMachineBuilder parent, string name)
111         {
112             this.parent = parent;
113             this.name = name;
114             this.transitions = new Dictionary<string, string>();
115         }
116       
117         public StateBuilder AddTransition(string @event, string nextState)
118         {
119             this.transitions.Add(@event, nextState);
120             return this;
121         }
122       
123         public StateMachineBuilder Back() => this.parent;
124         public State Build() => new State(this.name, this.transitions);
125     }
126   
127     //在 StateMachine 類中,使用 Dictionary 儲存所有狀態的 State 物件,用於後續狀態轉換。通過呼叫 Process 方法進行狀態轉換,並更新當前狀態。如果當前狀態的轉換規則中不包含當前事件,則返回錯誤狀態的名稱。
128     class StateMachine
129     {
130         private readonly Dictionary<string, State> states;
131         private State currentState;
132         private State errorState;
133       
134         public StateMachine(Dictionary<string, State> states, State initialState, State errorState)
135         {
136             this.states = states;
137             this.currentState = initialState;
138             this.errorState = errorState;
139         }
140       
141         public string Process(string @event)
142         {
143             this.currentState = this.states[this.currentState.Process(@event)];
144             return this.currentState.Name;
145         }
146     }
147   
148     //在 State 類中,儲存當前狀態的名稱和所有轉換規則。通過呼叫 Process 方法處理當前事件,並返回下一個狀態的名稱。如果當前狀態的轉換規則中不包含當前事件,則返回錯誤狀態的名稱。
149     class State 
150     {
151         private readonly Dictionary<string, string> transitions;
152         public string Name { get; private set; }
153       
154         public State(string name, Dictionary<string, string> transitions) 
155         {
156             this.Name = name;
157             this.transitions = transitions;
158         }
159       
160         public string Process(string @event)
161         {
162             if (!this.transitions.TryGetValue(@event, out var nextState)) 
163             {
164                 return "ERROR";
165             }
166           
167             return nextState;
168         }
169     }

演演算法2實現了一個有限狀態機,用於模擬 TCP 協定的狀態轉換過程。具體實現方式是使用了建造者模式,將狀態機的構建過程分解為多個步驟,每個步驟對應一個建造者物件,最終通過呼叫 Build() 方法將所有建造者物件組裝成一個完整的狀態機物件。

在這個有限狀態機中,狀態被抽象為一個名為 State 的類,包含狀態名稱和狀態轉換規則(即從當前狀態處理某個事件後,轉換到下一個狀態的規則)。狀態機被抽象為一個名為 StateMachine 的類,包含當前狀態、錯誤狀態和狀態轉換方法。建造者物件被抽象為一個名為 StateMachineBuilder 的類,包含多個 StateBuilder 物件,用於構建每個狀態的轉換規則。


演演算法1(直接實現)的優點:

  1. 直接實現狀態機的構建和狀態轉換邏輯,程式碼相對簡單直接,適用於簡單的狀態機。
  2. 不需要引入額外的設計模式,程式碼結構相對簡單,易於理解和上手。

演演算法1的缺點:

  1. 對於複雜的狀態機,直接實現可能會使得程式碼結構變得混亂,不易維護和擴充套件。
  2. 可能會將狀態機的構建邏輯和狀態轉換邏輯耦合在一起,不利於程式碼的分層和模組化。

演演算法2(建造者模式)的優點:

  1. 可以將狀態機的構建過程分解為多個步驟,每個步驟對應一個建造者物件,使得構建過程更加靈活和可控。
  2. 通過建造者模式將狀態機的構建過程與表示分離,使得程式碼結構更加清晰,易於維護和擴充套件。
  3. 可以在建造者物件中進行狀態轉換規則的設定,使得狀態機的構建過程更加模組化和可複用。

演演算法2的缺點:

  1. 建造者模式引入了多個建造者物件,增加了程式碼的複雜度,可能會使得程式碼量增加。
  2. 對於簡單的狀態機,引入建造者模式可能會顯得過於繁瑣和複雜。

以實際應用場景說明:假設我們有一個複雜的自動駕駛系統,其中包含了多種車輛狀態(行駛中、停車中、充電中、故障中等),以及各種複雜的狀態轉換規則(例如在行駛中狀態下,如果檢測到障礙物需要切換到停車狀態,然後根據充電狀態和電量情況決定是否前往充電站等)。在這種情況下,如果用演演算法1直接實現狀態機的邏輯,可能會將所有狀態和狀態轉換規則耦合在一起,導致程式碼結構變得混亂,不易維護和擴充套件。這種場景適合使用演演算法2。


 測試用例:

 1 using NUnit.Framework;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 
 6 public class SolutionTest
 7 {
 8 
 9     [Test]
10     public void FixedTests()
11     {
12         Assert.AreEqual("FIN_WAIT_1", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE" }));
13         Assert.AreEqual("ESTABLISHED", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK" }));
14         Assert.AreEqual("SYN_RCVD", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN" }));
15         Assert.AreEqual("LISTEN", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN" }));
16         Assert.AreEqual("CLOSED", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "APP_CLOSE" }));
17         Assert.AreEqual("TIME_WAIT", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN", "APP_CLOSE", "RCV_FIN", "RCV_ACK" }));
18         Assert.AreEqual("CLOSED", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN", "APP_CLOSE", "RCV_FIN", "RCV_ACK", "APP_TIMEOUT" }));
19         Assert.AreEqual("ERROR", TCP.TraverseStates(new[] { "RCV_SYN", "RCV_ACK", "APP_CLOSE" }));
20         Assert.AreEqual("FIN_WAIT_2", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN", "APP_CLOSE", "RCV_ACK" }));
21         Assert.AreEqual("CLOSE_WAIT", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN_ACK", "RCV_FIN" }));
22         Assert.AreEqual("LAST_ACK", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN_ACK", "RCV_FIN", "APP_CLOSE" }));
23         Assert.AreEqual("SYN_SENT", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN" }));
24         Assert.AreEqual("CLOSED", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "APP_CLOSE" }));
25         Assert.AreEqual("FIN_WAIT_1", TCP.TraverseStates(new[] { "APP_ACTIVE_OPEN", "RCV_SYN_ACK", "APP_CLOSE" }));
26         Assert.AreEqual("ERROR", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_PASSIVE_OPEN" }));
27         Assert.AreEqual("TIME_WAIT", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE", "RCV_FIN_ACK", "APP_TIMEOUT", "APP_ACTIVE_OPEN", "RCV_SYN", "APP_CLOSE", "RCV_FIN", "RCV_ACK" }));
28         Assert.AreEqual("ERROR", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE", "RCV_SYN" }));
29         Assert.AreEqual("ERROR", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "APP_CLOSE", "RCV_SYN" }));
30         Assert.AreEqual("FIN_WAIT_1", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE" }));
31         Assert.AreEqual("CLOSING", TCP.TraverseStates(new[] { "APP_PASSIVE_OPEN", "RCV_SYN", "RCV_ACK", "APP_CLOSE", "RCV_FIN" }));
32     }
33 
34     //------------------------------------------
35 
36     private static string[] ALL_CMDS = "APP_PASSIVE_OPEN, APP_ACTIVE_OPEN, APP_SEND, APP_CLOSE, APP_TIMEOUT, RCV_SYN, RCV_ACK, RCV_SYN_ACK, RCV_FIN, RCV_FIN_ACK".Split(", ");
37 
38     private static string START = "CLOSED", ERROR = "ERROR";
39 
40     private static Dictionary<string, Dictionary<string, string>> STATES = new Dictionary<string, Dictionary<string, string>>()
41     {
42         ["CLOSED"] = new Dictionary<string, string> { ["APP_PASSIVE_OPEN"] = "LISTEN", ["APP_ACTIVE_OPEN"] = "SYN_SENT" },
43         ["LISTEN"] = new Dictionary<string, string> { ["RCV_SYN"] = "SYN_RCVD", ["APP_SEND"] = "SYN_SENT", ["APP_CLOSE"] = "CLOSED" },
44         ["SYN_RCVD"] = new Dictionary<string, string> { ["APP_CLOSE"] = "FIN_WAIT_1", ["RCV_ACK"] = "ESTABLISHED" },
45         ["SYN_SENT"] = new Dictionary<string, string> { ["RCV_SYN"] = "SYN_RCVD", ["RCV_SYN_ACK"] = "ESTABLISHED", ["APP_CLOSE"] = "CLOSED" },
46         ["ESTABLISHED"] = new Dictionary<string, string> { ["APP_CLOSE"] = "FIN_WAIT_1", ["RCV_FIN"] = "CLOSE_WAIT" },
47         ["FIN_WAIT_1"] = new Dictionary<string, string> { ["RCV_FIN"] = "CLOSING", ["RCV_FIN_ACK"] = "TIME_WAIT", ["RCV_ACK"] = "FIN_WAIT_2" },
48         ["CLOSING"] = new Dictionary<string, string> { ["RCV_ACK"] = "TIME_WAIT" },
49         ["FIN_WAIT_2"] = new Dictionary<string, string> { ["RCV_FIN"] = "TIME_WAIT" },
50         ["TIME_WAIT"] = new Dictionary<string, string> { ["APP_TIMEOUT"] = "CLOSED" },
51         ["CLOSE_WAIT"] = new Dictionary<string, string> { ["APP_CLOSE"] = "LAST_ACK" },
52         ["LAST_ACK"] = new Dictionary<string, string> { ["RCV_ACK"] = "CLOSED" }
53     };
54 
55     private Random rnd = new Random();
56     private int Rand(int a, int b) => a + rnd.Next(b - a);
57     private int Rand(int a) => rnd.Next(a);
58     private string Choice(string[] stuff) => stuff[Rand(stuff.Length)];
59     private string[] GetCmdsOf(string state) => STATES[state].Keys.ToArray();
60 
61     [Test]
62     public void RandomTests()
63     {
64         DoTests(100, 2, 5, 79);
65         DoTests(100, 10, 51, 97);
66     }
67 
68     private void DoTests(int nTests, int nCmdMin, int nCmdMax, int endProba)
69     {
70         for (int n = 0; n < nTests; n++)
71         {
72             string state = START, last = null;
73             var cmds = new List<string>();
74             var x = Rand(nCmdMin, nCmdMax);
75 
76             for (int i = 0; i < x; i++)
77             {
78                 var endIt = Rand(0, 100) > endProba;
79                 last = Choice(endIt ? ALL_CMDS : GetCmdsOf(state));
80                 state = STATES[state].GetValueOrDefault(last, ERROR);
81                 cmds.Add(last);
82                 if (endIt) break;
83             }
84             Assert.AreEqual(state, TCP.TraverseStates(cmds.ToArray()));
85         }
86     }
87 }

 RandomTests()是一個隨機測試演演算法,它使用了亂數生成器來執行一系列測試。在程式碼中,定義了一些輔助方法來生成亂數和隨機選擇陣列中的元素。在RandomTests方法中,呼叫了DoTests方法來執行一定數量的測試。DoTests方法中,使用迴圈生成亂數量的命令,並根據狀態轉移表來更新狀態,並將命令新增到列表中。最後,使用斷言來驗證狀態轉移的正確性。整體來說,這段程式碼利用隨機性來執行測試,以發現潛在的問題和錯誤。

隨機測試的概念在軟體工程和電腦科學領域中已經存在了很長時間。早期的軟體測試主要集中在手動編寫測試用例和靜態分析上,但是隨著軟體規模的增長和複雜性的提高,傳統的測試方法變得不夠高效。

隨機測試的概念是在20世紀70年代提出的,最初用於測試編譯器和直譯器。隨機測試的理念是利用隨機性來生成測試用例,以期望發現一些邊緣情況和異常行為,從而提高軟體的質量和穩定性。隨機測試方法在軟體測試領域中得到了廣泛的應用,特別是在開源軟體和大型系統中。

隨機測試的方法和技術也在不斷髮展和演進,包括基於模型的隨機測試、符號執行、模糊測試等。隨機測試已經成為了軟體測試領域中重要的一種測試方法,對於發現軟體中的潛在問題和錯誤具有重要作用。