天天看點

遠端終端服務的簡單實作

本文将介紹如何建構一個最簡單的 web 遠端終端服務程式。

首先明确幾個相關概念:

終端

終端是一種字元型輸入輸出裝置,通過它使用者才能與計算機進行 io。在 linux 系統中,終端裝置檔案一般位于 /dev/ 下。

每打開一個終端,就會産生一個新的 tty 裝置檔案。使用指令 <code>tty</code> 可以檢視目前使用的終端裝置。

終端大緻分為:

串行端口終端( /dev/ttysx )。是使用計算機串行端口連接配接的終端,串行端口所對應的裝置名稱是/dev/ttys0、/dev/ttys1 等等。

控制台終端( /dev/ttyn, /dev/console )。通常在 linux 系統中,把計算機顯示器稱為控制台終端,與之相連的裝置檔案有:tty0, tty1, tty2 等。

控制終端( /dev/tty )。并不面向裝置,而是面向程序組的,在 linux 系統中,一個控制終端控制一個會話。

通常情況下,使用者通過終端輸入的指令經由shell解釋和執行,進而與系統核心進行互動。

系統啟動以後,在指定的波特率上打開串行端口終端(ttys0), 并将 stdin、 stdout、stderr 都綁定到該裝置上,然後啟動 login 程式等待使用者完成登陸 。若使用者登陸成功,則啟動一個 shell 程式為使用者服務,這樣使用者就擁有一個 shell 終端了。

僞終端

對于遠端網絡使用者來說,上節描述的 terminal 登入過程并不适用,網絡使用者既不能遠端使用串行端口裝置,也不能遠端控制顯示器裝置。是以需要建立一個虛拟的終端裝置為其服務 —— 僞終端。

僞終端,顧名思義,不是真正的終端,不能操作某個實體裝置。它是虛拟的終端驅動裝置,用來模拟串行終端的行為。

當使用 ssh、telnet 等程式連接配接到某台伺服器上時進行操作時,底層使用的就是僞終端技術。

僞終端是成對的邏輯終端裝置,分為“主裝置”(master)和“從裝置”(slave),例如/dev/ptyp3和/dev/ttyp3。

其中,“從裝置”提供了與真正終端無異的接口,可以與系統進行 io,規範終端行輸入。; 而“主裝置”與管道檔案類似,可以進行讀寫操作。往“主裝置”寫入的資料會傳輸到“從裝置”,而“從裝置”從系統擷取到的資料也會同樣的傳輸到“主裝置”。是以,也可以說,僞終端是一個雙向管道。

上面已經介紹過,想要與系統進行互動,除了有終端裝置,還需要 shell 程式。兩者結合才能完成使用者的指令。

是以,一個遠端終端服務程式由兩個部分構成:僞終端和 shell 程序。通常建構如下:

1 建立僞終端裝置。

2 fork 建立子程序,并将該子程序的标準輸入、輸出和錯誤輸出均 dup 為僞終端的"從裝置"。

3 在子程序中 exec 執行 /bin/bash 指令,啟動 shell 程序。由于上一步的操作,該子程序(也就是 shell 程序)的 stdin、stdout 和 stderr 已與僞終端進行了綁定。如此,shell 子程序的輸出、輸出、錯誤輸出均是通過僞終端的“從裝置”進行的。

經過上述操作,可以說這個子程序就是一個“終端程序“了:既能夠完成終端的輸入輸出操作,又能解釋執行使用者輸入與系統核心互動。

由于僞終端“雙向管道”的特性:對僞終端“主裝置”的寫操作,将傳輸到“從裝置”,也就是傳輸給”終端程序“;而”終端程序“執行指令後的輸出,将通過“從裝置”傳輸傳回至“主裝置”。如此一來,對 ”終端程序“ 的 io 操作完全可以通過操作僞終端的“主裝置”來完成。

對“主裝置”進行讀寫操作,就等同于在對一個終端 shell 進行操作。是以,如果在父程序中将該僞終端“主裝置”與網絡 socket 綁定,就能夠實作遠端終端操作了。(當然也可以将該“主裝置”與其他檔案描述符綁定,例如與另一程序通信的管道 fd 綁定等等,這些就取決于功能需求了)

資料傳輸可見下圖:

遠端終端服務的簡單實作

下面給出實作一個 remote terminal 服務的關鍵代碼。

主幹架構

代碼邏輯與上一節所描述的實作流程一緻。

建立僞終端

下面給出建立僞終端裝置所需的最簡單的代碼。當然,還可以添加更複雜的代碼來實作更多終端設定,例如屏蔽回顯等等。

子程序退出處理邏輯

子程序就是 shell 程序。在 shell 中輸入 <code>exit</code> 将會退出該程序,為了保證主程序的正常退出,這裡在捕獲到子程序的退出信号後,直接退出。

資料處理

這裡給出的隻是最簡單的示例代碼,同步且阻塞的讀寫。可以看到,在主幹代碼中,是先從 master echo 資料到 socket的。這是因為 shell 程式啟動後,會立即有資料輸出到 stdout,也就是 master 了。

例如下圖中的輸出: <code>sh-3.2$</code>

遠端終端服務的簡單實作

下面代碼的實作是同步阻塞的讀寫,建議使用更高效的方式,例如 io 複用等。

1 終端預設是具有回顯功能的,且終端是字元裝置

remote terminal 在使用者展示層需要格外注意,因為從 socket 寫入到 master 的資料,socket 還會從 master 中讀到。

是以 remote termial 最簡單省事的實作是 在顯示層捕獲使用者輸入的每一個字元,并立即通過網絡傳輸該單個字元 。這種方式,保留了 terminal 最原始的功能,并不用處理回顯等設定。(當然你也可以采用行資料網絡傳輸的方式,隻是要 care more ^.^)

注: linux 系統中有 <code>stty</code> 指令,用于檢視和更改終端行設定。<code>stty -echo</code> 指令會關閉回顯,通常用于輸入密碼等場景。當然,也有相關的接口來實作屏蔽回顯的功能。

2 終端操作通常是 io 密集的,尤其是上述的單字元傳輸方式

上述代碼中 echodata 的實作(同步阻塞 io),最好改成 io 複用的方式。可以使用select、poll、epoll 等架構, 監聽 master fd 和 socket fd,提高 io 效率。

3 開源元件