iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Software Development

程式設計沒有告訴你的事:30 天破解每一個 Why系列 第 9

為什麼一般程式執行完就結束,但 Web Server 可以一直等 Request?

  • 分享至 

  • xImage
  •  

昨天,我終於找到了一個程式真正開始執行的位置。

以前我一直把 Main() 理解成「程式的起點」,但透過 ILDasm 實際觀察後,我才知道更精確的概念其實是 Entry Point,即使使用 Top-level statements,沒有親手寫下 Main(),Compiler 仍然會替程式產生對應的進入點。

所以現在我知道:

啟動程式
    ↓
Entry Point
    ↓
開始執行

但這又讓我產生了下一個問題。

假設我的程式只有:

Console.WriteLine("程式開始");
Console.WriteLine("程式結束");

它從 Entry Point 開始,兩行執行完之後,很快就結束了。

可是 Web Server 明明不是這樣。

我們開啟網站時,Server 不可能處理完一個 Request 就直接關閉。

它必須繼續存在,等待下一個 Request。

所以今天我想追的問題變成:

為什麼一般程式執行完就結束,但 Web Server 卻可以一直活著?


為什麼一般程式執行完就結束,但 Web Server 可以一直等 Request?

以前看到 Server 一直運作,我其實沒有特別思考過它為什麼不會結束。

只是很自然地認為:

Server 本來就是一直開著的。

但如果把它當成一個普通程式來看,事情就變得很奇怪。

普通程式:

開始

執行程式碼

執行完畢

結束

Web Server:

開始

等待 Request

收到 Request

處理 Request

回傳 Response

等待下一個 Request

等...

兩者都是程式。

為什麼一個會走到終點,另一個卻可以一直留在那裡?

我決定先不碰真正的 Web Server,而是從最簡單的 Console 程式開始實驗。


一個普通程式為什麼會結束?

Console.WriteLine("程式開始");

Console.WriteLine("程式結束");

執行後,結果很單純:

程式開始
程式結束

然後程式就結束了。

如果把昨天學到的 Entry Point 加進來,可以把流程想成:

Entry Point

「程式開始」

「程式結束」

沒有其他程式碼要執行

結束

這讓我先得到第一個很基本、但重要的觀察:

程式不會因為「它是程式」就永遠存在。當執行流程完成,而且沒有其他需要讓程序繼續存在的工作時,程序就可以結束。

https://ithelp.ithome.com.tw/upload/images/20260810/20183395uQ7lBQBYZI.png

那如果我故意讓執行流程永遠走不到終點呢?

原來「程式不結束」其實很簡單?

做到這裡,我一度覺得,那 Server 不就是弄一個無限迴圈,讓程式永遠不要跑完就好了嗎?

這個實驗證明了一件事,只要執行流程一直沒有完成,程式就不會因為「後面的程式碼執行完」而自然走到終點。

但馬上又出現另一個問題我們剛才寫的:

while (true)
{
}

其實什麼有意義的事情都沒做。

它只是在不停地:

判斷 true

回去

判斷 true

回去

判斷 true

等……

程式確實「活著」,但 CPU 也在不斷執行這個迴圈。

這種方式稱為 Busy Waiting(忙碌等待/空轉) 的典型形式。

所以「程式沒有結束」不代表「程式正在有效率地等待」,這兩件事情不能畫上等號。


可不可以不要一直空轉,而是真的等?

所以第三次實驗,我把程式改成:

Console.WriteLine("程式開始");

Console.WriteLine("正在等待輸入...");

Console.ReadLine();

Console.WriteLine("收到輸入,程式準備結束");

https://ithelp.ithome.com.tw/upload/images/20260810/201833953igr4SdKMo.png

https://ithelp.ithome.com.tw/upload/images/20260810/20183395TMl3GTa7Wd.png

執行後,我先看到:

程式開始:正在等待輸入...

然後程式停在那裡,這次跟剛才一樣程式沒有結束。

但兩者的狀況卻完全不同。

while (true) 是:

沒有事情

一直跑

一直檢查

一直跑

而 Console.ReadLine() 則是在等待輸入,當沒有輸入時,這個呼叫不會立刻完成,因此後面的程式碼暫時不會繼續執行。

直到我在 Console 輸入文字並按下 Enter:

輸入資料

Console.ReadLine() 完成

程式繼續往下

「收到輸入,程式準備結束」

結束

這是今天三個實驗裡,我覺得最重要的一次。

一開始,我看到兩種程式都沒有結束:

while (true)
{
}

以及:

Console.ReadLine();

