前段時間給學校做一個線上練習C語言程式和C語言考試的系統,伺服器架構是LAMP的。因為其他的都沒什麼好多的,要實現線上編譯只要exec()函數直接調用gcc進行編譯就行了,$compile_str = "gcc ".$filename." -o ".$prog_name." 2>"."compile_result.txt;iconv -f UTF-8 -t GB2312 "."compile_result.txt -o compile_res.txt";
這裡解釋一下命令:$filename是你的C語言來源程式的地址,-o指定產生可執行檔名,2>compile_result.txt是將編譯結果輸出到compile_result.txt檔案裡面去(gcc的編譯結果是輸出到標準錯誤2的而不是標準輸出1)分號是本語句結束,下一個語句是編碼轉換的命令,因為我要返回的是gb2312編碼的字元,所以就將之前的編譯結果檔案轉換成GB2312編碼放到compile_res.txt裡面去了。這裡實現其實很多餘,因為你完全可以就enconv -L zh_CN -x UTF-8 filename直接搞定,但是蛋疼的是伺服器居然沒有這個工具。沒辦法之下才用的iconv。如果客戶要的就是UTF-8編碼的話,那直接 2>&1就OK了,都不用臨時檔案咯~
接下來是要在用戶端上運行我們的C程式,並且將程式的輸出輸出到瀏覽器上。(並且能夠讀入使用者端在瀏覽器輸入的輸入)這才是我們的重頭戲。
首先,我們的實現還是基於臨時檔案和系統調用的基礎之上實現的。不錯如果要把使用者的輸入給弄到程式裡面去的話,僅僅這樣是不行的,下面我來說下我的實現方法:
$input = $_POST['input']; //取得使用者輸入 $fp=fopen($path.'input.txt','w+');//開啟檔案 if(!$fp) //錯誤處理 { echo "開啟檔案失敗"; echo $path.'input.txt'; } $temp=fwrite($fp, $input); //使用者輸入寫入檔案 fclose($fp); if(chdir($path) == false){ //進入目錄執行命令 echo "進入目錄失敗:"; } if(!chmod("run_me", 0777)){ //run_me程式我們接下來講 echo "改變檔案許可權失敗"; } $cmd_str = "./run_me ".$prog_name." input.txt";//命令語句 echo "運行結果:"; exec($cmd_str, $arr); //執行命令將輸出結果放到數組arr裡面 for($i = 0; $i < count($arr); $i++) { echo $arr[$i]."/n"; }
主要的代碼就是這樣,這裡的run_me程式是一個用C語言寫的,將使用者程式作為一個子進程運行,並且將使用者程式的標準輸入關閉,通過管道把input.txt的內容輸入到使用者程式中。(這裡需要寫過Linux下的進程最後輸出運行結果的操作。
run_me的原始碼如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> void get_file_buf(char (*buf)[81], char *filename)//取得檔案的緩衝到資料緩衝區 { FILE *fp; int i = 0; if( (fp = fopen(filename, "r")) == NULL) { perror("can't open the file"); exit(EXIT_FAILURE); } while(fgets(buf[i], 81,fp) != NULL) { i++; } } int main(int argc, char **argv) //主函數 { int file_pipe[2], i = 0; //定義管道 pid_t fork_result; //fork結果 char row_input[100][81]; //定義使用者輸入緩衝大小 char progname[255]; //定義程式名稱 char *filename; if(argc > 3){ printf("usage :run_me $programname $filename"); exit(EXIT_FAILURE); } //Initiaze buffer for(i = 0; i < 100; i++) { memset(progname, '/0', 81); memset(row_input[i],'/0',81); } // strncat(progname, "./", strlen("./")); //這裡將使用者程式加入./指定目前使用者 strncat(progname, argv[1], strlen(argv[1]));//連結參數傳遞的程式名稱 if(argc == 2) { system(progname); return 0; } filename = argv[2]; //輸入檔案名稱 get_file_buf(row_input, filename); i = 0; if(pipe(file_pipe) == 0) //管道初始化 { fork_result = fork(); //分叉程式產生子進程 if(fork_result == (pid_t) -1){ //錯誤處理 perror("Fork failure"); exit(EXIT_FAILURE); } if(fork_result == (pid_t) 0 ) //子進程操作 { close(0); dup(file_pipe[0]); close(file_pipe[0]); close(file_pipe[1]); execlp(progname, progname, NULL, NULL); exit(EXIT_FAILURE); } else //父進程操作 { close(file_pipe[0]); while( *(row_input[i]) != '/0')//向子進程寫入緩衝流 { write(file_pipe[1], row_input[i], strlen(row_input[i])); i++; } close(file_pipe[1]); usleep(10000); //只讓程式執行10毫秒 execlp("killall", "killall", argv[1], NULL);//結束子進程 } } return 0; }
如果熟悉在Linux下操作的朋友門一定覺得我上面的操作有點多餘,直接就運行./prog_name < input.txt不就完了麼。呵呵,是這樣的,但是你要想到,如果使用者是一個死迴圈程式,那麼,你的伺服器估計活不了太長的時間,如果只在系統層面limit對系統做限制,又不能保證大量使用者同時使用這個系統。所以我才自己手動實現了一個重新導向操作,並且加入了只讓使用者程式執行10毫秒的元素,這樣就解決問題了~呵呵,如果有更好的方法希望大家推薦一下。也歡迎大家指出我的錯誤和問題。有什麼不明白的地方也可以說出來大家討論討論~~