袁明磊,付賢政
(安徽國(guó)防科技職業(yè)學(xué)院信息工程系,安徽 六安237011)
當(dāng)前我校程序類課程的教學(xué)訓(xùn)練與考核方式仍然比較單一,主要采取人工評(píng)閱紙質(zhì)源代碼的模式。隨著計(jì)算機(jī)技術(shù)的發(fā)展,傳統(tǒng)的源代碼評(píng)測(cè)方式越來越顯出耗時(shí)、耗力、人為因素大等缺點(diǎn)。因此建立一套適合我校的源代碼在線評(píng)測(cè)系統(tǒng)成為迫切的需求[1]。
源代碼在線評(píng)測(cè)系統(tǒng)可以作為高職學(xué)校程序設(shè)計(jì)類課程的輔助教學(xué)平臺(tái)。源代碼在線評(píng)測(cè)系統(tǒng)本身也是一個(gè)程序競(jìng)賽平臺(tái),該平臺(tái)可以完成程序競(jìng)賽的實(shí)時(shí)評(píng)測(cè)。源代碼在線評(píng)測(cè)系統(tǒng)還可以作為學(xué)生課余時(shí)間鍛煉程序設(shè)計(jì)能力的一個(gè)平臺(tái),為學(xué)生提供一個(gè)提高程序設(shè)計(jì)能力的平臺(tái)。源代碼在線評(píng)測(cè)系統(tǒng)能根據(jù)程序運(yùn)行計(jì)算出程序執(zhí)行過程中所用的內(nèi)存和時(shí)間。時(shí)間越短內(nèi)存越少,說明程序設(shè)計(jì)越優(yōu)秀。
源代碼在線評(píng)測(cè)系統(tǒng)分為Judge服務(wù)器(判題內(nèi)核)、Web服務(wù)器和MySQL數(shù)據(jù)庫(kù)服務(wù)器三部分。Judge服務(wù)可以監(jiān)視數(shù)據(jù)庫(kù)服務(wù)器中的待評(píng)測(cè)任務(wù)隊(duì)列;Web服務(wù)器為用戶提供了操作源代碼在線評(píng)測(cè)系統(tǒng)的人機(jī)交互界面;Judge服務(wù)器和Web服務(wù)器共用MySQL數(shù)據(jù)庫(kù),MySQL數(shù)據(jù)庫(kù)存儲(chǔ)待評(píng)測(cè)任務(wù)隊(duì)列和評(píng)測(cè)結(jié)果等信息。
源代碼在線評(píng)測(cè)系統(tǒng)的工作過程如下:
(1)用戶通過Web服務(wù)器提供的接口將待評(píng)測(cè)的源代碼提交,之后 Web服務(wù)器將源代碼加入MySQL數(shù)據(jù)庫(kù);
(2)Judge服務(wù)器定期讀取 MySQL數(shù)據(jù)庫(kù)中的待評(píng)測(cè)源代碼,讀取后就自動(dòng)鎖定該源代碼,用來防止多個(gè)Judge服務(wù)器對(duì)源代碼的重復(fù)讀??;
(3)Judge服務(wù)器評(píng)測(cè)源代碼并將評(píng)測(cè)結(jié)果存儲(chǔ)到MySQL數(shù)據(jù)庫(kù)內(nèi);
(4)Web服務(wù)器從MySQL數(shù)據(jù)庫(kù)中讀取并且顯示源代碼的評(píng)測(cè)結(jié)果。
源代碼在線評(píng)測(cè)系統(tǒng)的服務(wù)器結(jié)構(gòu)如圖1所示,其中MySQL服務(wù)器、Web服務(wù)器、Judge服務(wù)器既可以由同一臺(tái)服務(wù)器承擔(dān),也可以分別由不同的服務(wù)器承擔(dān)。
圖1 源代碼在線評(píng)測(cè)系統(tǒng)服務(wù)器結(jié)構(gòu)
源代碼在線評(píng)測(cè)系統(tǒng)主要由:用戶子系統(tǒng)、題目子系統(tǒng)、競(jìng)賽子系統(tǒng)、評(píng)測(cè)子系統(tǒng)4個(gè)模塊組成。其中評(píng)測(cè)子系統(tǒng)部署在Judge服務(wù)器內(nèi),是系統(tǒng)工作的核心。用戶子系統(tǒng)、題目子系統(tǒng)、競(jìng)賽子系統(tǒng)的數(shù)據(jù)部分部署在MySQL數(shù)據(jù)庫(kù)服務(wù)器內(nèi),顯示和邏輯控制部署在Web服務(wù)器中[2]。
系統(tǒng)總體結(jié)構(gòu)如圖2所示。
圖2 系統(tǒng)總體結(jié)構(gòu)
源代碼在線評(píng)測(cè)系統(tǒng)中的主要實(shí)體有:用戶(user)、管理員(administrator)、游客(guest)、題目(problem)、競(jìng) 賽 (contest)、測(cè) 試 代 碼 (source_code)、解題狀態(tài)(solution)、編譯錯(cuò)誤(compileinfo)和運(yùn)行時(shí)錯(cuò)誤(runtimeinfo)。為了更清楚地表示系統(tǒng)的原理,將評(píng)測(cè)服務(wù)也作為實(shí)體來看待。各個(gè)實(shí)體之間的關(guān)系如圖3所示。
圖3 源代碼在線評(píng)測(cè)系統(tǒng)E-R圖
評(píng)測(cè)子系統(tǒng)的主要工作由myoj_client實(shí)現(xiàn)。myoj_client工作流程如圖4所示。
工作過程如下:
(1)讀取配置文件“./init/judge.conf”,從配置文件中獲取評(píng)測(cè)子系統(tǒng)全局變量的值;
(2)利用 MySQL提供的接口函數(shù),實(shí)現(xiàn)對(duì)MySQL數(shù)據(jù)庫(kù)的連接;
(3)利用 MySQL提供的接口函數(shù),獲取MySQL數(shù)據(jù)庫(kù)中待評(píng)測(cè)源代碼的信息;
(4)將待評(píng)測(cè)的源代碼,存儲(chǔ)到run文件夾下,文件名為main.c;
(5)將輸出文件重定向至“./run/ce.txt”;
(6)執(zhí)行編譯;
(7)編譯失敗,則將編譯錯(cuò)誤(Compile Error)存儲(chǔ)到數(shù)據(jù)庫(kù),跳轉(zhuǎn)至第(19)步;
(8)編譯成功,則會(huì)生成可執(zhí)行文件,名為main;
(9)記錄當(dāng)前時(shí)間time1;
(10)讀取題號(hào)對(duì)應(yīng)的測(cè)試數(shù)據(jù)文件中的一對(duì)的輸入文 件 和 輸 出 文 件;eg:./date/1000/1.in 與./date/1000/1.out相對(duì)應(yīng)。
(11)如果讀取文件成功,則將輸入輸出文件分別存儲(chǔ)到run文件夾內(nèi),存儲(chǔ)為data.in和data.out。
(12)如果讀取文件失敗,記錄當(dāng)前時(shí)間time2,并將測(cè)試成功(Accepted)存儲(chǔ)到數(shù)據(jù)庫(kù),將time2-time1存儲(chǔ)到數(shù)據(jù)庫(kù)中,作為程序運(yùn)行時(shí)間,跳轉(zhuǎn)至第(19)步;
(13)重定向輸入文件為data.in,重定向輸出文件為user.out;
(14)執(zhí)行main程序;
(15)超時(shí),則將超時(shí)間限制(Time Limit Exceed)運(yùn)行結(jié)果存儲(chǔ)到數(shù)據(jù)庫(kù);跳轉(zhuǎn)至第(19)步;
(16)超內(nèi)存,則將超內(nèi)存限制(Memory Limit Exceed)運(yùn)行結(jié)果存儲(chǔ)到數(shù)據(jù)庫(kù);跳轉(zhuǎn)至第(19)步;
(17)比較user.out文件和data.out文件,如果完全匹配則跳轉(zhuǎn)至第(10)步;
(18)如果user.out文件和data.out文件有區(qū)別,則存儲(chǔ)(Wrong Answer)到數(shù)據(jù)庫(kù),跳轉(zhuǎn)至第(19)步;
(19)本次源代碼評(píng)測(cè)結(jié)束。
評(píng)測(cè)功能模塊應(yīng)能給出以下4種類型的反饋信息并存入數(shù)據(jù)庫(kù):
編譯錯(cuò)誤:源代碼無法通過編譯。
運(yùn)行時(shí)錯(cuò)誤信息:用戶程序運(yùn)行時(shí)出現(xiàn)異常 ,如超內(nèi)存,超時(shí)間等。
結(jié)果正確:用戶程序能準(zhǔn)確地通過所有測(cè)試用例且其他指標(biāo)均符合要求。
結(jié)果錯(cuò)誤:用戶程序沒能得到正確運(yùn)行結(jié)果。
評(píng)測(cè)模塊主要通過C語言提供的接口實(shí)現(xiàn)對(duì)MySQL數(shù)據(jù)庫(kù)的操作。用到的API主要有:
MYSQL*mysql_init(MYSQL*mysql),用來實(shí)現(xiàn)初始化一個(gè)MySQL對(duì)象。
MYSQL*mysql_connect(MYSQL*mysql,const char*host,const char*user,const char*passwd),用來實(shí)現(xiàn)連接數(shù)據(jù)庫(kù)。
int mysql_query(MYSQL*mysql,const char*query),實(shí)現(xiàn)對(duì)SQL語句的執(zhí)行操作。
對(duì)源代碼文件進(jìn)行編譯。實(shí)現(xiàn)對(duì)源代碼文件進(jìn)行編譯的核心代碼如下:
編譯的參數(shù)主要有:-o out_file,-O2,-Wall,-lm,--static,-std=c99,-DONLINE_JUDGE。 分 別具有如下含義:
編譯結(jié)果判斷。通過測(cè)試編譯時(shí)輸出文件的大小來確定編譯是否通過。當(dāng)編譯輸出文件大小大于0時(shí),說明在編譯時(shí)有警告或錯(cuò)誤信息。當(dāng)編譯輸出文件等于0時(shí),說明編譯通過。
源代碼文件編譯通過之后,會(huì)生成一個(gè)名為“main”的文件。接下來的工作是測(cè)試程序的正確性。源代碼在線評(píng)測(cè)系統(tǒng)采用的是黑盒測(cè)試。
執(zhí)行被測(cè)試程序的過程如下:
(1)重定向輸入輸出文件。
在執(zhí)行被測(cè)試程序時(shí),需要先去讀取程序的標(biāo)準(zhǔn)輸入文件。因此在執(zhí)行被測(cè)試程序之前需要將輸入文件重定向到提前準(zhǔn)備好的標(biāo)準(zhǔn)輸入數(shù)據(jù),將輸出數(shù)據(jù)重定向到用戶工作目錄內(nèi)。實(shí)現(xiàn)的關(guān)鍵代碼如下:
(2)創(chuàng)建一個(gè)子進(jìn)程。
利用fork()函數(shù)生成一個(gè)子進(jìn)程。被測(cè)試程序的整個(gè)運(yùn)行過程都是在子進(jìn)程內(nèi)完成的。
(3)設(shè)置資源限制。
運(yùn)行程序之前需要設(shè)置運(yùn)行時(shí)的限制資源,包括內(nèi)存和CPU時(shí)間等限制。資源限制采用struct rlimit實(shí)現(xiàn)。struct rlimit結(jié)構(gòu)如下
結(jié)構(gòu)體中rlim_cur是要取得或設(shè)置的資源軟限制的值,rlim_max是硬限制的值。
(4)用函數(shù)調(diào)用要執(zhí)行的程序。
執(zhí)行被測(cè)試程序使用Execl()實(shí)現(xiàn)。
Execl()可以執(zhí)行第一個(gè)參數(shù)path字符串所代表的文件,接下來的參數(shù)代表執(zhí)行該文件時(shí)傳遞的參數(shù)argv[0],argv[1].....最后一個(gè)參數(shù)必須用空指針NULL結(jié)束。使用該函數(shù)時(shí)需要的頭文件為:#include<unistd.h>。
測(cè)試程序運(yùn)行結(jié)果的基本思想如下:利用標(biāo)準(zhǔn)輸入文件作為待評(píng)測(cè)程序的輸入文件,將待評(píng)測(cè)程序的輸出結(jié)果和標(biāo)準(zhǔn)輸出結(jié)果進(jìn)行比較。如果所有測(cè)試用例的程序輸出和標(biāo)準(zhǔn)輸出均一致,則認(rèn)為待評(píng)測(cè)程序正確,否則認(rèn)為被評(píng)測(cè)程序有錯(cuò)誤。在測(cè)試過程中還需要監(jiān)視內(nèi)存和時(shí)間是否超限,如果超限則返回相應(yīng)的錯(cuò)誤代碼。
在測(cè)試運(yùn)行結(jié)果之前和之后分別對(duì)系統(tǒng)時(shí)間進(jìn)行獲取,兩者時(shí)間差即為評(píng)測(cè)時(shí)間。
測(cè)試運(yùn)行結(jié)果的核心代碼如下所示:
(1)定義全局目錄、標(biāo)準(zhǔn)輸入、輸出文件的名字和用戶程序輸出文件名。下面為具體的程序代碼:
(2)判斷標(biāo)準(zhǔn)輸入文件是否存在。下面為具體的程序代碼:
(3)以標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出為依據(jù),測(cè)試源代碼程序的正確性。其中prepare_files()函數(shù)實(shí)現(xiàn)將標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出文件復(fù)制到run文件夾下。run_solution()函數(shù)運(yùn)行在子進(jìn)程中,用來執(zhí)行程序并判斷程序輸出結(jié)果和標(biāo)準(zhǔn)答案之間的關(guān)系,同時(shí)負(fù)責(zé)監(jiān)視程序的運(yùn)行狀態(tài),并可以將程序運(yùn)行狀態(tài)返回,以供父進(jìn)程捕獲。watch_solution()函數(shù)運(yùn)行在父進(jìn)程中。用來獲取run_solution()函數(shù)的返回值,返回值代表程序的測(cè)試結(jié)果。下面為具體的程序代碼:
為防止非法用戶的反復(fù)提交,數(shù)據(jù)庫(kù)記錄每次用戶提交的時(shí)間,限制用戶最快提交速度是1min提交一次程序。
測(cè)試時(shí)間獲取是以s為單位的,獲取評(píng)判時(shí)間的精度為ms級(jí)。主要用到了結(jié)構(gòu)體timeval和gettimeofday()函數(shù)。評(píng)判時(shí)間獲取的方法如下:
程序測(cè)試完成之后,需要將測(cè)試的結(jié)果保存到數(shù)據(jù)庫(kù)。同時(shí)更新和題目相關(guān)的數(shù)據(jù)表。需要更新的數(shù)據(jù)表有:problem表、solution表和user表。
更新數(shù)據(jù)庫(kù)信息需要利用C語言來操作MySQL數(shù)據(jù)庫(kù)來實(shí)現(xiàn)。
下面以存儲(chǔ)測(cè)試結(jié)果為例,說明如何利用C語言將測(cè)試結(jié)果存入數(shù)據(jù)庫(kù):
下面是源代碼在線評(píng)測(cè)系統(tǒng)的運(yùn)行截圖,圖5是用戶提交源代碼的界面。圖6是源代碼在線評(píng)測(cè)系統(tǒng)的評(píng)測(cè)結(jié)果界面。
圖5 提交題目源代碼界面(正確源代碼)
圖6 源代碼測(cè)試狀態(tài)界面
本文首先介紹了系統(tǒng)的意義,接著闡述了系統(tǒng)的模型。本文重點(diǎn)介紹了源代碼在線評(píng)測(cè)系統(tǒng)的設(shè)計(jì)和實(shí)現(xiàn)技術(shù),最后給出了系統(tǒng)的運(yùn)行截圖。實(shí)踐表明,該系統(tǒng)能極大地提高程序測(cè)試的效率,提高學(xué)生的學(xué)習(xí)興趣,是一個(gè)很好的教學(xué)平臺(tái)。
[1]梁嵩,王建新,盛羽.在線程序語言評(píng)測(cè)系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)[J].計(jì)算技術(shù)與自動(dòng)化,2010,6(2):128-132.
[2]王騰,姚丹霖.計(jì)算機(jī)應(yīng)用與軟件[J].計(jì)算機(jī)應(yīng)用與軟件,2006,12:129-130.