檢視原始碼 應用程式

建議您同時閱讀此章節以及 Kernel 中的 appapplication

應用程式概念

在建立程式碼以實作特定功能後,您可能會考慮將其轉換為一個應用程式 — 一個可以作為單元啟動和停止,並可在其他系統中重複使用的組件。

建立應用程式的步驟如下

  • 建立一個應用程式回呼模組,描述如何啟動和停止該應用程式。

  • 建立一個應用程式規範並將其放置在一個應用程式資源檔案中。 除此之外,此檔案指定該應用程式由哪些模組組成以及回呼模組的名稱。

如果您使用 systools,Erlang/OTP 用於封裝程式碼的工具(請參閱發行版本),則每個應用程式的程式碼都會放置在一個單獨的目錄中,並遵循預定義的目錄結構

應用程式回呼模組

如何啟動和停止應用程式的程式碼,包括其監督樹,由兩個回呼函數描述

start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
stop(State)
  • start/2 在啟動應用程式時被呼叫,並透過啟動頂層監督者來建立監督樹。 它預期會傳回頂層監督者的 PID 和一個可選的詞彙 State,其預設值為 []。 此詞彙會原封不動地傳遞給 stop/1
  • StartType 通常是原子 normal。 只有在接管或故障轉移的情況下才會有其他值;請參閱分散式應用程式
  • StartArgs應用程式資源檔案中的鍵 mod 定義。
  • stop/1 在應用程式停止之後被呼叫,並執行任何必要的清理工作。應用程式的實際停止,也就是關閉監督樹,會按照啟動和停止應用程式中的描述自動處理。

封裝來自 監督者行為 的監督樹的應用程式回呼模組範例

-module(ch_app).
-behaviour(application).

-export([start/2, stop/1]).

start(_Type, _Args) ->
    ch_sup:start_link().

stop(_State) ->
    ok.

無法啟動或停止的程式庫應用程式不需要任何應用程式回呼模組。

應用程式資源檔案

若要定義應用程式,會建立一個應用程式規範,該規範會放入一個應用程式資源檔案,或簡稱為 .app 檔案

{application, Application, [Opt1,...,OptN]}.
  • Application,一個原子,是應用程式的名稱。檔案名稱必須為 Application.app
  • 每個 Opt 都是一個元組 {Key,Value},它定義了應用程式的某個屬性。 所有鍵都是可選的。 對於任何省略的鍵,都會使用預設值。

程式庫應用程式 libapp 的最小 .app 檔案內容如下所示

{application, libapp, []}.

監督樹應用程式(如 ch_app)的最小 .app 檔案 ch_app.app 的內容如下所示

{application, ch_app,
 [{mod, {ch_app,[]}}]}.

mod 定義了應用程式的回呼模組和啟動引數,在此例中分別為 ch_app[]。 這表示當要啟動應用程式時會呼叫以下內容

ch_app:start(normal, [])

當應用程式停止時會呼叫以下內容

ch_app:stop([])

當使用 systools(Erlang/OTP 用於封裝程式碼的工具,請參閱發行版本)時,也必須指定鍵 descriptionvsnmodulesregisteredapplications

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}}
 ]}.
  • description - 簡短描述,字串。 預設值為 ""
  • vsn - 版本號碼,字串。 預設值為 ""
  • modules - 此應用程式引入的所有模組。 systools 在產生啟動腳本和 tar 檔案時使用此列表。 一個模組只能包含在一個應用程式中。 預設值為 []
  • registered - 應用程式中所有已註冊處理程序的名稱。 systools 使用此列表來偵測應用程式之間的名稱衝突。 預設值為 []
  • applications - 必須在此應用程式啟動之前啟動的所有應用程式。 systools 使用此列表來產生正確的啟動腳本。 預設值為 []。 請注意,所有應用程式都至少依賴於 Kernel 和 STDLIB。

注意

有關應用程式資源檔案的語法和內容的詳細資訊,請參閱 Kernel 中的 app

目錄結構

當使用 systools 封裝程式碼時,每個應用程式的程式碼都會放置在一個單獨的目錄 lib/Application-Vsn 中,其中 Vsn 是版本號碼。

即使未使用 systools,了解這一點也很有用,因為 Erlang/OTP 是根據 OTP 原則封裝的,因此具有特定的目錄結構。 如果存在一個應用程式的多個版本,程式碼伺服器(請參閱 Kernel 中的模組 code)會自動使用具有最高版本號碼的目錄中的程式碼。

開發環境的目錄結構指南

只要發行版本的目錄結構符合下面的描述,任何開發目錄結構都足夠,但建議在開發環境中也使用相同的目錄結構。版本號碼應該從應用程式目錄名稱中省略,因為這是發行步驟的產物。

