当前位置: 首页 > article >正文

WEB服务器实现(药品商超)

WEB 服务器(药品商店)及压力测试软件的设计

项目概述:在 C语言环境下,利用socket编程技术设计并制作一款基于HTTP协议的轻量级Web服务器。这个服务器不仅能够处理基本的Web请求,如用户注册、登录、产品罗列、详情页展示等,而且支持多用户实时并发访问。这意味着服务器能够同时处理来自多个客户端的请求,为用户提供快速且流畅的访问体验。

相关功能模块

  • TCP连接响应模块:主要负责设置服务端的IP地址以及端口号等,当监听套接字等到客户端的连接请求,便会生成通信套接字,便于后续的通信
  • HTTP报文解析:解析客户端发送的HTTP请求报文,其中包含了其想要的获得的资源信息,当解析完请求报文后,服务器会根据请求内容像客户端发送相应的HTML网页、图片、视频等。
  • 网页生成模块:通过用户请求关键字,在本地Sqlite3数据库中查询文字、图片、视频等信息,使用HTML语言动态编写网页。

设计亮点:

  • 通过epoll+多线程技术实现多并发服务器:服务器能够充分利用多核CPU的优势,同时处理多个客户端的请求
  • 学习webBench开源代码对服务器性能做压力测试

      这个项目主要是是基于c语言的轻量级的web服务器,如用户的注册,登录,商品展示和详情页面查看,还可以实现多用户并发访问,利用http协议和socket编程技术结合epoll和多线程技术实现的服务框架。我的项目主要包含3各模块

        tcp连接响应模块:首次初始化服务器的socket设置服务器的ip地址以及端口号,并监听来自客户端的连接请求一旦接收到请求,就创建一个新的通信套接字用于该用户的后续通信。「socket( )创建socket套接字;bind( )函数将socket与服务器地址(ip和端口)绑定;listen( ) 监听套接字监听连接请求;accept( )函数接受连接请求,并创建新的socket」

        第二个模块HTTP报文解析,解析来自客户端发送的HTTP 请求报文,从其中获取到请求方法例如 (GET,POST,URI,HTTP版本等信息,以及请求头( Content-Type)和请求体 )在项目中我主要是通过字符串处理函数解析的请求行,请求体和请求头的,其中最重要的部分是URI是HTTP协议中用于标识网络资源的字符串,它可以是一个URL或URN。在实际应用中,URL更为常见,因为它不仅能标识资源,还能定位到资源的具体位置。

        最后网页生成模块,根据用户请求,从seqlite3数据库中查询相应数据内容,并使用HTML语言动态生成网页内容返回给客户端

(epoll和webbench) 通过epoll+多线程技术实现多发服务器:

//webBench是一个简单的网站性能开源的压力测试工具,可以模拟多个客户端同时访问网站,用于测试网站在并发压力下的表现。指定测试的URL,并发客户端数量,测试时间等参数。运行webBench命令开始压力测试,根据测试报告分析服务器的响应时间,吞吐量等指标,评估服务器性能。

 

 

URI


        在HTTP(Hypertext Transfer Protocol,超文本传输协议)中,URI(Uniform Resource Identifier,统一资源标识符)扮演着关键角色,用于唯一地标识网络上的资源。URI是一个字符串,它可以是一个URL(Uniform Resource Locator,统一资源定位符)或者URN(Uniform Resource Name,统一资源名称)。然而,在实际应用中,URI的概念经常被URL所替代,因为URL是URI的一个子集,它除了能够标识资源外,还能定位到资源的具体位置。

URI的组成部分

一个典型的URI(或URL)可以包含多个部分,但并非所有部分都是必需的。这些部分通常遵循以下结构(以URL为例):

<scheme>://<user>:<password>@<host>:<port>/<path>?<query>#<fragment>

- **scheme**(协议):指明了用于访问资源的协议类型,如`http`、`https`、`ftp`等。
- **user:password**(认证信息):可选部分,包含访问资源所需的用户名和密码。出于安全考虑,现代Web应用中很少在URL中直接包含密码。
- **host**(主机名):服务器或资源的域名或IP地址。
- **port**(端口号):可选部分,指定了访问资源的端口。如果不指定,则使用默认端口(如HTTP的80端口,HTTPS的443端口)。
- **path**(路径):资源的路径。在Web服务器中,这通常指向服务器上文件系统中的某个位置。
- **query**(查询字符串):可选部分,用于传递额外的信息给服务器,通常用于动态页面内容的请求。它跟在`?`后面,参数之间用`&`分隔。
- **fragment**(片段标识符):可选部分,用`#`表示,用于指定资源的某个特定部分(如HTML页面中的锚点)。这部分不会发送给服务器。

