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