某些子目錄是必要的。 某些子目錄是可選的,這表示只有在應用程式本身需要時才應使用它。 最後,某些子目錄是建議的,這表示建議使用它,並在此處描述的方式使用它。 例如,建議應用程式中同時存在文件和測試,才能將其視為適當的 OTP 應用程式。

    ─ ${application}
      ├── doc
      │   ├── internal
      │   ├── examples
      │   └── src
      ├── include
      ├── priv
      ├── src
      │   └── ${application}.app.src
      └── test
  • src - 必要。 包含 Erlang 原始程式碼、.app 檔案的原始程式碼和應用程式本身使用的內部包含檔案。 src 內的其他子目錄可以用作命名空間來組織原始程式碼檔案。 這些目錄的深度永遠不應超過一層。
  • priv - 可選。 用於應用程式特定檔案。
  • include - 可選。 用於其他應用程式必須可存取的公開包含檔案。
  • doc - 建議。 任何原始文件都應放置在此處的子目錄中。
  • doc/internal - 建議。 任何描述此應用程式實作細節的文件,不打算發布的文件,都應放置在此處。
  • doc/examples - 建議。 關於如何使用此應用程式的範例原始程式碼應放置在此處。 建議從此目錄將範例來源到公開文件。
  • doc/src - 建議。 所有文件的原始程式碼檔案,例如 Markdown、AsciiDoc 或 XML 檔案,都應放置在此處。
  • test - 建議。 所有與測試相關的檔案,例如測試套件和測試規範,都應放置在此處。

開發環境中可能需要其他目錄。 如果使用 Erlang 以外的語言的原始程式碼,例如 NIF 的 C 程式碼,則該程式碼應放置在單獨的目錄中。 依照慣例,建議以語言名稱作為此類目錄的前綴,例如 C 的 c_src、Java 的 java_src 或 Go 的 go_src。 帶有 _src 字尾的目錄表示它是應用程式的一部分和編譯步驟。 最終的組建產物應以 priv/libpriv/bin 目錄為目標。

priv 目錄包含應用程式在執行期間需要的資源。 可執行檔應位於 priv/bin 中,而動態連結的程式庫應位於 priv/lib 中。 其他資源可以自由地存在於 priv 目錄中,但建議它們以結構化的方式存在。

從其他產生 Erlang 程式碼的語言(例如 ASN.1 或 Mibs)產生的原始程式碼應放置在與原始語言名稱相同的目錄中,位於頂層或 src 中,例如 asn1mibs。 組建產物應放置在其各自的語言目錄中,例如 Erlang 程式碼的 src 或 Java 程式碼的 java_src

在開發環境中,發行版本的 .app 檔案可以存在於 ebin 目錄中,但建議它是組建步驟的產物。 依照慣例,使用位於 src 目錄中的 .app.src。 此檔案幾乎與 .app 檔案相同,但在組建步驟期間會替換某些欄位,例如應用程式版本。

目錄名稱不應大寫。

建議省略空的目錄。

發行系統的目錄結構

發行的應用程式必須遵循特定的結構。

    ─ ${application}-${version}
      ├── bin
      ├── doc
      │   ├── html
      │   ├── man[1-9]
      │   ├── pdf
      │   ├── internal
      │   └── examples
      ├── ebin
      │   └── ${application}.app
      ├── include
      ├── priv
      │   ├── lib
      │   └── bin
      └── src
  • src - 可選。 包含 Erlang 原始程式碼和應用程式本身使用的內部包含檔案。
  • ebin - 必要。 包含 Erlang 物件程式碼,即 .beam 檔案。 .app 檔案也必須放置在此處。
  • priv - 可選。 用於應用程式特定檔案。 code:priv_dir/1 用於存取此目錄。
  • priv/lib - 建議。 應用程式使用的任何共享物件檔案,例如 NIF 或連結的驅動程式,都應放置在此處。
  • priv/bin - 建議。 應用程式使用的任何可執行檔,例如埠程式,都應放置在此處。
  • include - 可選。 用於其他應用程式必須可存取的公開包含檔案。
  • bin - 可選。 應用程式的任何可執行產品,例如 escript 或 shell 腳本,都應放置在此處。
  • doc - 可選。 任何發行的文件都應放置在此處的子目錄中。

src 目錄對於發布以進行除錯可能很有用,但這並非必要。include 目錄只有在應用程式具有公開的包含檔時才應該發布。

建議省略空的目錄。

應用程式控制器

當 Erlang 執行時系統啟動時,會啟動一些進程作為 Kernel 應用程式的一部分。其中一個進程是應用程式控制器進程,註冊為 application_controller

