项目定位与服务器(SERVER)模块划分

打印 上一主题 下一主题

主题 1726|帖子 1726|积分 5178

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录
定位
HTTP协议以及HTTP服务器
高并发服务器
单Reactor单线程
单Reactor多线程
多Reactor多线程

模块划分
SERVER模块划分
Buffer 模块
Socket模块
Channel 模块
Connection模块 
Acceptor模块
TimerQueue模块
Poller模块
EventLoop模块
TcpServer模块
SERVER子块之间的连接
连接模块
EventLoop 模块

Acceptor模块


定位

在这个项目中,我们要实现的是一个高并发服务器组件,项目中通过组件的方式提供不同的应用层协议支持,以让使用者能够快速搭建指定应用层协议的一个高并发服务器。 当然,实际在项目中我们只提供了HTTP协议的支持。 
要注意的是,我们的服务器只是一个服务器组件,不提供详细的业务逻辑,业务逻辑须要组建的使用者根据需求自行设置。
那么我们的项目标重点就是实现两个模块大概说组件,一个是服务器的组件,一个是HTTP协议的组件。
在这里我们先相识服务器组件的模块划分。

HTTP协议以及HTTP服务器

HTTP协议(Hyper Text Transfer Protoccol)  ,超文本传输协议,他是一个应用层协议,是一种简朴的哀求-相应式协议,一般都是有客户端向服务器建立连接,并且发起哀求,而服务器则根据客户端的哀求将特定的资源返回给客户端大概完成特定的功能,而一般相应返回之后,连接就关闭了,当然HTTP也支持长连接
详细的HTTP的讲解在我们的Linux系统与网络专栏中有讲解,假如想要深入相识,可以前往
   应用层协议 —— https-CSDN博客
  HTTP协议是基于TCP协议的,所以我们所说的搭建一个HTTP服务器,其实是搭建一个TCP服务器,然后在服务器使用HTTP协议来分析哀求,完成业务逻辑,以及构建相应返回。
简朴来说,HTTP服务器其实就是以下四个步骤:
1 搭建一个TCP服务器,吸收TCP连接
2 吸收HTTP哀求,举行哀求的分析处理与相应
3 将处理结果构造成一个HTTP相应,通过TCP服务器返回给客户端

高并发服务器

实现一个HTTP服务器是十分简朴的,但是实现一个高性能的支持高并发的服务器并不简朴。而要实现一个高性能服务器,如今最常用的方式就是使用 Reactor 模子来搭建。
 
Reactor 模式简朴理解就是 多路复用,仅用一个线程就能监听多个连接,哪个连接有数据输入大概说哪个连接吸收到了发数据,就去处理该连接的事件。 他是一种事件驱动模式,只有在事件到来时,才会驱动服务器去处理。
而在Reactor线程监听到了对应的事件之后,就可以将数据分发给线程池举行业务逻辑的处理,这样就不须要多个线程不停在死循环等候IO就绪,能将系统资源让出来。
我们一般监听的事件就是 读事件,而读事件又分为两种,一种是读数据的事件就绪,另一种是监听套接字的读事件,其实就是底层有新的连接就绪,等候上层去获取。
Reactor 模子有三种:
单Reactor单线程

单Reactor单线程其实指的就是只由一个线程完成全部的连接的事件监控以及事件触发之后直接由该线程处理。 说白了就是单线程。那么在单线程环境下,不管是连接建立哀求,照旧数据处理的哀求,都是在这一个线程完成的。

在这种模式下,全部的操作都在同一个线程内完成。
   这种模式的优点就是设计简朴,不会出现线程安全问题,全部的操作都是船形的。
    但是他的缺点也很明显,由于是单线程,他无法发挥多核CPU的优势,性能上限不高。
  但是并不是说他就一无可取,他适合那些客户端很少,且业务处理速度很快的场景。 
而假如业务处理很慢,那么他的串行化就会导致新连接来不及处理

单Reactor多线程

