工控網(wǎng)中基于Linux的嵌入式HTTP服務器設計
關鍵詞:嵌入式HTTP服務器 Linux 控制網(wǎng)絡
引言
控制網(wǎng)絡一般指以控制“事物對象”為特征的計算機網(wǎng)絡系統(tǒng),簡稱Infranet(infrastructure network),它處在企業(yè)網(wǎng)(Intranet)的底層,構成了整個企業(yè)網(wǎng)的基礎。近些年來,在控制網(wǎng)絡中采用了現(xiàn)場總線(Field Bus)和工業(yè)以太網(wǎng)(Ethernet)兩種技術。出現(xiàn)這種情況主要有兩個原因:第一,目前存在多種現(xiàn)場總線標準,不同的標準采用完全不同的通信協(xié)議,也就是說現(xiàn)場總線的開放性不夠;第二,以太網(wǎng)雖然能夠解決開放性的問題,并具備成本低廉、技術成熟等優(yōu)點,但由于以太網(wǎng)最初是被設計用于以資源共享為目的的計算機局域網(wǎng),因此在實時性和可靠性上暫時還不能完全滿足工業(yè)控制的要求。事實上,目前一個較大規(guī)模的控制網(wǎng)絡往往綜合采用了這兩種技術。在現(xiàn)場層,由于強調(diào)實時性、可靠性和安全性,常采用現(xiàn)場總線技術;在監(jiān)控管理層,考慮到采用不同標準的控制網(wǎng)段之間的集成以及與高層企業(yè)信息網(wǎng)絡的集成,一般采用以太網(wǎng)技術。現(xiàn)場層的現(xiàn)場總線控制系統(tǒng)FCS(Field Control System)或其它設備級輕質(zhì)網(wǎng)絡通過網(wǎng)關或嵌入式HTTP服務器與高層以太網(wǎng)相連。這樣,不僅采用不同協(xié)議的控制網(wǎng)段能夠?qū)崿F(xiàn)互聯(lián),而且各個控制網(wǎng)段能方便地與高層企業(yè)信息網(wǎng)互通,從而最終實現(xiàn)企業(yè)網(wǎng)的管控一體化和對現(xiàn)場設備的Internet遠程監(jiān)控。通過以上分析可以看出,控制網(wǎng)絡中的網(wǎng)關或嵌入式HTTP服務器起著連接現(xiàn)場層和監(jiān)迭管理層的作用,因此它是整個控制網(wǎng)絡的關鍵設備。
網(wǎng)關或嵌入式HTTP服務器與傳統(tǒng)的嵌入式系統(tǒng)相比,有一些不同的特性。傳統(tǒng)的嵌入式系統(tǒng)是面向應用、有一些不同的特性。傳統(tǒng)的嵌入式系統(tǒng)是面向應用、面向產(chǎn)品的,強調(diào)成本和高效設計系統(tǒng),因此本質(zhì)上不具備通用性和可移植性。網(wǎng)關或嵌入式HTTP服務器由于處在現(xiàn)場層和監(jiān)控管理層的中間,因此它與具體應用和產(chǎn)品是一種弱耦合的關系。同時,技術發(fā)展的趨勢是:硬件成本越來越低,功能越來越強,越來越多的芯片和板卡具備“平臺”的特點,適用于多種應用場合。嵌入式實時操作系統(tǒng)(Embedded Real Time Operationg System)的發(fā)展更是為嵌入式軟件提供了一個通用的軟件平臺。綜上所述,在網(wǎng)關或嵌入式HTTP服務器設計中,考慮通過選用適合的硬件和嵌入式實時操作系統(tǒng),使整個系統(tǒng)具備相當?shù)耐ㄓ眯院涂梢浦残浴τ谶B接不同的設備級輕質(zhì)網(wǎng)絡或不同的應用,只需要通過更換硬件模塊和對代碼作最小的修改即可實現(xiàn)。
為了實現(xiàn)設計目標,嵌入式HTTP服務器一般應采用功能較強的能用PC、工業(yè)PC、或高檔MPU作為硬件平臺,嵌入式實時操作系統(tǒng)作為軟件平臺進行平發(fā)。硬件平臺應具備以太網(wǎng)口和一個或多個通信模塊,比如RS232、RS485、CAN通信卡等。嵌入式實時操作系統(tǒng)實現(xiàn)了TCP/IP等網(wǎng)絡協(xié)議,并提供實時任務、進程管理、內(nèi)存管理、文件系統(tǒng)、API等功能。
Linux操作系統(tǒng)是一種多進程,多用戶的通用操作。由于它具備免費、源碼公開、內(nèi)核可裁減、支持多線程、網(wǎng)絡功能強大、設計精巧、性能穩(wěn)定的特點,因此近年它也被廣泛用到嵌入式系統(tǒng)的設計中。一個應用于嵌入式系統(tǒng)的Linux經(jīng)過裁減和重新編譯后只包括進程管理、內(nèi)存管理、文件系統(tǒng)、若干個驅(qū)動程序和實用的函數(shù)等。
下面以本人參與的轉(zhuǎn)子秤控制系統(tǒng)為例來說明一個基于Linux的嵌入式HTTP服務器的結(jié)構。轉(zhuǎn)子秤是水泥工業(yè)中的關鍵計量喂料設備,一條大型的生產(chǎn)線需要許多臺轉(zhuǎn)子秤,對轉(zhuǎn)子秤的控制涉及到重量、轉(zhuǎn)速、溫度、一氧化碳含量等若干個參量。由于現(xiàn)場環(huán)境的高噪聲、高粉塵、高電磁干擾,無法在現(xiàn)場配備鍵盤、顯示器、觸摸屏等人機交互設備,無法在現(xiàn)場實現(xiàn)對設備的監(jiān)控和維護。同時,一條生產(chǎn)線有多臺轉(zhuǎn)子秤,為每臺轉(zhuǎn)子秤配備人機交互設備也是不經(jīng)濟的。為此,考慮為整個系統(tǒng)設計一個嵌入式HTTP服務器,各轉(zhuǎn)子秤控制器與嵌入式HTTP服務器用CAN總線相連。通過嵌入式HTTP服務器實現(xiàn)對整個系統(tǒng)的在線監(jiān)控和遠程監(jiān)控。在嵌入式HTTP服務器的設計中,選用研祥公司PC104總線的486X嵌入式CPU卡作為硬件平臺,該板卡是具有128MB的在板ROM、CF卡接口和以太網(wǎng)接口等。選擇該板卡的原因是PC104總線的功能擴展模塊非常豐富,通過選擇不同的模塊很容易就支持多種總線。軟件平臺方面,選用Linux2.0內(nèi)核并對它作適當裁減。整個嵌入式HTTP服務器的結(jié)構簡圖如圖1所示。
2 基于Linux的嵌入式HTTP服務器的設計
工控領域的嵌入式HTTP服務器應該具備如下基本功能。
①實時數(shù)據(jù)發(fā)布。實時數(shù)據(jù)主要包括系統(tǒng)運行過程中設備的各種狀態(tài)信息。嵌入式HTTP服務器將實時數(shù)據(jù)以網(wǎng)頁形式發(fā)布到Internet上,且動態(tài)實時刷新??蛻艨梢酝ㄟ^瀏覽器訪問這些實時信息。
②參數(shù)設置。參數(shù)包括運行參數(shù)和設備狀態(tài)參數(shù),如各種初始值、常數(shù)等。嵌入式HTTP服務器接收到客戶提交的參數(shù)設備請求后,執(zhí)行參數(shù)寫入操作。
③遠程實時控制。遠程實時控制允許遠程用戶在線地控制系統(tǒng)中的相應執(zhí)行機構,比如電機、電磁閥等。嵌入式HTTP服務器接收到遠方客戶提交的控制操作請求后,將下發(fā)控制命令驅(qū)動監(jiān)控系統(tǒng)中相應的執(zhí)行機構。
④訪問級別設置和權限認證。只有權限不低于要求訪問級別的客戶,經(jīng)嵌入式HTTP服務器認證后,方可進行其權限范圍內(nèi)的監(jiān)控操作。
3 主要實現(xiàn)技術
3.1 超文本傳輸協(xié)議
HTTP協(xié)議是一個面向事務、無狀態(tài)的應用層協(xié)議。在傳輸層,HTTP協(xié)議使用請求(request)/響應(response)模型。一次簡單的HTTP事務包括以下過程。首先,客戶(瀏覽器)發(fā)起和建立一條到服務器的TCP連接。然后,客戶發(fā)送一個HTTP請求到服務器,請求包含方法、URI、協(xié)議版本和一個類MIME報文。服務器解析HTTP請求后,給出相應的HTTP響應,響應包括協(xié)議版本、狀態(tài)碼、解釋狀態(tài)碼的簡短短語和一個類MIME報文。最后,釋放TCP連接。Linux操作系統(tǒng)為用戶提供了稱為BSD Socket的網(wǎng)絡編程接口。利用其中的TCP套接口函數(shù),可以非常方便地實現(xiàn)HTTP協(xié)議。
HTTP1.0為每一次HTTP請求/響應建立一條新的TCP連接,由于建立一條TCP連接要經(jīng)歷3次握手,因此效率不高。HTTP1.1提出了可持續(xù)性連接的概念。HTTP1.1只建立一次TCP連接,而重復地使用它傳送一條素的請求/響應消息,減少了額外開銷。在嵌入式HTTP服務器中,一般使用HTTP1.1協(xié)議。HTTP1.1協(xié)議的細節(jié)請參考RFC2616。
3.2 通用網(wǎng)關接口CGI
參數(shù)設置和遠程控制功能都是通過CGI(通用網(wǎng)關接口)程序和表單實現(xiàn)的。CGI使用HTML表單向Web服務器發(fā)送信息?;菊Z法如下:
FORM METHOD=get/post ACTION=URL>/FORM>
其中,METHOD屬性指定將數(shù)據(jù)傳送到Web服務器的方法。輸入方法有兩種:GET和POST。ACTION屬性定義要對表單數(shù)據(jù)進行處理的CGI腳本的URL。
CGI的工作流程是首先由瀏覽器將用戶輸入的數(shù)據(jù)傳遞給Web服務器,Web服務器根據(jù)接收到的數(shù)據(jù)設置環(huán)境變量并啟動CGI腳本,CGI腳本從環(huán)境變量中讀取所需要的數(shù)據(jù)并進行相應處理,最后使用STDOUT輸出HTML形式的結(jié)果文件,經(jīng)Web服務器送回瀏覽器,最終顯示給用戶。傳統(tǒng)的CGI程序與服務器代碼分開,是一個符號CGI標準的可執(zhí)行文件,并儲存在CF卡等存儲設備上,一般用腳本語言編寫??紤]到嵌入式HTTP服務器要求速度快,功能和代碼都盡可能精簡的特點,可以把原先由可執(zhí)行文件完成的功能用C函數(shù)實現(xiàn),放在服務器代碼內(nèi)部,并直接從HTTP請求報文接收數(shù)據(jù)。與傳統(tǒng)CGI程序相比,這種方法具備如下特點:
*不需要標準輸入,CGI函數(shù)可以直接獲取到瀏覽器送來的信息;
*不需要標準輸出,CGI函數(shù)可以直接將數(shù)據(jù)送回給瀏覽器;
*不需要環(huán)境變量,CGI和Web服務器在同一程序中實現(xiàn),不需要環(huán)境變量來交換信息。
3.3 自定義標記
要在網(wǎng)頁中顯示工控系統(tǒng)中大量的實時數(shù)據(jù),常規(guī)方法是將HTML代碼直接集成到程序代碼中,或者反之將C程序代碼集成到HTML標記語言中。這兩種方法均要求開發(fā)人員對HTML標記語言的語法細節(jié)非常熟悉。網(wǎng)頁或程序結(jié)構的單方面調(diào)整都將導致整個系統(tǒng)全盤修改,系統(tǒng)不具備靈活性與可擴展性。HTML的精髓在于該語言的“標記”性,各種不同標記的具體含義是由服務器和瀏覽器進行解析。因此,當現(xiàn)有標記不能滿足新的應用需求時,可以自行定義新的標記,只需服務器將自定義標記解析為標準標記,然后傳送給瀏覽器即可。在本項目中,主要的實時數(shù)據(jù)轉(zhuǎn)速、重量、一氧化碳含量等狀態(tài)信息,可以定義相應的標記。服務器中解析相應標記的函數(shù)同樣用C語言來實現(xiàn)。運行時,當客戶端發(fā)出查看某實時網(wǎng)頁的請求后,嵌入式HTTP服務器將相應的網(wǎng)頁文件從電子盤加載到內(nèi)存進行逐項解析。當辨識出自定義標記后,就調(diào)用相應的函數(shù)。該函數(shù)返回該標記對應的當前值,并置換HTML文件流中的自定義標記。最后,嵌入式HTTP服務器將解析結(jié)果發(fā)送給客戶端。實時網(wǎng)頁的設計與相應的HTTP服務器處理程序得以分離,處于一種弱耦合關聯(lián)狀態(tài)。這樣,網(wǎng)頁界面的調(diào)整不會影響HTTP服務器的程序設計,HTTP服務器程序的修改也與網(wǎng)頁界面設計無關,整個嵌入式HTTP服務器具備靈活性和可擴展性。
3.4 多線程
最初的進程定義包含程序、資源及其執(zhí)行三部分,其中程序通常指代碼,資源通常包括 內(nèi)存資源、I/O資源、信號處理等,而程序的執(zhí)行指執(zhí)行上下文,這一部分后來發(fā)展為線程。在線程的概念出現(xiàn)以前,為了減小進程切換的開銷,操作系統(tǒng)設計者逐漸修改正進程的概念,允許將進程所占有的資源從其主體剝離出來,允許某些進程共離享一部分資源,例如文件、信號、數(shù)據(jù)內(nèi)存、甚至代碼,這就是輕質(zhì)進程的概念。Linux內(nèi)核的2.0.x版本就已經(jīng)實現(xiàn)了輕質(zhì)進程。應用程序可以通過一個統(tǒng)一的clone()系統(tǒng)調(diào)用接口,用不同的參數(shù)指定創(chuàng)建輕質(zhì)進程還是普通進程。在內(nèi)核中,clone()調(diào)用經(jīng)過參數(shù)傳遞和解釋后會調(diào)用do_fork(),這個核內(nèi)函數(shù)同時也是fork、vfork()系統(tǒng)調(diào)用的最終實現(xiàn)。在do_fork()中,不同的flone_flags將導致不同的行為。在LinuxThreads中,使用(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND)參數(shù)調(diào)用clone()創(chuàng)建“線程”,表示共享內(nèi)存、共享文件系統(tǒng)、共享文件描述符表,以及共享信號處理方式。Linux操作系統(tǒng)下,已經(jīng)實現(xiàn)基于輕質(zhì)進程的符號POSIX1003.C標準的線程庫LinuxThreads。
在傳統(tǒng)的Unix服務器程序設計中,為了使服務器具備并發(fā)處理連接的能力,通常采用父進程處理連接,并調(diào)用fork()創(chuàng)建子進程來處理用戶請求的方法。這種方法的缺點是進程創(chuàng)建慢,耗費資源,進程切換開銷大,進程之間通信比較困難等,不適用對資源、速度有要求的嵌入式系統(tǒng)。因此,在嵌入式HTTP服務器的開發(fā)中使用線程的方法。利用LinuxThreads提供的pthread_create()等函數(shù)派生出線程,也即輕質(zhì)進程來處理多個HTTP請求。
4 工作流程和代碼設計
4.1工作流程
嵌入式HTTP服務器程序開始運行時,主進程首先創(chuàng)建一個接口,并和主機地址綁定到一起,隨后置為被動監(jiān)聽狀態(tài),等待客戶端連接請求的到來。分別用函數(shù)socket()創(chuàng)建一個接口,bind()綁定地址,listen()監(jiān)聽,accept()接收來完成。當建立一個TCP連接后,函數(shù)accept()返回一個新的套接口描述符,主進程就創(chuàng)建一個新的子線程(輕質(zhì)進程)處理這個新的連接。
子線程用于處理每具體的HTTP請求。子線程首先解析用戶的HTTP請求。當用戶請求一個網(wǎng)頁時,子線程查找文件系統(tǒng)。如果該網(wǎng)頁文件存在,且通過權限認證,就把它從CF卡讀入內(nèi)存并掃描,發(fā)現(xiàn)有自定義標記則調(diào)用相應函數(shù)進行處理,最后把結(jié)果返回給瀏覽器;否則給一個簡單的出錯消息。當用戶是上傳數(shù)據(jù)時,子線程調(diào)用相應函數(shù)讀取數(shù)據(jù)進行處理,并返回處理結(jié)果給瀏覽器。
4.2 代碼設計
在嵌入式HTTP服務器的代碼設計中,考慮到代碼的移植性和擴展性,利用C語言實現(xiàn)了面向?qū)ο箫L格的代碼結(jié)構。代碼主要由兩上數(shù)據(jù)結(jié)構request_inf和response_inf以及其上一組操作函數(shù)組成。
結(jié)構request_inf和response_inf分別用來保存HTTP請求報文和響應報文的所有信息。在結(jié)構定義時,應根據(jù)具體應用特點設計結(jié)構包含的成分。
嵌入式HTTP服務器的函數(shù)包括通用函數(shù)、CGI函數(shù)和自定義標記處理函數(shù)等,其中通用函數(shù)是一些與HTTP1.1協(xié)議有關的函數(shù)。
(1)通用函數(shù)
*void prase_request_line(char *,struct *request_inf)
該函數(shù)用來解析HTTP請求報文的請求行(Request_Line),并把相應信息存放在結(jié)構request_inf中。其中,對請求行中URI部分的解析包括兩種情況。如果用戶請求一個網(wǎng)頁,則獲取文件路徑、文件類型;如果用戶要求上傳數(shù)據(jù),則把數(shù)據(jù)放在一個字符數(shù)組中。然后將文件路徑和類型,或者指向該數(shù)組的指針、方法、版本號信息都放入結(jié)構request_inf中。
*void prease_general_header(char*,struct*request_inf)
該函數(shù)用來解析HTTP請求報文的調(diào)用首部(General_Header)。之所以把此函數(shù)與函數(shù)prase_request_line()分開,是考慮到程序的修植性和擴展性。請求行和通用首部是請求報文中的不同部分,不不同的場合下,要求解析的信息可能存在差導師。同時,這樣也能使程序結(jié)構更清楚。比如,本項目要從通用首部解析字段Keep_Alive。該字段指明一個最長的時間或最大請求數(shù)目,在此范圍內(nèi)可以保持TCP連接不被釋放(即前文提到的HTTP1.1的持續(xù)連接特性,persistent connection)。
*void prase_request_header(char*,struct*request_inf)
void prase_entity_header(char*,struct*request_inf)
HTTP請求報文的請求頭部用來說明瀏覽器的一些信息,實體頭部則用來說明請求報文中可能存在的實體主體信息。本項目實際上并不需要使用這兩個函數(shù)來獲取相關信息,但考慮到程序的擴展性和移植性,此處仍然把它列出來,它們是兩個空函數(shù)。
*send_status_line(int fd,struct *response_inf)
此函數(shù)用來產(chǎn)生一個HTTP響應報文的狀態(tài)行(Status_line)。狀態(tài)行包括三部分內(nèi)容,即HTTP版本、狀態(tài)碼以及解釋狀態(tài)碼的簡單短語。這些信息預先放在結(jié)構response_inf中。
*send_general_header(int fd,struct*response_inf)
send_response_header(int fd,struct*response_inf)
send_entity_header(int fd,struct*response_inf)
這三個函數(shù)分別用來產(chǎn)生HTTP響應報文的通用首部、響應首部(Response_header)和實體首部。嵌入式HTTP服務器是一個瘦服務器,功能非常簡單。因此HTTP響應報文的通用首部、響應首部和實體首部中的可選字段許多是不需要的,還有許多是固定不變的,例如Last_modified和Content_type字段。Last_modified字段指出資源上次被修改的時間并由接收方解釋。如果接收方已有此資源的拷貝,但此拷貝比Last-Modified域所指定的要舊,那該拷貝就是過期的。由于網(wǎng)頁文件中含有自定義標記,具有實時性,所以此字段根本沒有含有Content_type字段指出實體的媒體類型,本項目中的嵌入式HTTP服務器被設計成只支持HTML類型,因此該字段的內(nèi)容總是Content_type=text/html。有關服務器和資源的所有標題域信息都被放入結(jié)構response_inf中。
*send_white_line(int fd)
此函數(shù)用于實體首部和實體之間傳送一個空白行。
*void send_entity_body(int fd,char *buff_file)
此函數(shù)用來傳遞實體主體,實體主體實際上是一個處理后的網(wǎng)頁文件,它被放在指針buff_file指向的緩沖區(qū)內(nèi)。
*void zero_request_inf(struct*request_inf)
void zero_response_inf(struct*response_inf)
這兩個函數(shù)用于結(jié)構request_inf和response_inf清零。
*void get_file(struct*request_inf,struct * response_inf,char*buff_file,void*,void*)
該函數(shù)用來處理用戶HTTL請求。首先,函數(shù)會檢查request_inf結(jié)構,判斷用戶是請求一個網(wǎng)頁文件還是上傳數(shù)據(jù)。當用戶請求網(wǎng)頁文件時,函數(shù)將根據(jù)request_inf結(jié)構中的文件路徑信息,在文件系統(tǒng)錄找此文件。如果文件不存在或不具備權限,則函數(shù)將狀態(tài)碼和解釋短語寫入結(jié)構response_inf,然后直接返回;否則讀取文件并調(diào)用自定義標記處理函數(shù),對標記進行處理,處理過的網(wǎng)頁文件被放入buff_file指向的緩沖區(qū)內(nèi),并把狀態(tài)碼、解釋短路和與實體有關的一些信息寫入結(jié)構response_inf。當用戶上傳數(shù)據(jù)時,該函數(shù)調(diào)用CGI處理函數(shù)向CAN總線網(wǎng)絡發(fā)送幀,然后將狀態(tài)碼和解釋短路寫入結(jié)構response_inf。利用狀態(tài)碼和解釋短語只能用“200,OK”或“500,Internal Server Error”等,簡單反映執(zhí)行情況。用戶要獲取詳細信息,可待一段合適的時間后請求網(wǎng)頁文件。函數(shù)中兩個void指針分別指向自定義標記處理函數(shù)和CGI處理函數(shù),或者對應的函數(shù)指針數(shù)組。
(2)自定義標記處理函數(shù)和CGI處理函數(shù)
自定義標記處理函數(shù)用于對自定義的處理,每一類自定義標記對對應一種自定義標記處理函數(shù),同一類自定義標記的不同數(shù)據(jù)點利用參數(shù)來區(qū)分,比如轉(zhuǎn)子秤1的重量標記可以用weight1來表示。所有的自定義標記處理函數(shù)被放在一起,構成一個函數(shù)指針數(shù)組。自定義標記處理函數(shù)向CAN總線網(wǎng)絡發(fā)送遠程幀和接收數(shù)據(jù)幀,獲取相應的狀態(tài)信息。CGI總線網(wǎng)絡發(fā)送遠程幀和接收數(shù)據(jù)幀,獲取相應的狀態(tài)信息。CGI處理函數(shù)用變量名來區(qū)分,同一類變量對應一種CGI處理函數(shù)。與自定義標記處理函數(shù)類似,所有的CGI處理函數(shù)也被放在一起,構成一個函數(shù)指針數(shù)組。由于自定義標記函數(shù)和CGI處理函數(shù)類型眾多,這里就不列舉了。
結(jié)語
我們設計的嵌入式HTTP服務器具備良好的通用性和可移植性。通過更換或增加PC104通信模塊,該服務器能夠支持不同的現(xiàn)場總線,或同時連接幾種不同的設備級輕質(zhì)網(wǎng)絡。同時在服務器代碼設計中,用C語言實現(xiàn)了面向?qū)ο箫L格的代碼結(jié)構。這樣,如果要求服務器端具備更多的特性,只需要簡單修改結(jié)構request_inf、response_inf、操作函數(shù)和網(wǎng)頁文件即可達到目的。這種設計思路不僅適用于嵌入式HTTP服務器,隨著硬件技術尤其是嵌入式操作系統(tǒng)技術的發(fā)展,它同樣能夠應用到其它嵌入式產(chǎn)品的開發(fā)中。
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評論