所有應用程式的操作都由應用程式控制器協調。使用 Kernel 中的 application 模組來載入、卸載、啟動和停止應用程式。

載入和卸載應用程式

在應用程式可以啟動之前,它必須先被載入。應用程式控制器會讀取並儲存來自 .app 檔案的資訊。

1> application:load(ch_app).
ok
2> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]

已停止或從未啟動的應用程式可以被卸載。關於應用程式的資訊會從應用程式控制器的內部資料庫中刪除。

3> application:unload(ch_app).
ok
4> application:loaded_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

注意

載入/卸載應用程式不會載入/卸載應用程式所使用的程式碼。程式碼載入由程式碼伺服器以通常的方式處理。

啟動和停止應用程式

應用程式透過呼叫以下方式啟動

5> application:start(ch_app).
ok
6> application:which_applications().
[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
 {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
 {ch_app,"Channel allocator","1"}]

如果應用程式尚未載入,應用程式控制器會先使用 application:load/1 載入它。它會檢查 applications 鍵的值,以確保在這個應用程式之前要啟動的所有應用程式都在執行中。

接著,應用程式控制器會為應用程式建立一個應用程式主控

應用程式主控會將自己建立為應用程式中所有進程的 群組領導者,並將 I/O 轉發給先前的群組領導者。

注意

應用程式主控作為群組領導者的目的是為了更容易追蹤屬於應用程式的哪些進程。這是為了支援 application:get_application/0application:get_env/1 函數,以及在停止應用程式時,確保屬於應用程式的所有進程都被終止。

應用程式主控透過在由 .app 檔案中的 mod 鍵定義的模組中,呼叫應用程式回呼函式 start/2 來啟動應用程式。

應用程式會停止,但不會卸載,透過呼叫以下方式

7> application:stop(ch_app).
ok

應用程式主控會透過告知頂層監管者關閉來停止應用程式。頂層監管者會告知其所有子進程關閉,依此類推;整個樹狀結構會以相反的啟動順序終止。接著,應用程式主控會呼叫由 mod 鍵定義的模組中的應用程式回呼函式 stop/1

配置應用程式

可以使用配置參數來配置應用程式。這些是 .app 檔案中由鍵 env 指定的 {Par,Val} 元組列表

{application, ch_app,
 [{description, "Channel allocator"},
  {vsn, "1"},
  {modules, [ch_app, ch_sup, ch3]},
  {registered, [ch3]},
  {applications, [kernel, stdlib, sasl]},
  {mod, {ch_app,[]}},
  {env, [{file, "/usr/local/log"}]}
 ]}.

Par 必須是原子。Val 可以是任何項。應用程式可以透過呼叫 application:get_env(App, Par) 或許多類似的函數來檢索配置參數的值。有關更多資訊,請參閱 Kernel 中的 application 模組。

範例

% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"/usr/local/log"}

.app 檔案中的值可以被系統配置檔中的值覆蓋。這是一個包含相關應用程式的配置參數的檔案

[{Application1, [{Par11,Val11},...]},
 ...,
 {ApplicationN, [{ParN1,ValN1},...]}].

系統配置檔應命名為 Name.config,並且 Erlang 應使用命令行參數 -config Name 啟動。有關詳細資訊,請參閱 Kernel 中的 config

範例

建立一個內容如下的 test.config 檔案

[{ch_app, [{file, "testlog"}]}].

file 的值會覆蓋 .app 檔案中定義的 file 的值

% erl -config test
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}

如果使用 發布處理,則只能使用一個系統配置檔,且該檔案應命名為 sys.config

.app 檔案中的值和系統配置檔中的值可以直接從命令行覆蓋

% erl -ApplName Par1 Val1 ... ParN ValN

範例

% erl -ch_app file '"testlog"'
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]

Eshell V5.2.3.6  (abort with ^G)
1> application:start(ch_app).
ok
2> application:get_env(ch_app, file).
{ok,"testlog"}

應用程式啟動類型

啟動應用程式時會定義啟動類型

application:start(Application, Type)

application:start(Application) 與呼叫 application:start(Application, temporary) 相同。類型也可以是 permanenttransient

  • 如果永久應用程式終止,所有其他應用程式和執行時系統也會終止。
  • 如果暫時應用程式以 normal 原因終止,則會回報此情況,但不會終止其他應用程式。如果暫時應用程式異常終止,也就是以任何其他原因而非 normal 終止,則所有其他應用程式和執行時系統也會終止。
  • 如果臨時應用程式終止,則會回報此情況,但不會終止其他應用程式。

應用程式始終可以透過呼叫 application:stop/1 來顯式停止。無論模式如何,都不會影響其他應用程式。

暫時模式實際上用途不大,因為當監管樹終止時,原因會設定為 shutdown,而不是 normal