項目: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;
}

歡迎指點,詢問!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章