linux在線編譯系統
一、需求分析
在線編譯系統的實現,需要有服務器和多個客戶端實現;
客戶端:
- 允許客戶選擇編譯的語言:c/c++/java...
- 能夠提供客戶編寫代碼的功能(編寫完成後自動保存到本地文件)
- 能夠將用戶編寫的代碼傳輸到服務器
- 能夠接收到服務器的處理結果並顯示
服務器:
- 能夠接受客戶的代碼(識別語言類型和接收代碼)
- 在線編譯(根據語言的類型調用不同的編譯器)
- 執行(編譯成功,將編譯的可執行文件執行)
- 第二步出錯,則返回結果給客戶端/第三步成功則返回執行結果給客戶端
二、系統設計
- 客戶端和服務器的業務處理流程設計(分析和處理數據的流程)
客戶端業務流程:
服務器業務流程:
三、詳細設計
服務器編程流程:
-
我們要實現在線編譯系統,首先要實現的是服務器與客戶端相連接,所以第一步我我們創建出套接字,選用TCP協議使得客戶端和服務器相連接;
-
其次我們創建epoll_creat(),將客戶端所連接上的所有時間添加到epoll在內核所創建的事件表中;
-
第三步,因爲有的時間爲空,所以我們使用epoll_wait()循環獲取就緒的文件描述符;
-
第四部,根據所獲取的具體時間,安排不同的任務;
主函數實現:
int main()
{
int sockfd=sock_create();//創建套接子,客戶端可以和服務器連接
assert(sockfd!=-1);
int epfd=epoll_create(5);//創建內核事件表,返回的是內核時間表的文件描述符
assert(epfd!=-1);
//定義epoll事件表結構
struct epoll_event event;
event.data.fd=sockfd;//放入事件表中的描述符號
event.events=EPOLLIN;//監聽可讀事件
//將客戶端所鏈接上的事件放入epoll內核事件表中
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);
while(1)
{
//返回就緒的文件描述符 epoll_create()
struct epoll_event events[MAX];//事件表數組
int n=epoll_wait(epfd,events,MAX,-1);//返回就緒描述符
//-1:設爲超時時間 以毫秒爲單位 我們在這裏設置爲-1 假設不超時
if(n<=0)//==0爲超時,我們這裏沒有超時時間
{
//出錯
perror("epoll wait error");
continue;
}
//就緒事件分爲兩種
//1.客戶端的連接 sockfd
//2.內核事件的數據處理
//處理就緒的事件
DealFinishEvents(sockfd,epfd,events,n);//就緒的事件包含客戶端的連接sockfd,就緒事件表中的文件描述符號epfd,所關注的事件events-EPOLLIN,就緒事件的個數n;
}
}
搭建起主函數之後,我們將實現套接字的建立,爲客戶端與服務器搭建搭建環境‘’
套接字初始化:
int sock_create()//創建套接子,實現客戶端與服務器連接
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
return -1;
}
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
if(res==-1)
{
return -1;
}
res=listen(sockfd,5);
if(res==-1)
{
return -1;
}
return sockfd;
}
連接成功,處理時間:
void DealFinishEvents(int sockfd,int epfd,struct epoll_event *events,int num)//處理就緒事件
{
int i=0;
for(;i<num;i++)
{
//循環遍歷就緒描述符號
int fd=events[i].data.fd;
//1.有新的客戶端進行連接 sockfd就緒
if(fd==sockfd)
{
//獲取新的客戶端
GetNewClient(sockfd,epfd);//要獲取新的客戶端必須拿到就緒sockfd和內核事件表的文件描述符
}
else
{
//有事件就緒-也可能爲客戶端斷開了連接
if(events[i].events & EPOLLRDHUP)
{
//客戶端斷開連接
//1.關閉文件描述符
//2.刪除對應內核時間表中的事件
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
}
else
{
//處理真實事件
DealClientData(fd);
}
}
}
}
獲取新的客戶端:
void GetNewClient(int sockfd,int epfd)//獲取新的客戶端
{
struct sockaddr_in cli;//定義客戶端結構
int len=sizeof(&cli);
int fd=accept(sockfd,(struct sockaddr*)&cli,&len);//連接客戶端
if(fd<0)
{
return;
}
//客戶端有事件要處理
struct epoll_event event;
event.data.fd=fd;
event.events=EPOLLIN | EPOLLRDHUP;
//將事件放入內核事件事件表中
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
}
處理就緒非連接時間:
void DealClientData(int fd)//處理真實事件
{
//這裏具體我們要分析客戶端傳輸過來的信息
//1.接受到客戶端的 struct Head文件屬性 和代碼
int language=RecvCoding(fd);
//對代碼進行編譯
//1.識別語言類型 2.選擇不同的編譯器進行編譯
int flag=BuildCoding(language);
//編譯結果
//1.編譯成功--執行--發送編譯結果
//2.編譯失敗--發送結果
//flag=0 執行程序 》0 編譯出錯
if(flag==0)
{
//執行程序
Carry(language);
//發送結果
SendResult(fd,flag);//執行成功的代碼在result.txt
}
else
{
SendResult(fd,flag);//執行失敗的在build_error.txt
}
}
接收客戶端的代碼:
struct Head
{
int language;
int file_size;
};//定義結構體接收代碼
int RecvCoding(int fd)//接受客戶端的代碼
{
//接受到代碼信息後保存到本地
struct Head head;
recv(fd,&head,sizeof(head),0);
//創建本地文件-接收協議頭
int filefd=open(file[head.language-1],O_WRONLY | O_TRUNC | O_CREAT,0664);
//接收代碼
int size=0;
while(1)
{
int num=head.file_size-size>127?127:head.file_size;//接收的代碼長度
char buff[128]={0};
int n=recv(fd,buff,num,0);
if(n==0)
{
break;
}
size=size+n;
write(filefd,buff,n);
if(size>=head.file_size)
{
break;
}
}
close(filefd);
//返回語言類型
return head.language;
}
對客戶端的代碼進行編譯:
char *build[]={"usr/bin/gcc","usr/bin/g++"};//調用不同的編譯器處理
int BuildCoding(int language)//對代碼進行比編譯
{
struct stat st;
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
//zi
//文件爲空 定義
int fd=open("./build_error.txt",O_CREAT | O_WRONLY | O_TRUNC,0664);
close(1);//1.標準輸出 2.標準錯誤輸出
close(2);
dup(fd);
dup(fd);
//失敗的文件爲空則成功
execl(build[language-1],build[language-1],file[language-1],(char*)0);
write(fd,"build_error",11);
exit(0);
}
else
{
wait(NULL);
stat("./build_error.txt",&st);
}
return st.st_size;
}
執行程序,反饋結果:
char *carry[]={"a.out","a.out"};
void Carry(int language)//執行程序
{
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
int fd=open("./result.txt",O_WRONLY | O_TRUNC | O_CREAT,0664);
close(1);
close(2);
dup(fd);
dup(fd);
//我這裏沒編輯除c語言c++語言之外的語言 因爲他們編譯成功都是a.out文件
//要是還有其他語言 這裏以java爲例
//if(language==3)
//{
// execl(carry[language-1],carry[language-1],"mian.class",(ahcr *)0);
//}
execl(carry[language-1],carry[language-1],(char*)0);
write(fd,"carry error",11);
exit(0);
}
else
{
wait(NULL);
}
}
void SendResult(int fd,int flag)
{
char *file="./result.txt";
if(flag)
{
file="./build_error.txt";
}
struct stat st;
stat(file,&st);
send(fd,(int*)&st.st_size,4,0);
int filefd=open(file,O_RDONLY);
while(1)
{
char buff[128]={0};
int n=read(filefd,buff,127);
if(n<=0)
{
break;
}
send(fd,buff,n,0);
}
close(filefd);
}
客戶端編程流程:
-
在線編譯系統實現,首先與客戶端相連接
-
其次選擇要使用的語言 然後編寫代碼
-
第三步,發送自己寫的代碼
這裏我們採用協議的方式,我們與服務器相互合作的協議是:語言類型+代碼量
其次發送代碼
- 第四部,得到反饋結果,做出下一步的選擇
主函數實現:
int main()
{
//實現與服務器的連接
int sockfd=Sock_Link();
assert(sockfd!=-1);
//連接已完成 可以發送數據,接受數據
//客戶端
//1.選擇使用的語言c c++
int language=ChoiceLanguage();
int flag=2;//第一次編寫代碼和編寫下一個重新創建文檔 作用相似
while(1)
{
//選擇好語言之後 我們要開始編寫代碼
WriteCoding(flag,language);
//編寫完代碼後 發送選擇的語言和代碼返回給服務器
SendData(sockfd,language);
//獲取服務器的反饋信息
RecvData(sockfd);
//得到反饋信息給客戶端提示(flag)
//1.修改代碼
//2.下一道題
//3.退出
flag=PrintTag();
if(flag==3)//退出
{
break;
}
}
close(sockfd);//關閉文件描述符
}
實現與服務器的連接:
int Sock_Link()//實現與服務器的連接
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//創建套接字
if(sockfd==-1)
{
return -1;
}
//連接服務器
struct sockaddr_in ser;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
if(res==-1)
{
return -1;
}
return sockfd;
}
選擇編程的語言:
int ChoiceLanguage()//選擇使用的語言
{
//我的電腦只有c/c++的編譯器
printf("*****************************\n");
printf("** 1-------c語言 **\n");
printf("** 2-------c++語言 **\n");
printf("*****************************\n");
printf("please input language(input number):");
int language=0;
scanf("%d",&language);
return language;
}
編寫代碼:
void WriteCoding(int flag,int language)//編寫代碼
{
//flag爲1 打開上次的文件繼續編寫
//flag爲2 創建新文件編寫
if(flag==2)
{
//打開文件編寫
unlink(file[language-1]);//把原有文件刪除 然後調用vim編寫
}
pid_t pid=fork();//創建子進程
assert(pid!=-1);
if(pid==0)//子進程
{
execl("usr/bin/vim","usr/bin/vim",file[language-1],(char*)0);
printf("exec vim error\n");
exit(0);
}
else//父進程
{
wait(NULL);
}
}
發送自己寫的代碼:
struct Head
{
int language;//語言類型
int file_size;//文件大小
};
void SendData(int sockfd,int language)//發送數據
{
//先發送語言的類+數據長度(文件的大小)
//代碼文件的內容內容
//爲了獲取文件屬性 我們要創建一個結構體包含文件的屬性(類型和大小)
struct stat st;
stat(file[language-1],&st);//顯示結構體狀態
struct Head head;
head.language=language;
head.file_size=st.st_size;
//文件的大小怎麼獲取?
//先發送文件的類型
send(sockfd,&head,sizeof(head),0);//把文件結構體屬性先發過去
//定義文件屬性 main.c main.cpp
int fd=open(file[language-1],O_RDONLY);//以只讀形式打開文件
while(1)
{
//發送數據
char buff[128]={0};
int n=read(fd,buff,127);
if(fd<=0)
{
//<0出錯 ==0已經讀完
break;
}
send(sockfd,buff,n,0);
}
close(fd);
}
接收反饋信息:
void RecvData(int sockfd)
{
int size=0;
recv(sockfd,&size,4,0);
int num=0;
while(1)
{
int x=size-num>127?127:size-num;
char buff[128]={0};
int n=recv(sockfd,buff,x,0);
if(n<=0)
{
close(sockfd);
exit(0);
}
printf("buff:%s\n",buff);
num=num+n;
if(num>=size)
{
break;
}
}
}
對反饋信息做出選擇:
int PrintTag()
{
printf("******************************\n");
printf("** 1---修改代碼 **\n");
printf("** 2---下一個 **\n");
printf("** 3---退出 **\n");
printf("******************************\n");
printf("please input flag:");
int flag=0;
scanf("%d",&flag);
return flag;
}
歡迎指點,詢問!!!