在这种模式下,我们使用一个 Reactor 线程专门用来监听事件,假如是新连接到来,那么在Reactor线程中举行连接的获取以及将新连接添加到多路复用模子中举行事件监控,而假如是数据到来,那么照旧由Reactor线程举行数据的IO,然后将完整的哀求交给线程池中的业务线程举行业务处理。 最终业务线程把数据哀求完之后,将相应交给Reactor线程举行相应操作。

   在这个工作模式下,能够使用多核CPU的优势,让不同线程并行推进进度,进步服从。
  他的缺点也很明显,首先,多线程访问任务队列要做共享数据安全的掩护。 同时,我们的Reactor线程负担了全部的IO事件(获取哀求和相应哀求)和连接事件,那么意味着,假如我们的服务器是一个高并发的服务器,也就是说,每一个时刻都有许多的客户端发送连接哀求,而由于在这个模子中,IO和连接事件都是由一个Reactor线程处理的,也就是说,IO和连接事件的处理是串行的,那么也就意味着,IO的时候,我们是来不及举行新连接的获取的,由于IO的时间很长,最终大概导致大量连接来不及处理,无法满足高并发场景。

多Reactor多线程

上面的单Reactor多线程模子的最大的缺点就是高并发场景下,新连接大概来不及处理。而多Reactor多线程模子就是解决这个问题的。 
首先,我们只须要让一个单独的Reactor线程,专门用来获取新连接,这样就不会出现新连接来不及处理的问题。 而对于已经建立的连接的IO事件的监控,则交给其他的Reactor线程去完成。而这些Reactor线程读到完整的哀求之后,照旧将数据放到任务队列中,让我们的工作线程举行处理。

我们一般把这个负责获取新连接的Reactor线程称为主Reactor线程,而把这些负责详细的连接的事件监控的Reactor线程称为从属Reactor线程,末了,照旧把负责业务处理的线程称之为工作线程。
但是上面的模子有一些很明显的缺点:
   1 由于我们须要管理从属Reactor线程池和工作线程池,我们须要管理的共享资源都须要加锁,而加锁会导致服从的低落。
  2 由于线程过多,CPU调度时线程切换的本钱高。
  那么要怎么解决呢?
一般在主从Reactor模式里面,从属Reactor线程之下不设置工作线程,而是直接由从属线程举行业务处理

那么这么做的利益是什么呢?首先,线程的数量明显淘汰了,CPU线程切换的次数大概说本钱就低落了。 
同时,每一个连接对应的事件都只在一个Reactor线程中举行,那么对于一个连接的事件处理可以在线程中通过执行顺序的控制,保证一个连接的事件操作的安全。

而在我们的项目中要使用的就是主从Reactor模子,我们不须要实现工作线程池,直接在从属线程中举行事件的监控和处理。
从属Reactor线程要做三件事: 1、IO事件的监控 ;2、IO事件的处理;3、业务处理



模块划分

我们的项目中须要两个大模块,一个就是服务器模块,该模块用于实现一个主从Reactor模子的TCP服务器。 另一个就是协议模块,他用来给我们的服务器提供应用层的协议支持。在我们这个项目中就只提供HTTP协议的支持。

SERVER模块划分

我们的SERVER模块就是服务器模块,他须要管理全部的连接,以及管理我们所创建的全部的线程大概历程,我们须要保证他们安全高效的运行,最终达到高性能服务器的目标。
而详细的管理可以划分为三个部分:
   1 对新连接的监听管理
  2 对已建立的连接的管理
  3 对超时连接的管理
  对新连接的监听管理,其实就是管理我们的监听套接字,以及新连接建立之后的一系列操作,这须要由我们的主Reactor线程完成。
而队以建立的连接的管理则是监听已建立的连接的IO事件以及举行详细的业务处理,理所当然由从属Reactor线程完成。
对超时连接的管理其实就是对已建立的连接大的管理,也须要在从属Reactor线程中举行,为什么要举行超时管理呢?我们知道TCP协议是有自己的超时管理的,但是他的超时时间太长,是以小时为单位的,而我们要实现的服务器并不想让一些非活跃的连接来长期占用我们的资源,毕竟我们是要应对高并发的场景,假如一个连接不活跃,不如尽早把他的资源接纳了来交给新的连接。
那么基于以上的管理思想,我们又可以将SERVER模块划分为以下的子模块:
Buffer 模块