示例

假设有一个URL如下:

https://user:password@www.example.com:8080/path/to/resource?param1=value1&param2=value2#section

- **scheme**:`https`
- **user:password**:`user:password`(出于安全考虑,不建议在URL中明文包含密码)
- **host**:`www.example.com`
- **port**:`8080`
- **path**:`/path/to/resource`
- **query**:`param1=value1&param2=value2`
- **fragment**:`section`

总结

URI是HTTP协议中用于标识网络资源的字符串,它可以是一个URL或URN。在实际应用中,URL更为常见,因为它不仅能标识资源,还能定位到资源的具体位置。了解URI的组成部分有助于更好地理解和使用HTTP协议。

epoll

epoll在web服务器中的具体操作流程是一个高效处理大量并发连接的关键机制,它主要通过Linux内核提供的接口实现。以下是epoll在web服务器中的具体操作流程:

1. 创建epoll实例

  • 使用epoll_create()epoll_create1()函数创建一个epoll实例,该函数返回一个文件描述符,即epoll文件描述符。

  • 这个文件描述符用于后续的注册文件描述符和等待事件。

2. 注册文件描述符

  • 使用epoll_ctl()函数将需要监听文件描述符(如socket)添加到epoll实例中,并指定需要监听的事件类型(可读 // 可写)(如EPOLLIN表示可读事件)。

  • 通过epoll_ctl()可以添加、修改删除epoll实例中的文件描述符及其事件

3. 等待事件发生

  • 调用epoll_wait()函数等待epoll实例中的文件描述符上的事件发生

  • epoll_wait()会阻塞直到有事件发生达到指定的超时时间

  • 当事件发生时epoll_wait()会返回触发事件的文件描述符数量,并将事件信息填充到用户提供的数组中

4. 处理事件

  • 遍历epoll_wait()返回的事件队列,根据事件类型(如可读、可写等)进行相应的处理。

  • 对于可读事件,通常表示有新的客户端连接或客户端发送了数据,web服务器可以读取数据并处理请求。

  • 处理完事件后,可以继续调用epoll_wait()等待下一轮事件的发生。

5. 循环监听和处理

  • web服务器通过不断循环调用epoll_wait()来持续监听和处理事件。

  • 这样可以高效地处理大量并发连接,避免了传统的轮询方式带来的性能问题。

6. 边缘触发和水平触发

  • epoll提供了两种触发模式:边缘触发(Edge-Triggered)和水平触发(Level-Triggered)。

  • 边缘触发模式只在事件状态发生变化时通知应用程序,适用于非阻塞I/O操作。

  • 水平触发模式在文件描述符处于就绪状态时持续通知应用程序,适用于阻塞I/O操作。

  • 根据实际需求选择合适的触发模式可以提高web服务器的性能。

7. 清理和关闭

  • 在web服务器关闭时,需要调用close()函数关闭epoll文件描述符和所有已注册的文件描述符。

  • 这可以释放系统资源,避免资源泄漏。

 

        epoll在web服务器中的具体操作流程包括创建epoll实例、注册文件描述符、等待事件发生、处理事件、循环监听和处理以及清理和关闭。通过高效的事件驱动机制,epoll能够在大规模并发的网络应用中提供卓越的性能和可扩展性。这使得epoll成为构建高性能web服务器的理想选择。

  •  socket接受线程:
            C语言为了高并发所以选择了epoll。当程序启动的时候(g_net_update.c文件中main函数,会启动一个thread函数create_accept_task)
            这个thread就处理一件事情,只管接收客户端的连接,当有连接进来的时候 通过epoll_ctl函数,把socket fd 加入到epoll里面去,epoll设置监听事件EPOLLIN | EPOLLET;
            主要是监听的是加入到epoll中的socket是否可读(因为我的需求是客户端连上了server就会马上向server发送一份数据的)。其它的部分在主线程中处理。
  • 主线程:
    是一个无线循环,epoll_wait 函数相当于把客户端的连接从epoll中拿出来(因为我们监听的是EPOLLIN | EPOLLET)说明这个时候客户端有数据发送过来)。再通过recv_buffer_from_fd 函数把客户端发送过来的数据读出来。然后其他的一切就抛给线程池去处理。
     
  • 线程池:
    (代码中我会在池里面创建15个线程) 双向链表。加入线程就是在链表后面加一个链表项,链表的前面会一个一个被拿出来处理。主要是malloc 函数free函数,sem_wait函数sem_post的处理(sem_wait 会阻塞当值大于0是会减一,sem_post是值加一)。typedef void* (FUNC)(void arg, int index);是我们自定义的线程的逻辑处理部分,arg是参数,index是第几个线程处理(我们隐形的给每个线程都标了号),例如代码中的respons_stb_info,更加具体可以看看代码里面是怎么实现的。聪明的你也可以改掉这块的内容改成动态线程池,当某个时刻的处理比较多的时候能够动态的增加线程,而不像我代码里面的是固定的。
     
  • 数据库连接池:按照我的需求在处理客户端请求数据的时候是要访问数据库的。就是一下子创建出一堆的数据连接。要访问数据库的时候先去数据库连接池中找出空闲的连接,具体可以看下代码。使用的时候可以参考下database_process.c文件(代码中数据库连接池和线程池中的个数是一样的)。这里我想说下get_db_connect_from_pool这个函数,我用了随机数,我是为了不想每次都从0开始去判断哪个连接没有用到。为了数据库连接池中的每个链接都能等概率的使用到,具体的还是可以看下代码的实现。

 

