这篇文章是我的生产实习报告,在Linux操作系统上实现的一个简单的HTTP服务器,也算是一个小项目。请大家多多指教。

一、实习目的

本次实习紧紧围绕Linux操作系统基础知识展开,主要学习了Linux系统的常用命令、gcc编译链接过程、多线程通信和同步技术、socket网络通信、HTTP服务器等内容。与此同时,在老师的带领下进行实操训练,例如:编写Makefile文件管理工程、实现静态库和动态库、模仿系统bash实现自己的命令解释器、编写多线程程序并实现同步、实现TCP/UDP服务器端和客户端进行通信等。
最后通过独立完成一个基于Linux平台C语言编写的http服务器,巩固课程学到的Linux平台上的编程规范、技术和技巧,增强对于Linux操作系统的熟练度,培养我们编写较大型程序的能力,培养底层软件开发的能力,并为将来从事Linux平台开发、嵌入式开发等相对高端的软件开发工作打下基础。
本次实习具体目的如下:
(1)掌握并熟练使用Linux操作系统常用命令;
(2)熟练使用vim、gcc编译器、gdb等工具在Linux平台上进行程序的编写、编译以及调试;
(3)使用C语言编写轻量级http服务器实现发布静态页面功能;
(4)采用线程池和I/O复用方法实现同时处理多个客户端请求。

二、实习项目及内容

2.1开发平台

本项目是基于Linux系统C语言实现的http服务器,开发环境如下:
开发平台:腾讯云服务器
操作系统:Ubuntu Server 20.04 LTS 64bit
CPU:2核
内存:4GB
系统盘:60GB SSD云硬盘

2.2项目功能

本项目设计的http服务器是一个轻量级的服务器,使用Reactor模式,即主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。除此之外,主线程不做其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
本项目的基本功能如下:
(1)能接收客户端的GET请求;
(2)能够解析客户端的请求报文,根据客户端要求找到相应的资源;
(2)能够回复http应答报文;
(3)能够读取服务器中存储的文件,并返回给请求客户端,实现对外发布静态资源;
(4)使用I/O复用来提高处理请求的并发度;
(5)服务器端支持错误处理,如要访问的资源不存在时回复404错误等。

2.3技能储备

为了完成本项目,实现本项目的具体功能,需要具有一定的技能储备作为技术支撑。
首先应该掌握Linux操作系统的常用命令,C语言基础,熟练使用vim、gcc编译器、gdb等工具,Linux平台上进行程序的编写、编译以及调试能力,socket网络通信的编程能力,I/O复用理论知识以及编程能力,多线程编程能力,以及一定的HTML语言能力。

三、项目设计

3.1设计概述

本项目是基于Linux操作系统,使用C语言实现的轻量级http服务器。使用socket网络编程技术实现服务器端和客户端之间的通信。同时,为了提高本服务器的并发处理性能,本次http服务器设计使用Reactor模式。通过I/O复用和线程池相结合,实现同时响应多个客户端的请求,保证http服务器的并发性。

3.2 Reactor模式

Reactor模式是指主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。除此之外,主线程不做其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。
工作流程如下:
(1)主线程往epoll内核事件表中注册socket上的读就绪事件。
(2)主线程调用epoll_wait等待socket上有数据可读。
(3)当socket上有数据可读时,epoll_wait 通知主线程。主线程则将socket可读事件放入消息队列。
(4)一旦放入消息队列便创建相应的线程即工作线程,在线程函数中处理客户端信息,然后往epoll内核事件表中注册该socket上的写就绪事件。
(5)主线程调用epoll_ wait 等待socket可写。
(6)当socket可写时,epoll _wait 通知主线程。主线程将socket可写事件放入消息队列。
(7)创建工作线程,往socket上写入服务器处理客户请求的结果。

3.3 socket网络编程

本项目通过socket网络编程技术实现http服务器端和客户端实现通信。并且采用的是TCP协议。
TCP 提供的是面向连接的、可靠的、字节流服务。TCP 的服务器端和客户端编程流程如下图:

3.4 http服务器应答报文设计

如果客户端请求响应成功,则想客户端发送成功应答报文。如下表所示:
表3-1 请求成功的应答报文
如果客户端请求响应失败,例如服务器端没有客户端所请求的资源,则回复失败报文。如下表所示:
表3-2 请求失败应答报文

四、代码实现及运行结果

4.1主要功能实现

