项目:linux在线编译系统

linux在线编译系统

一、需求分析

在线编译系统的实现,需要有服务器和多个客户端实现;

客户端

- 允许客户选择编译的语言:c/c++/java...

- 能够提供客户编写代码的功能(编写完成后自动保存到本地文件)

- 能够将用户编写的代码传输到服务器

- 能够接收到服务器的处理结果并显示 

服务器

- 能够接受客户的代码(识别语言类型和接收代码)

- 在线编译(根据语言的类型调用不同的编译器)

- 执行(编译成功,将编译的可执行文件执行)

- 第二步出错,则返回结果给客户端/第三步成功则返回执行结果给客户端

二、系统设计

  1. 客户端和服务器的业务处理流程设计(分析和处理数据的流程)

客户端业务流程
客户端

服务器业务流程
服务器

三、详细设计

服务器编程流程

  • 我们要实现在线编译系统,首先要实现的是服务器与客户端相连接,所以第一步我我们创建出套接字,选用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;
}

欢迎指点,询问!!!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值