【项目】在线编译系统
程序员文章站
2022-05-02 20:22:42
...
一、需求分析
1、客户端
(1)允许用户选择不同的语言,比如说:C,C++。
(2)提供用户编写代码的功能,将用户编写代码保存到本地
(3)将用户编写的代码传输到服务器
(4)能够接受服务器处理结果并显示
2、服务器
(1)接受客户端传输的数据,包括语言类型和代码
(2)能根据用户选择的语言类型对代码进行编译,编译完成后有两种结果
- 编译成功:将编译的可执行文件执行,将执行结果发送给客户端
- 编译失败:将出错信息反馈给客户端
3、功能流程
二、具体功能实现
1、服务器功能实现
(1)首先要实现的是服务器与客户端的连接,所以第一步我们创建出套接字,这里我们选用TCP协议使得客户端和服务器相连接(三次握手建立连接),也就是实现socket()、bind()和listen()等操作。
注意:
- 如果实现的是一台主机上完成操作,那么就可以将IP地址设为回环地址“127.0.0.1”。但是要是在两台主机(一个模拟客户端一个模拟服务器)来实现通讯就要把这个IP地址设置为服务器主机的IP地址。
- listen的第二个参数是内核维护的完成三次握手的连接,一般设置为5。
- bind()失败有两种原因:①ip地址不是我们本机的IP地址。
②端口号不对(使用了没有权限的端口号或者使用了别的程序正在用的端口号)。
//创建套接子,实现客户端与服务器连接,对套接字进行初始化
//1、实现对套接字的初始化(调用socket()、bind()、listen())
int CreateSocket(char *ip, short port)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) return -1;//在主函数中实现断言
struct sockaddr_in ser_addr;
memset(&ser_addr, 0, sizeof(ser_addr));//地址初始化
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port);
ser_addr.sin_addr.s_addr = inet_addr(ip);
int res = bind(listenfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
if (res == -1) return -1;
res = listen(listenfd, LISTENSIZE);
if (res == -1) return -1;
return listenfd;
}
(2)创建epoll_creat()
,将客户端所连接上的所有事件添加到epoll
在内核所创建的事件表中,实现了I/O复用技术;
实现:首先要通过epoll_create()
创建一个内核事件表epfd
。然后将套接字sockfd
使用cepoll_ctl
的方式添加到内核事件表当中,关注的事件为读事件。最后使用epoll_wait
来监听内核事件表,返回就绪事件。
(3)因为有的事件为空,所以我们使用epoll_wait()
循环获取就绪的文件描述符;
- 获取新的客户端连接
当有新的客户端连接造成sockfd
就绪的时候就需要获取新的客户端连接 。通过socket
编程里面的accept
函数获取。再将该连接关注的事件类型设置为可读和断开异常事件并采取ET模式通过epoll_ctl
添加到内核事件表当中。最后将监听的文件描述符通过fcntl
函数设置为非阻塞。
//客户端有事件要处理
static void InsertFdToEpoll(int epfd, int fd, short event)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = event;
int res = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);//将事件放入内核事件事件表中
assert(res != -1);
}
//获取新的客户端
static void GetNewLink(int epfd, int listenfd)
{
struct sockaddr_in cli_addr;//定义客户端结构
socklen_t len = sizeof(cli_addr);
int c = accept(listenfd, (struct sockaddr*)&cli_addr, &len);//连接客户端
if (c == -1) return;
InsertFdToEpoll(epfd, c, EPOLLIN | EPOLLRDHUP | EPOLLET);
}
- 连接成功,处理事件
//删除对应内核时间表中的事件
static void DeleteFdFromEpoll(int epfd, int fd)
{
int res = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
assert(res != -1);
}
//处理就绪事件和处理就绪非连接事件
static void DealReadyFd(int epfd, struct epoll_event *evbuff, int n, int listenfd)
{
int i = 0;
for (; i < n; ++i)
{
//循环遍历就绪描述符号
int fd = evbuff[i].data.fd;
if (fd == listenfd) // 有新的客户端连接 sockfd就绪
{
//获取新的客户端进行连接
GetNewLink(epfd, fd);//要获取新的客户端必须拿到就绪sockfd和内核事件表的文件描述符
}
else // fd这个客户端有数据到达
{
//有事件就绪——也可能为客户端断开了连接
if (evbuff[i].events & EPOLLRDHUP)
{
//客户端断开连接
//1.删除对应内核时间表中的事件
//2.关闭文件描述符
DeleteFdFromEpoll(epfd, fd);
close(fd);
}
else
{
// 两种选择: 1) 一次将数据读完 2)分多次将数据读完
int language = RecvFile(fd);
int err_size = BuildFile(language);
if (err_size > 0)
{
// 将错误信息发送给客户端
SendFile(fd, ERR, err_size);
continue;
}
// 执行 a.out java main
}
}
}
}
- 接收客户端的代码
static int RecvFile(int fd)
{
//接收协议头。根据语言类型创建对应文件
//接受到代码信息后保存到本地
Head head; // language filesize
//创建本地文件-接收协议头
int n = recv(fd, &head, sizeof(head), 0);
if (n <= 0)
{
return -1;
}
int filefd = open(file[head.language], O_WRONLY | O_CREAT | O_TRUNC, 0664);
assert(filefd != -1);
int sum = 0;
//接收代码
while (1)
{
char buff[128] = { 0 };
int size = head.filesize - sum > 127 ? 127 : head.filesize - sum;
int n = recv(fd, buff, size, 0);
sum += n;
write(filefd, buff, n);
if (sum == head.filesize)
{
break;
}
}
close(filefd);
//返回语言类型
return head.language;
}
(4)根据所获取的具体时间,安排不同的任务;
2、客户端功能实现
(1)在线编译系统实现,首先与客户端相连接
建立连接即就是socket编程。首先通过socket创建一个套接字,然后客户端通过connect的方式与服务器建立连接。
(2)其次选择要使用的语言 然后编写代码
(3)发送自己写的代码
这里我们采用协议的方式,我们与服务器相互合作的协议是:语言类型+代码量
其次发送代码
int BuildFile(int language)
{
// C -- gcc CPP -- g++ Java --- javac go --- go build
int num = 0;
int fds[2];
pipe(fds);
//应该创建子进程去替换系统的可执行文件vim
pid_t pid = fork();
assert(pid != -1);
if (pid == 0)
{
close(fds[0]);
close(1);
close(2);
dup(fds[1]);
dup(fds[1]);
switch (language)
{
case 0:
execl("/usr/bin/gcc", "/usr/bin/gcc", file[language]);
perror("gcc error: ");
break;
case 1:
execl("/usr/bin/g++", "/usr/bin/g++", file[language]);
perror("g++ error: ");
break;
case 2:
execl("/usr/bin/javac", "/usr/bin/javac", file[language]);
perror("javac error: ");
break;
default:
break;
}
close(fds[1]);
close(1);
close(2);
}
else
{
close(fds[1]);
wait(NULL);
int fd = open("error.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
assert(fd != -1);
while (1)
{
char buff[128] = { 0 };
int n = read(fds[0], buff, 127);
if (n <= 0)
{
break;
}
num += n;
write(fd, buff, n);
}
close(fd);
}
return num; // 0 > 0
}
(4)得到反馈结果,做出下一步的选择
上一篇: mybatis 查询数据表
推荐阅读
-
win10系统本地iis服务器部署vue.js项目
-
Windows 10测试项目负责人离职:这下系统问题要少了?
-
2015年高考如何在线估分 高考在线估分系统详解
-
Win10怎么判断系统是纯净安装还是在线升级(实用技巧解答)
-
谈一谈个人利用Java的mysql的知识完成的数据库的项目-----用户信息管理系统
-
用于cocos2d-x引擎(ndk)中为android项目生成编译文件列表
-
利用ASP+XML架设在线考试系统
-
A5创业项目推荐:云计算、大数据、在线教育等平台获得融资
-
Swoole跟thinkphp5结合开发WebSocket在线聊天通讯系统
-
PHP框架实现WebSocket在线聊天通讯系统