表面上好像沒有差別。

Console 視窗都還在。

程式都沒有走到最後。

但實際上它們代表的是兩種不同的狀態。


這時候我也第一次碰到一個很重要的概念:

Blocking(阻塞)

以今天的:

Console.ReadLine();

來說,程式執行到這裡時,需要等待輸入完成。

在這個呼叫完成以前,目前這條同步執行流程不能直接越過它執行下一行:

Console.WriteLine("收到輸入,程式準備結束");

可以先用一個簡化的方式理解:

執行到 ReadLine()
       ↓
   等待輸入
       │
       │ 尚未輸入
       │
       └────── 等待
       
       │ 收到輸入
       ▼
 ReadLine() 完成
       ↓
 執行下一行

所以 Blocking 並不是「程式壞掉了。」

而是目前的執行流程正在等待某個操作完成,因此暫時不能繼續往下。

這個概念之後碰到檔案、資料庫、網路 I/O 時,都還會再次出現。

現在終於可以把今天的實驗拉回 Mini Web Framework。

真正的 Web Server 當然不是寫:

Console.ReadLine();

來等待 Request。

今天的 Console.ReadLine() 只是為了讓我用最簡單的方法親手觀察:

程式可以等待外部事件發生,而不是執行完幾行程式碼就立刻結束。

Console 程式今天等待的是:

鍵盤輸入

而 Web Server 關心的則是:

網路上的連線與資料

可以先用非常簡化的方式比較:

Console 程式

等待鍵盤輸入

使用者輸入

繼續處理

Web Server 則像:

等待網路事件

Client 傳來資料

Server 處理

回應

繼續等待

這裡的關鍵不是 Console.ReadLine()。


做到這裡,我還不能直接說:

「Web Server 就是用 Blocking 等 Request。」

因為真正的 Server 還會遇到更多問題。

例如:

假設第一個 Client 的 Request 還沒處理完:

Client A

Server 正在處理

這時 Client B 又來了:

Client B

???

難道 B 必須一直等 A 完全處理完嗎?

如果同時有:

100 個 Request

甚至:

10,000 個 Request

又該怎麼辦?

這就會慢慢帶出之後非常重要的:

Thread
I/O
Blocking
Non-blocking
Asynchronous
async / await

但這些都不是今天要一次解決的問題。

今天只需要先建立最重要的第一層:

Server 能長時間存在,不是因為它單純「永遠執行同一段空迴圈」,而是它的生命週期中包含等待、接收與處理外部事件的過程。


到目前為止,我的 Mini Web Framework 還只是一個 Console 專案。

但如果最後真的要讓它成為 Web Framework,就不能只是:

Console.WriteLine("Hello");

然後:

執行

結束

它未來必須能做到:

MiniWebFramework 啟動

準備接收連線

等待

收到 Client 的資料

處理

產生 Response

回傳

繼續等待

今天雖然還沒有真正接收到 HTTP Request,但我至少已經理解:

為什麼 Server 類型的程式不能像普通 Console 範例一樣,執行完幾行就直接離開。

而下一步,就是搞懂:

它到底在等什麼?


今天我最大的發現其實不是:

while (true) 可以讓程式不要結束。

而是:

「程式活著」和「CPU 一直執行我的程式碼」並不是同一件事情。

以前看到 Server 一直開著,我很容易想像成:

它是不是一直在跑?

但今天透過兩個非常簡單的實驗,我第一次看到:

一直空轉

和:

等待事件

是完全不同的事情。

這個差異也讓我開始理解,為什麼 Web Server 的核心不只是:

「想辦法不要讓程式關掉。」

真正的問題其實是:

如何讓程式持續存在,同時有效率地等待、接收並處理外部事件?

這才是我們接下來真正要解決的問題。


現在,我已經知道 Web Server 必須「等待」。

但這句話馬上又產生了一個更具體的問題:

Console 程式可以用:

Console.ReadLine();

等待鍵盤輸入,那 Web Server 呢?

它總不可能等待鍵盤,Browser 傳來的 Request 究竟是透過什麼東西進入我們的程式?

換句話說:

如果 Web Server 要等待網路上的資料,它到底是在等什麼?

這就會把我們帶到一個真正屬於網路世界的概念:

Socket。

而當我們開始碰到 Socket,我們的 Mini Web Framework 也會第一次真正從普通 Console 程式開始往,

可以接收網路連線的程式前進。


上一篇
我沒有寫 Main(),程式到底從哪裡開始?
下一篇
Web Server 到底在等什麼?Socket 是什麼?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言