4.1.1 主函数

主函数中主要调用各个封装好的方法函数,首先调用创建套接字函数,创建套接字,然后创建消息队列。接着创建线程池,子线程同时执行loop_thread线程函数,将在 msgrcv处阻塞,等待获取消息队列中的消息。主线程调用epoll_create方法创建内核事件表,调用epoll_add函数添加描述符和事件。接着使用epoll_wait方法获取就绪描述符,一旦获取到就绪描述符便向消息队列中发送消息,便可以解除子线程中消息队列的阻塞,执行子线程中的程序,连接客户端,实现通信。

int main()
{signal(SIGPIPE,sig_fun);sockfd = socket_init();//调用创建套接字函数if ( sockfd == -1 ){exit(0);}msgid = msgget((key_t)1234,IPC_CREAT|0600);//创建消息队列if ( msgid == -1 ){exit(0);}pthread_t id[4];for( int i = 0; i < 4; i++ )    //循环创建线程池{pthread_create(&id[i],NULL,loop_thread,NULL);}    epfd = epoll_create(MAXFD);//创建内核事件表if ( epfd == -1 ){printf("create epoll err\n");exit(0);
}
epoll_add(epfd,sockfd);//调用封装的函数添加描述符和事件struct epoll_event evs[MAXFD];while( 1 ){int n = epoll_wait(epfd,evs,MAXFD,-1);//获取就绪描述符if( n == -1 ){continue;}
else    {struct mess m;m.type = 1;for(int i = 0; i < n; i++ ){m.c = evs[i].data.fd;if ( evs[i].events & EPOLLIN ){msgsnd(msgid,&m,sizeof(int),0); //向消息队列发送消息}}}}
}

4.1.2创建套接字函数

将初始化创建套接字函数封装。

int socket_init()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);if ( sockfd == -1 ){return -1;}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(80);saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if ( res == -1 ){printf("bind err\n");return -1;}res = listen(sockfd,5);if ( res == -1 ){return -1;}return sockfd;
}

4.1.3线程函数

线程将在 msgrcv处阻塞,等待获取消息队列中的消息,判断描述符类型,进行accept操作或recv操作。收到客户端请求数据后再调用get_filename函数获取客户端请求的资源名称,再判断发送错误或者正确应答报文。

 void* loop_thread(void*  arg)
{while( 1 ){struct mess m;msgrcv(msgid,&m,sizeof(int),1,0);//从消息队列中读取消息int c = m.c;if ( c == sockfd ){struct sockaddr_in caddr;int len = sizeof(caddr);int cli = accept(sockfd,(struct sockaddr*)&caddr,&len);if ( cli < 0 ){continue;}epoll_add(epfd,cli);}else{char buff[1024] = {0};int n = recv(c,buff,1023,0);if ( n <= 0 ){epoll_del(epfd,c);//调用移除描述符函数close(c);printf("close\n");continue;}char* filename = get_filename(buff);//调用资源名获取函数if ( filename == NULL ){send_404status(c);//调用发送错误应答报文函数epoll_del(epfd,c);//调用移除描述符函数close(c);continue;}printf("filename:%s\n",filename);if ( send_httpfile(c,filename) == -1 )//调用发送正确应答报文函数{printf("主动关闭连接\n");epoll_del(epfd,c);close(c);continue;}}epoll_mod(epfd,c);//调用重置函数}
}

4.1.4封装epoll函数

//添加描述符函数
void epoll_add(int epfd,int fd)
{struct epoll_event ev;ev.data.fd = fd;ev.events = EPOLLIN | EPOLLONESHOT;if ( epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1){printf("epoll add err\n");}
}
//移除描述符函数
void epoll_del(int epfd, int fd )
{if ( epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1 ){printf("epoll del err\n");}
}
//重置描述符函数
void epoll_mod(int epfd, int fd)
{struct epoll_event ev;ev.data.fd = fd;ev.events = EPOLLIN | EPOLLONESHOT;if ( epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev) == -1 ){printf("epoll mod err\n");}
}

4.1.5获取资源名函数

通过这个函数来解析客户端请求报文,获取资源名称。

char* get_filename(char buff[])
{   char* ptr = NULL;char * s = strtok_r(buff," ",&ptr);if ( s == NULL ){printf("请求报文错误\n");return NULL;}printf("请求方法:%s\n",s);s = strtok_r(NULL," ",&ptr);if ( s == NULL ){printf("请求报文 无资源名字\n");return NULL;}if ( strcmp(s,"/") == 0 ){return "/index.html";}return s;
}