Buffer模块其实就是一个用户态的缓冲区,那么对应每一个连接都须要配备两个缓冲区,吸收缓冲区和发送缓冲区。 为什么我们有内核的缓冲区了,还须要提供用户级缓冲区? 这一点我们在网络的学习中应该也十分熟练了,因为我们从内核缓冲区读取出来的数据不一定就是一个完整的报文,那么我们就须要将已经读出来的数据继承生存。  于此同时,我们为什么要有发送缓冲区呢? 因为假如我们直接向内核缓冲区写的话,大概内核缓冲区的写事件没有就绪,那么也就会壅闭在写入操作,所以对用户的要发送的数据我们须要使用一个缓冲区来举行生存,使用缓冲区的缘故原由也有一点就是不须要让用户在写入失败时再来思量什么时候能写,而是把这些工作交给了我们的底层的事件监听以及事件处理来做。
而我们的缓冲区模块须要对外提供以供其他的模块调用调用的接口其实就只有两个,一个就是往缓冲区中写入数据,一个是从缓冲区中读取数据



Socket模块

Socket 模块其实就是对socket的封装,简化我们对套接字的操作,同时,我们还可以提供一些集成的功能,比如提供接口直接创建一个服务端的监听套接字,大概直接创建一个客户端连接服务端的通信套接字。
那么Socket的的操作其实就是对socket的操作的一些封装,让用户用起来更简朴。

而Socket重点的接口大概常用的接口其实就是两个继承功能,以及发送和担当数据。


Channel 模块

Channel模块主要负责套接字所监听的事件的管理,以及就绪的事件的处理的回调函数的管理
每一个连接都须要对应一个 Channel 模块,Channel 模块中生存了该连接的套接字所须要监听的事件,同时Channel对象中也生存了各类事件的处理的回调方法。
那么详细有哪些事件呢?其实很简朴,我们可以看一下 epoll 可以为我们监视的事件的类型,我们大抵将其分为四类

第一类就是 读事件,EPOLLIN和EPOLLPRI都是数据到来,我们须要举行读取。 而EPOLLRDHUP则表示对端已经关闭套接字,准备关闭连接了,这时候其实我们也须要尽快将数据从内核缓存区读走。 这三个我们都归为读事件
第二类就是写事件,EPOLLOUT        
第三类是 挂断事件,EPOLLHUP,这个挂断和前面的EPOLLRDHUP的区别就是,EPOLLRDHUP是对端挂断,也就是对端关闭连接,而EPOLLHUP则是本端主动挂断,这时候我们也须要举行处理,比如将new出来的Channel对象开释,以及从其他的布局中拿走
第四类就是错误事件,错误事件发生之后我们其实也是须要开释对象和将对象从其他的模块中拿走。
第三类和第四类时间固然有一定的区别,但是实际上我们的处理方法是类似的。

那么在与其他模块的关联上,重点接口就是四类事件的监控,以及四类事件的处理


Connection模块 

Connection 模块是对一个通信套接字的整体的管理,他内部其实会管理 Buffer,Socket,Channel对象,以及超时连接的处理等。每当我们Accept获取到一个新链接的时候,其实就须要创建一个Connection对象,而Connection 对象举行创建的时候,会对内部的三个子模块举行初始化设置,比如 Channel 内部的四个回调函数,就是有Connection传进去的。
同时Connection模块也须要包含几个上层设置的回调函数: 连接建立时须要执行的回调函数,连接关闭时执行的回调函数,有一个新数据时执行的回调函数,以及事件就绪时的回调函数
而Connection模块还须要提供协议切换/升级的功能,这个功能其实就是对协议上下文以及Connection的四个上层回调方法的重新设置。