socket步骤

Socket编程的基本概念

Socket:在网络编程中,Socket是一个抽象层,它提供了端到端的通信服务。一个Socket代表了一个连接的一端,这个连接可以是两个应用程序之间的双向数据通信通道。

客户端(Client)和服务器(Server):在Socket编程中,通常有一方是服务提供者(服务器),它监听网络上的特定端口,等待客户端的连接请求;另一方是服务请求者(客户端),它向服务器发起连接请求,并在连接建立后通过该连接与服务器进行数据交换。

TCP(传输控制协议)和UDP(用户数据报协议):Socket编程可以基于TCP或UDP协议。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,适合传输大量数据;UDP则是一种无连接的、不可靠的、基于数据报的传输层通信协议,适用于对实时性要求较高但允许少量丢包的应用场景。

Socket编程的步骤

以TCP为例,Socket编程通常包括以下几个步骤:

创建Socket:在客户端和服务器端分别创建一个Socket实例。

绑定(Bind):服务器端通过调用bind()函数将Socket与特定的IP地址和端口号绑定在一起。这一步是服务器端特有的。

监听(Listen):服务器端调用listen()函数使Socket进入被动监听状态,准备接收客户端的连接请求。

建立连接(Connect):客户端通过调用connect()函数向服务器发起连接请求。对于服务器端,这一步是通过accept()函数完成的,它接受客户端的连接请求,并返回一个新的、用于与客户端通信的Socket。

数据交换(Send/Receive):连接建立后,客户端和服务器就可以通过read()和write()(或send()和recv())等函数进行数据交换了。

关闭连接(Close):通信结束后,客户端和服务器都会调用close()函数关闭Socket,释放资源。

 

 

 

 

 


http://www.kler.cn/a/396840.html

相关文章:

  • 视频流媒体播放器EasyPlayer.js RTSP播放器视频颜色变灰色/渲染发绿的原因分析
  • 论文阅读 - Causally Regularized Learning with Agnostic Data Selection
  • 生成式GPT商品推荐:精准满足用户需求
  • 基于 PyTorch 从零手搓一个GPT Transformer 对话大模型
  • 【Java基础知识系列】之Java类的初始化顺序
  • nacos-operator在k8s集群上部署nacos-server2.4.3版本踩坑实录
  • 前端页面一些小点
  • 跳房子(弱化版)
  • 论文中引用的数据如何分析?用ChatGPT-o1完成真的强!
  • websocket初始化
  • (02)ES6教程——Map、Set、Reflect、Proxy、字符串、数值、对象、数组、函数
  • 谷歌浏览器的自动翻译功能如何开启
  • Spring: IOC和DI 入门案例
  • Docker 安装全平台详细教程
  • 《C++ 实现生成多个弹窗程序》
  • 【Conda】Windows下conda的安装并在终端运行
  • 谷歌AI进军教育,这将改变未来?
  • Vue3中实现插槽使用
  • 桥梁缺陷YOLO免费数据集分享 – 6308张已标注8类缺陷图像
  • 牛客题库 21738 牛牛与数组
  • 【React】状态管理之Zustand
  • SQL Server 查询设置 - LIKE/DISTINCT/HAVING/排序
  • C++创建型设计模式综合示例
  • Docker中最常用的一些命令
  • [杂项] C++从一个序列查找子序列的方法
  • SOHO场景开局(小型,多子网):AP+管理型交换机+路由器+光猫