4.1.6发送正确应答报文函数

如果客户请求资源可以正常访问,则调用该函数发送应答报文。

int  send_httpfile(int c, char* filename)
{if ( filename == NULL || c < 0 ){send(c,"err",3,0);return -1 ;}char path[128] = {PATH};strcat(path,filename);//  /home/ubuntu/ligong/day12/index.hmtlint fd = open(path,O_RDONLY);if ( fd == -1 ){//send(c,"404",3,0);send_404status(c);return -1;}int size = lseek(fd,0,SEEK_END);lseek(fd,0,SEEK_SET);char head_buff[512] = {"HTTP/1.1 200 OK\r\n"};strcat(head_buff,"Server: myhttp\r\n");sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);strcat(head_buff,"\r\n");//分隔报头和数据 空行send(c,head_buff,strlen(head_buff),0);printf("send file:\n%s\n",head_buff);int num = 0;char data[1024] = {0};while( ( num = read(fd,data,1024)) > 0 ){send(c,data,num,0);}close(fd);return 0;
}

4.1.7发送错误应答报文函数

如果客户端访问的在服务器端资源不存在,则调用该函数发送应答
报文。

int  send_404status(int c)
{int fd = open("err404.html",O_RDONLY);if ( fd == -1 ){send(c,"404",3,0);return 0;}int size = lseek(fd,0,SEEK_END);lseek(fd,0,SEEK_SET);char head_buff[512] = {"HTTP/1.1 404 Not Found\r\n"};strcat(head_buff,"Server: myhttp\r\n");sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);strcat(head_buff,"\r\n");//分隔报头和数据 空行send(c,head_buff,strlen(head_buff),0);char data[1024] = {0};int num = 0;while( ( num = read(fd,data,1024)) > 0 ){send(c,data,num,0);}close(fd);return 0;
}

4.1.8 index.htlm

<html><head><meta charset=utf8><title>baixingyu</title></head><body background="R-C.jpg"><center><h2>bxy</h2></center><a href="test.html">下一页</a></body></html>

4.1.9 test.html

 <html><head><meta charset=utf8><title>测试</title></head><body><center><h2>小狗小狗</center><a href="index.html">返回</a></body></html>

4.1.10 404err.html

 <html><head><meta charset=utf8><title>访问失败</title></head><body background="1.jpg"><center><h2>页面走丢了</center></body>
</html>

4.2测试及运行结果

为了测试http服务器是否能够正常运行,并且实现上文提到的功能,分别采用了PC端和移动手机端进行网页测试。
本次测试用例及预期结果如下表所示:
表4-1 测试用例及结果

首先PC端在浏览器地址栏输入服务器所在的IP地址进行访问,可以成功获取到服务器端的index页面,如图所示。

点击下一页,跳转到test页面。如图所示:

在访问地址后随意追加错误访问信息,即访问客户端不存在的资源,得到404err页面。如图所示:

访问客户端存在的资源,读取到的相应的资源。例如输入1.116.157.150\3.jpg或2.jpg。得到图片内容,如图所示:


移动手机端测试同理,同样可以得到相应的结果如下图所示:


最后,为测试http服务器端的并发性,同时使用多个客户端进行连接,同时访问服务器端的资源,均可正常运行。服务器端打印的部分请求信息如下图:

通过测试结果显示,本http服务器实现了对外发布的静态资源的功能,并且对于错误的访问信息可以进行处理回复。并且,通过多用户同时访问测试结果显示,该服务器具有较好的并发性,能够满足一定客户端同时请求资源的需求。

五、实习总结

六、参考文献

《高性能服务器编程》游双
《C语言程序设计》谭浩强
《http权威指南》[美]David Gourley Brian Totty Marjorie Sayer
Sailu Reddy Anshu Aggarwal