他的主要接口其实分为三个部分,一类是对外提供的接口,供其他模块调用,一类是封装内部模块的操作的接口,还有一类是由上层模块设置的事件回调,当然这一类其实是算在他的成员,严格意义上来说并不是他的接口,不过为了理解他与上层模块的联系,我们照旧将其罗列出来。


Acceptor模块

Acceptor模块是对监听套接字的Socket和Channel模块的一个整体管理,他实现的是对监听套接字的管理。
他的主要操作就是获取新连接,然后通过内部设置的回调方法将新连接封装成一个Connection对象,而他的可读事件的处理其实就是 accept 并创建Connection对象交给上层。



TimerQueue模块

TimerQueue简朴理解就是对超时连接举行管理,更好的理解就是用于设置定时任务,以及管理定时任务。相当于一个定时任务的管理器,我们可以往里面添加一些定时任务,同时也可以革新定时人物的超时事件,以及取消定时任务等等。
这个模块在我们项目中主要的作用就是对Connection对象的超时管理,也就是对超时连接的资源开释



Poller模块

Poller模块就是对epoll及其操作的封装,是直接举行事件监控的模块,须要完成监控事件的添加,移除,以及对形貌符移除监控,以及获取就绪事件等。



EventLoop模块

EventLoop模块其实就是我们的主从Reactor模子中的从属Reactor模块,它是对Poller,TimerQueue,Socket模块的整体封装,负责全部的形貌符的事件监控。
每一个EventLoop模块其实都须要对应一个线程,每一个连接创建出来之后,都须要关联大概说绑定一个EventLoop线程,今后之后,对于该连接的全部操作都须要在这个EventLoop线程中完成,以保证操作的线程安全。 不能出现说其中一个线程正在举行操作,另一个线程就把连接关闭了。 把对同一个连接的全部操作放在同一个线程中执行,他们就是串行化的,能避免出现线程安全问题。
EventLoop须要举行实际的监控和事件处理,那么他须要关联一个Poller模块,每一个EventLoop模块内部都须要一个Poller来举行事件的监控,每一个连接和对应的EventLoop也须要相互干联,EvenetLoop中才能调用Connection中的Channel模块举行事件的处理。
同时,EventLoop模块还须要保证内部的全部的连接的活跃性,所以他还须要包含一个TimerQueue模块,来举行超时连接的资源开释工作。
那么EventPool须要对外提供的紧张接口其实大部分都是对子模块的封装,比如添加事件监控,移失事件监控,更新/修改时间监控(取消某个时间的监控都算在更新事件监控中)。 以及添加定时任务,革新定时任务,取消定时任务, 还有对四类就绪事件以及恣意事件的处理。
同时,由于同一时间大概会有许多事件就绪,IO事件完成之后,还须要举行一些其他的操作,而EventLoop为了进步服从,高优先级的先把全部的IO事件处理完成,而将对连接的其他的操作,比如数据处理,从用户缓冲区读取和发送数据等等操作放入一个任务队列中,而后对任务队列中的全部的任务按顺序执行。

紧张接口 :



TcpServer模块

TcpServer模块是全部管理模块的整合,它内部主要就是Acceptor模块和我们的从属Reactor线程池,也就是EventLoop线程池,这个模块是提供给用户使用的,提供一些接口让用户能够简易搭建一个服务器,以及举行一些设置。
这个模块是在末了全部的小模块都设计完了,再对Acceptor和EventLoopPool模块举行一个封装。重点工作其实照旧在其他的子模块,他只是对其他模块提供的功能的封装。



SERVER子块之间的连接

连接模块

首先,最紧张的就是 Connection模块与他的子模块的关系,因为Conection是我们整个项目中设计最复杂的模块,同时也是功能最复杂的模块,我们就拿他的几个紧张的接口来举例:

首先对于四个向外提供的机构来说,关闭连接,开释资源这一点,其实TcpServer模块是会对全部的Connection对象举行管理的,在实现关闭连接开释资源的时候,是会调用TcpServer给Connection设置的一个回调函数的。
而对于发送数据,Connection提供的发送数据是将数据拷贝到发送缓冲区,因为我们并不能保证调用发送时,我们套接字的内核缓冲区一定就能够举行写入,一定要比及写事件就绪才会真正的发送
而协议切换,主要有两个方面,一个就是我们Connection中会生存数据处理的上下文,这是跟协议强相干的,比如我们使用Http协议的话,我们的上下文中就会生存Http协议的报头中的各个字段,来表示当前处理到哪一个阶段了,那么假如这一次读取没有读到一个完整的报文,我们可以将这个进度生存在上下文中,那么下一次读事件再到来,有新数据担当的时候,我们就可以直接从上下文生存的进度开始继承读取,进步服从。那么协议在举行切换的时候,就须要将上下文数据举行替换。 除了上下文的替换之外,与协议强相干的还有TcpServer给Connection设置的四个回调方法,这四个回调方法就是不同协议该如那里理的区别所在,尤其是吸收新数据之后的回调,当然这些方法是由用户设置,协议切换时,用户也须要传入新的数据和新的上下文布局数据。 
而启动和取消超时开释计谋,测试依赖于TimerQueue所提供的接口来完成的,当启动超时计谋时我,我们就会添加/革新(有大概上次取消超时计谋之前设置的定时任务还在时间轮中)一个当前连接的定时关闭任务,而取消超时计谋就是将Connection的定时任务通过TimerQueue提供的接口取消掉。同时,一个连接是否须要启动超时计谋也是由TcpServer大概用户来设置的。

而Connection中的吸收数据的方法,其实是作为Channel中的读事件的回调执行的,由Connection提供,而吸收数据最终也是将数据缓存到Connection中的Buffer缓冲区中。 
而发送数据也是由Channel中的写事件触发的,写事件就绪时,会调用由Connection设置的写事件回调,将数据从Buffer缓冲区写到内核缓冲区中。 
关闭套接字接口也是由Channel事件触发的,但是这个关闭套接字接口其实只会将该套接字的监听移除,而整个连接的开释其实会在全部数据处理完之后举行
革新活跃度的接口其实就是通过调用TimerQueue中提供的接口来完成的。

剩下的就是用户设置给TcpServer,而TcpServer设置给Connection 的四个回调方法。 建立连接回调,会在Acceptor获取到一个新连接,并将其封装成一个Connection,建立完连接时举行调用。
而用户设置的吸收数据的回调,会在我们的Channel的读事件回调中举行一次调用,其实就是在Connection提供的读接口中。
而关闭连接回调也是在Connection设置给Channel的关闭连接事件的回调中调用。
恣意事件回调也是一样的,会在Connection提供的,设置给Channel的恣意事件回调用举行调用,也就是在革新定时器之后举行一次调用。

EventLoop 模块

其实对连接的全部的操作都会由EventLoop对应的线程来完成,EventLoop是操作的真正的执行者,Connection等模块其实并不会真正执行操作,只有交给EventLoop线程执行才会保证线程安全。
而在这里我们就重点关注三个小模块,分别是TimerQueue模块,Channel模块以及Poller模块。

首先对于事件的管理,其实EventLoop中对于事件的处理的操作都是封装了Poller的接口,而Channel在监控的事件发生变革时,也会调用EventLoop中的接口举行操作,最终本质照旧调用了Poller的接口操作。
而事件就绪之后,调用处理方法,其实不是直接调用,因为未来我们是使用线程池来举行事件监控和获取的,取出这个事件的并不一定就是关联的EventLoop线程,所以未来我们举行操作时,假如是EventLoop获取的,那么可以直接执行,而假如不是,那么会放到EventLoop线程的任务队列中。
而EventLoop对于连接的定时任务的管理也是使用TimerQueue提供的接口完成的。



Acceptor模块

Acceptor模块就是我们的主Reactor线程,他负责担当新连接,同时要将新连接封装成一个Conencton对象,添加到EventLoop的事件循环模块中。


以上就是我们的项目中的服务器组件的根本的模块划分、各个模块的主要功能,以及模块之间的大抵关系。
下一篇文章会先容几个项目中会用到的紧张的知识点大概技术。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表