文章目录
1、概述
1、引言
有了对网络部分相关知识的了解和扩充以后,我在想着如何利用网络的一系列编程流程来实现一个可以在客户端和服务器两端分别实现的项目。基于对大量OJ题的练习,便引发了我对在线编译系统的探究和思考。
比如我们平时在力扣或者牛客上进行刷题的时候,我们作为一个用户角色在题库里面选择了一道练习题过后便开始编写代码,代码编写完毕后只需点击开始执行的按钮,系统就会出现这道题的编译结果
- 如果编译正确的话,我们再点击代码提交,系统就会自动运行,将运行结果显示
- 如果编译错误的话,就直接把编译出错信息反馈显示
2、思想
有了基于现实事物的发现,便引发了我对在线编译系统的思考。是否能实现一个在线编译系统使得用户在客户端能够编写自己想编写的语言的代码,然后将代码发送给服务器解析这段代码,最后服务器把结果发送给客户端。
关于实现该项目所用的知识储备,请见我写的另外一篇博客在线编译系统前期准备
2、项目综述
2.1项目功能描述
在线编译系统中分别在客户端和服务器两端具备了如下的功能:
1、客户端
- 允许用户选择不同的语言,比如说:C,C++。
- 提供用户编写代码的功能,将用户编写代码保存到本地
- 将用户编写的代码传输到服务器
- 能够接受服务器处理结果并显示
2、服务器
- 接受客户端传输的数据,包括语言类型和代码
- 能根据用户选择的语言类型对代码进行编译,编译完成后有两种结果
编译成功的话
:将编译的可执行文件执行,将执行结果发送给客户端
编译失败的话
:将出错信息反馈给客户端
2.2项目框架
因为整个代码的实现分为了客户端和服务器端的实现,因此主函数里面各自的流程图如下图所示。
3、项目功能具体实现
3.1服务器功能实现
(1)主函数
1、TCP建立连接——CreateSocket
因为我们使用的TCP的编程流程,所以首先要通过三次握手的方式建立客户端和服务器的连接。在服务器一端就应该实现socket()、bind()还有listen()
一系列流程。从而得到socket套接字sockfd。其中有几个点是需要注意的。
- IP地址的设置,如果就在一台机器上测试的话就设置为回环地址“127.0.0.1”即可。但是要实现两台主机一个模拟客户端一个模拟服务器的通讯就要把这个IP地址设置为服务器主机的IP地址。
- listen的第二个参数是内核维护的完成三次握手的连接,一般设置为5.
2、创建内核事件表,实现epoll的I/O复用
因为我们要实现高效的epoll I/O复用。所以首先要通过epoll_create
创建一个内核事件表epfd。然后将套接字sockfd使用cepoll_ctl
的方式添加到内核事件表当中,关注的事件为读事件。最后使用epoll_wait
来监听内核事件表,返回就绪事件。
(2)处理就绪事件
服务器应该就绪的文件描述进行处理,整个流程如下图所示:
1、获取新的客户端链接
当有新的客户端链接造成sockfd就绪的时候就需要获取新的客户端链接 。通过socket编程里面的accept
函数获取。再将该连接关注的事件类型设置为可读和断开异常事件并采取ET模式通过epoll_ctl
添加到内核事件表当中。最后将监听的文件描述符通过fcntl
函数设置为非阻塞
void GetNewClient(int sockfd,int epfd)
{
struct sockaddr_in cli;
socklen_t len = sizeof(cli);//保存连接的客户端信息
int fd = accept(sockfd,(struct sockaddr*)&cli,&len);
if(fd < 0)
{
return;
}
printf("客户端%d已连接\n",fd);
//设置新的链接关注的事件并将其添加到内核事件表中
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
int flag = fcntl(fd,F_GETFL);//获得文件描述符状态
flag = flag | O_NONBLOCK;//将状态设置为非阻塞
fcntl(fd,F_SETFL,flag);//将非阻塞状态设置给文件描述符
}
2、处理客户端数据
对于客户端传来的数据首先服务器要对其接收然后再执行编译、运行和反馈一系列操作。具体流程图如下:
在这个过程中发送结果分为两个内容。如果编译失败了发送编译失败提示信息结果,如果编译成功了要进行下一步执行,要把执行结果发送给客户端。
void DealClientData(int fd)
{
//3.1接收客户端的数据,将代码存储到本地文件中
int language = RecvCoding(fd);
//3.2编译代码,将编译结果存储到编译错误文件中
int flag = BuildCoding(language);
if(flag == 0)
{
//3.3执行代码,结果存储到文件中
Carry(language);
//3.4发送执行结果
SendResult(f