C语言实现http服务器(Linux)相关推荐

  1. Cisco服务器怎么安装系统,CISCO服务器Linux系统安装步骤

    CISCO服务器Linux系统安装步骤 开机进入CISCO LOGO界面,按ESC,进入DOS界面. 根据提示,按CTRL+C进入RAID界面. 在RAID界面的Adapter下选择1064E,单击回 ...

  2. web服务器 linux+apache+tomcat+mysql+jsp+php 整合安装

    2019独角兽企业重金招聘Python工程师标准>>> web服务器 linux+apache+tomcat+mysql+jsp+php 整合安装 自己的安装过程,以前发表在新浪博客 ...

  3. 组件分享之后端组件——基于Golang语言的游戏服务器框架leaf

    组件分享之后端组件--基于Golang语言的游戏服务器框架leaf 背景 近期正在探索前端.后端.系统端各类常用组件与工具,对其一些常见的组件进行再次整理一下,形成标准化组件专题,后续该专题将包含各类 ...

  4. linux系统配置sftp服务器,linux配置sftp服务器配置

    linux配置sftp服务器配置 内容精选 换一换 简要介绍miRanda是一种用于寻找microRNA基因组靶标的算法.该算法已用C语言编写,可以作为GPL下的开源方法使用.开发语言:C/C++一句 ...

  5. 无网络服务器(linux ubuntu),pip安装python科学计算所有需要包(packages)

    无网络服务器(linux ubuntu),pip安装python科学计算所有需要包(packages) # 在windows上打开anaconda,进入环境tab页,在base环境处单击,然后点开te ...

  6. FTServer 1.1 发布,多语言全文搜索服务器

    FTServer是一个简洁的多语言全文搜索服务器,能在低于2M内存的情况下对大于2M的文本进行搜索,并且接近内存搜索速度. 搜索结果排序按照ID顺序,不跳过,不漏词,不多余, 可以按任意组合搜索中文, ...

  7. 空服务器安装linux,debian服务器linux服务器web建站搭建linux服务器之Debian安装

    debian服务器linux服务器web建站搭建linux服务器之Debian安装 原文来自i火吧 大家都知道linux的发行版本很多,有centos啊,debian啊,ubuntu等,下面我就用de ...

  8. mysql独立服务器_独立服务器linux系统mysql设置方法

    独立服务器linux系统mysql设置方法: 一,如果您要用root身份使用您的mysql数据库,那么您可以直接将您的mysql数据库文件上传到:/usr/local/mysql/data目录下面,修 ...

  9. 服务器linux centos 7.4 搭建ftp服务器

    此操作是在腾讯云服务器linux centos 7.4 完成搭建ftp服务器 vsftpd 的: 安装 vsftpd $ yum install vsftpd -y 启动 $ service vsft ...

  10. linux上安装telnet服务器:linux vmvare虚拟机 安装telnet redhat9

    linux上安装telnet服务器:linux vmvare虚拟机 安装telnet redhat9 参考:http://blog.sina.com.cn/s/blog_5688414b0100bhr ...

最新文章

  1. python练习:猜价钱小游戏
  2. form表单提交踩坑记
  3. Spring5源码 - 02 Bean和Java对象的区别与猜想验证BeanDefinition
  4. 第十届 蓝桥杯样题 —— 5个砝码
  5. Springboot整合Quartz集群部署以及配置Druid数据源
  6. java使用poi实现大数据量导出为EXCEL
  7. 整合Spring与Hibernate
  8. Hibernate占位符?和:及JPA
  9. plt.Circle()
  10. NSArray去除重复元素
  11. 安卓系统源码、内核下载
  12. 大数据工程师的日常工作内容是干嘛?
  13. 30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)
  14. 公司要求实时监控服务器,写个Web的监控系统
  15. 【观察】亚信科技:“飞轮效应”背后的数智化创新“延长线”
  16. 1095 习题6-9 折半查找
  17. php接口接收json数据
  18. java 导出批量图片_Java Poi 导出excel(含图片及多个sheet)
  19. 锚杆拉拔试验弹性模量计算_锚杆拉拔试验方法
  20. 2022-2027年中国自动化立体仓库行业发展前景及投资战略咨询报告

热门文章

  1. 压缩视频大小画质不变,视频压缩大小清晰度不变怎么做?
  2. 下血本买的!Android开发者出路在哪?先收藏了
  3. 通过zCloud实现数据库故障的“1-3-5”一站式高效处理
  4. python绘制蟒蛇,绘制五彩蟒蛇
  5. ”去他丫的北上广,老子要去成都定居了,Android-Binder机制及AIDL使用
  6. 百度炮轰Google搜索不创新 拟全力进攻云搜索
  7. 多元伯努利分布 multivariate bernoulli distribution
  8. 微信支付找不到sdk
  9. 随手记——echarts图表
  10. 四、TCP中的流量控制和拥塞控制