在线编译系统的实现,需要有服务器和多个客户端实现;
客户端:
- 允许客户选择编译的语言: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;
}
欢迎指点,询问!!!