小伙伴关心的问题:服务器架构和控制器架构(服务器产品结构设计),本文通过数据整理汇集了服务器架构和控制器架构(服务器产品结构设计)相关信息,下面一起看看。

服务器架构和控制器架构(服务器产品结构设计)

Reactor简介

在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,Proactor用于异步I/O操作。

目前常见的开源网络事件库libevent、libev、libuv、muduo都用到了Reactor设计模式。

Reactor模式称之为响应器模式,通常用于NIO非阻塞IO的网络通信框架中。

在这之前,需要弄明白几个概念:

什么是阻塞和非阻塞?

阻塞和非阻塞是针对于进程在访问数据时,根据IO操作的就绪状态而采取的不同方式,简单来说是一种读取或写入操作函数的实现方式,阻塞方式下读取或写入函数将一直等待。非阻塞方式下,读取和写入函数会立即返回一个状态值。

什么是同步和异步?

同步和异步是针对应用程序和内核的交互而言的,同步是指用户进程触发IO操作并等待或轮询的查看IO操作是否就绪,异步是指用户进程触发IO操作以后便开始做自己的事情,当IO操作完成时会得到通知,换句话说异步的特点就是通知。

什么是IO模型?

一般而言,IO模型可以分为四种:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞

同步阻塞IO是指用户进程在发起一个IO操作后必须等待IO操作完成,只有当真正完成了IO操作后用户进程才能运行。同步非阻塞IO是指用户进程发起一个IO操作后立即返回,程序也就可以做其他事情。但是用户进程需要不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。异步阻塞IO是指应用发起一个IO操作后不必等待内核IO操作的完成,内核完成IO操作后会通知应用程序。这其实是同步和异步最关键的区别,同步必须等待或主动询问IO操作是否完成,那么为什么说是阻塞呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,采用select函数的好处在于可以同时监听多个文件句柄,从而提高系统的并发性。异步非阻塞IO是指用户进程只需要发起一个IO操作后立即返回,等IO操作真正完成后,应用系统会得到IO操作完成的通知,此时用户进程只需要对数据进行处理即可,不需要进行实际的IO读写操作,因为真正的IO读写操作已经由内核完成。

Reactor原理

在服务器开发中,也有不少人基于Reactor反应堆设计模式来封装自己的网络库。

其中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程阻塞在多路复用器上,一旦有I/O事件到来或是准备就绪,多路复用器将返回并将相应I/O事件分发到对应的处理器中。

Reactor是一种事件驱动机制,和普通函数调用不同的是应用程序不是主动的调用某个API来完成处理,恰恰相反的是Reactor逆置了事件处理流程,应用程序需提供相应的接口并注册到Reactor上,如果有相应的事件发生,Reactor将主动调用应用程序注册的接口(回调函数)。

接下来我们细说Reactor的内部结构组件和流程。

Reactor中的核心组件有哪些呢?

Reactor

IO事件的派发者,相当于有分发功能的Selector。Acceptor

接收客户端连接并建立对应客户端的Handler,向Reactor注册此Handler。相当于NIO中建立连接的那个判断分支。Handler

和一个客户端通讯的实体,一般在基础的Handler上会有更进一步的层次划分,用来抽象诸如decode、process、encode这些过程。相当于消息读写处理等操作类。

在Reactor模式中有五个关键的参与者:描述符handle、同步事件分离器demultiplexer、事件处理器接口event handler、具体的事件处理器、Reactor管理器

Reactor的结构

Reactor模式要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,如果有的话立即将该事件通知给工作线程(逻辑单元)。除此之外,主线程不做任何其它实质性的工作。读写数据、接收新连接、处理客户端请求均在工作线程中完成。

Handle 文件描述符

Handle在Linux中一般称为文件描述符,在Windows中称为句柄,两者含义一样。Handle是事件的发源地。比如网络socket、磁盘文件等。发生在Handle上的事件可以有connection、ready for read、ready for write等。

Handle是操作系统的句柄,是对资源在操作系统上的一种抽象,它可以是打开的文件、一个Socket连接、Timer定时器等。由于Rector模式一般使用在网络编程中,因而这里一般指的是Socket Handle,也就是一个网络连接(connection/channel)。这个channel注册到同步事件分离器中,以监听Handle中发生的事件,对ServerSocketChannel可以是CONNECT事件,对SocketChannel可以是read、write、close事件等。

Synchronous Event Demultiplexer 同步(多路)事件分离器

同步事件分离器本质上是系统调用,比如Linux中的select、poll、epoll等。比如select()方法会一致阻塞直到文件描述符handle上有事件发生时才会返回。

无限循环等待新请求的到来,一旦发现有新的事件到来就会通知初始事件分发器去调取特定的时间处理器。

Event Handler 事件处理器

事件处理器,定义一些回调方法或称为钩子函数,当handle文件描述符上有事件发生时,回调方法便会执行。供初始事件分发器回调使用。

Concrete Event Handler 具体的事件处理器

具体的事件处理器,实现了Event Handler,在回调方法中实现具体的业务逻辑。

Initiation Dispatcher 初始事件分发器

初始事件分发器,提供了注册、删除、转发Event Handler的方法。当Synchronous Event Demultiplexer检测到handler上有事件发生时,便会通知initiation dispatcher调用特定的event handler的回调方法。

初始事件分发器用于管理Event Handler,定义注册、移除EventHandler等。它还作为Rector模式的入口调用Synchronous Event Demultiplexer同步多路事件分离器的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handle事件处理器进行处理,也就是回调EventHandler中的handle_event方法。

IO框架工作时序图

事件多路分解器

现代操作系统大多提供了一种本机机制,该机制通过一种有效的方式处理并发和非阻塞资源,这种机制称为同步事件多路分解器或事件通知接口。

相关视频推荐

linux多线程之epoll原理剖析与reactor原理及应用

网络原理tcp/udp,网络编程epoll/reactor,面试中正经“八股文”

学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

Reactor启动流程

创建Reactor注册事件处理器调用事件多路分发器进入无限事件循环当操作系统通知某描述符状态就绪时,事件分发器找出并调用此描述注册的事件处理器。

使用同步IO模型(以epoll_wait为例)实现的Reactor模式的工作流程

主线程向epoll内核事件表中注册socket上的读就绪事件主线程调用epoll_wait等待socket上有数据可读当socket上有数据可读时,epoll_wait通知主线程,主线程将socket可读事件放入请求队列。休眠在请求队列上的某个工作线程被唤醒,从socket中读取数据并处理客户端请求,然后向epoll内核事件表中注册该socket上的写就绪事件。主线程调用epoll_wait等待socket可写当socket可写时epoll_wait通知主线程,主线程将socket可写事件放入请求队列。休眠在请求队列上的某个工作线程被唤醒,向socket上写入服务器处理客户请求的结果。

Reactor和生产者消费者的区别

说到这里,有人很容易跟生产者消费者联系起来,这里不细讲生产者消费者,,我们直入主题。Reactor模式和生产者和消费者之间最大的区别在于

生产者消费者模式是基于队列queue的实现,能够解决生产端和消费端处理速度不同步的问题,队列可以采用先有的MQ产品来实现。Reactor模式是基于事件驱动模型,当接收到请求后会将请求封装成事件,并将事件分发给相应处理事件的handler,handler处理完成后将时间状态修改为下一个状态,再由Reactor将事件分发给能够处理下一个状态的handle进行处理。

Reactor模式与Observer观察者模式在某些方面极为相似,当一个主体发生改变时,所有依属体都将得到通知。不过观察者模式与单个事件源关联,而反应器模式则于多个事件源关联。

Reactor模式的优点很明显:解耦、提升复用性、模块化、可移植性、事件驱动、细粒度的开发控制等。Reactor模式的缺点也很明显:模型复杂,涉及到内部回调、多线程处理、不容易调试、需要操作系统底层支持,因此导致不同操作系统可能会产生不一样的结果。总来而言,如果并发要求不是很高,可使用传统的阻塞线程池足够了。如果使用场景是产生瞬间大并发可使用Reactor模式来实现。

Reactor和线程池的完美结合

线程池

虽然利用线程池可以缓解线程创建和销毁的代价,不过还是存在一些问题,线程的粒度太大。每一个线程会将一次交互操作全部处理完成,包括读取和返回甚至是连接。表面上似乎连接不在线程里面,但是如果线程不够,新连接将无法得到处理。所以线程的任务可以简化为做三件事:连接、读取、写入。

显然传统一对一的线程处理无法满足需求的变化,对此考虑使用线程池使得线程可以被复用,大大降低创建线程和销毁线程的时间。然而,线程池并不能很好满足高并发线程的需求。当海量请求抵达时线程池中的工作线程达到饱和状态,此时可能就导致请求被抛弃,无法完成客户端的请求。对此,考虑到将一次完整的请求切分为几个小的任务,每个小任务都是非阻塞的,对于读写操作使用NIO非阻塞IO对其进行读写,不同的任务将被分配到与之关联的处理程序上进行处理,每个处理器通过异步回调机制来实现。这样可以大大提高系统吞吐量,减少响应时间。

由于线程同步的粒度太大限制了吞吐量,所以应该将一次连接操作拆分为更细的粒度或过程,这些更细的粒度则是更小的线程。这样做之后,整个线程池中线程的数量将会翻倍增加,但线程更加简单且任务更为单一。这也是Reactor出现的原因。

在Reactor中这些被拆分的小线程或子过程对应的处理程序,每一种处理程序会去处理一种事件。Reactor中存在一个全局管理者Selector,开发者需要将Channel注册到感兴趣的事件上,Selector会不断在Channel上检测是否有该类型的事件发生,如果没有主线程会被阻塞,否则会调用相应的事件处理函数来处理。

由于典型的事件包括连接、读取、写入,因此需要为这些事件分别提供对应的处理程序,每个处理程序可以采用线程的方式实现。一旦连接来了,而且显示被读取线程或处理程序处理了,则会再执行写入。那么之前的读取就可以被后面的请求复用,因此吞吐量就提高了。

传统的thread per connection中线程在真正处理请求之间是需要从socket中读取网络请求,由于读取完成之前线程本身是被阻塞的不能做任何事情,这就导致线程资源被占用,而线程资源本身很珍贵的,尤其是在处理高并发请求时。Rector模式指出在等待IO时,线程可以先退出,这样就会因为有线程等待IO而占用资源。但是这样原先的执行流程就没法还原了。因此可以利用事件驱动的方式,要求线程在退出之前向event loop事件循环中注册回调函数,这样IO完成时event loop事件循环就可以调用回调函数完成剩下的操作。所以Reactor模式通过减少服务器的资源消耗提供并发能力。

细分

Reactor从线程池和Reactor的选择上可细分为:Reactor单线程模型、Reactor多线程模型,Reactor主从模型

单线程Reactor模型

单线程的Reactor模式对于客户端的所有请求使用一个专门的线程去处理,这个线程无限循环地监听是否有客户端的请求抵达,一旦收到客户端的请求,就将其分发给响应处理程序进行处理。

事件驱动设计

采用基于事件驱动的设计,当有事件触发时才会调用处理器进行数据处理。使用Reactor模式可以对线程的数量进行控制,可以使用一个线程去处理大量的事件。

-Reactor 负责响应IO事件,当检测到一个新的事件会将其发送给相应的处理程序去处理。

Handler 负责处理非阻塞的行为,标识系统管理的资源,同时将处理程序与事件绑定。

Each handler may be started in its own thread

Reactor是单个线程,需要处理accept连接,同时发送请求到处理器中。由于只是单个线程,所以处理器中的业务需要能够快速处理完毕。

单线程的Reactor与NIO流程类似,只是将消息相关处理独立到Handler中。虽然NIO中一个线程可以支持所有的IO处理,但瓶颈也是显而易见的。如果某个客户端多次进行请求时在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢。所以需要引入Reactor多线程模型。

单线程的Reactor的特点是只有一个Reactor线程,也就是说只有一个Selector事件通知器,因此字节的读取I/O和后续的业务处理process()均由Reactor线程来做,很显然业务的处理影响后续事件的分发,所以引出多线程版本进行优化。

从性能角度来看,单线程的Reactor没有过多的提升空间,因为IO和CPU的速度严重不匹配。

单线程的Reactor模式并没有解决IO和CPU处理速度不匹配问题,所以多线程的Reactor模式引入了线程池的概念,将耗时的IO操作交由线程池处理,处理完毕后再同步到selectionkey中。

多线程Reactor模型

考虑到工作线程的复用,可以将工作线程设计线程池。将处理器的执行放入线程池,并使用多线程处理业务逻辑,Reactor仍然是单个线程。

使用多线程处理业务逻辑

Reactor读线程模型是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程。客户端的请求会被直接丢到线程池中,因此不会发生堵塞。

多线程的Reactor的特点是一个Reactor线程和多个处理线程,将业务处理即process交给线程池进行了分离,Reactor线程只关注事件分发和字节的发送和读取。需要注意的是,实际的发送和读取还是由Reactor来处理。当在高并发环境下,有可能会出现连接来不及接收。

当用户进一步增加时Reactor也会出现瓶颈,因为Reactor既要处理IO操作请求也要响应连接请求。为了分担Reactor的负担,可以引入主从Reactor模型。

主从Reactor模型

主从Reactor模型

对于多个CPU的机器,为了充分利用系统资源会将Reactor拆分为两部分。

Main Reactor 负责监听连接,将accept连接交给Sub Reactor处理,主Reactor用于响应连接请求。Sub Reactor 处理accept连接,从Reactor用于处理IO操作请求。

多处理器

主从Reactor的特点是使用 一个Selector池,通常有一个主Reactor用于处理接收连接事件,多个从Reactor处理实际的IO。整体来看,分工合作,分而治之,非常高效。

为什么需要单独拆分一个Reactor来处理监听呢?

因为像TCP这样需要经过3次握手才能建立连接,这个建立的过程也是需要消耗时间和资源的,单独拆分一个Reactor来处理,可以提高性能。

优缺点

Reactor模式的核心是解决多请求问题,如果有特别多的请求同时发生,不会因为线程池被短时间占满而拒绝服务。一般实现多请求的模块,会采用线程池的实现方案,这种方案对于并发量不是特别大的场景是足够用的,比如单机TPS 1000以下都是够用的。

线程池方案的最大缺点是:如果瞬间有大并发,则会一下子耗满线程,整个服务将会陷入阻塞中,后续请求无法介入。基于Reactor模式实现的方案,会有一个Dispatcher先接收事件event,然后快速分发给相应的耗时eventHandler处理器去处理,这样就不会阻塞请求的接收。

Reactor优缺点

Reactor模式的优点是什么呢?

响应快,不为单个同步时间所阻塞,虽然Reactor自身依然是同步的。编程相对简单,可以最大程度的避免复杂的多线程以及同步问题和多线程以及多进程的切换开销。可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源。可复用性, Reactor框架本身与具体事件处理逻辑无关,具有很高的复用性。

Reactor模式的缺点是什么呢?

相比传统的模型,Reactor增加了一定的复杂性,因而具有一定的门槛,并且不易于调试。Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持。Reactor模式在IO读写数据时会在同一线程中实现,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间。例如在大文件传输时,IO操作会影响其他客户端的时间,因而对于这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或者采用Proactor模式。

下面是用C++实现Reator模式实现的回射服务器

EventHandler.h

#ifndef EVENT_HANDLER_H_ #define EVENT_HANDLER_H_ typedef int Handler; class EventHandler { #endif // !EVENT_HANDLER_H_ public: EventHandler() {} virtual ~EventHandler() {} virtual Handler getHandler() = 0; virtual void handleRead() = 0; virtual void handleWirte() = 0; virtual void handleError() = 0; };

Poller.h

#ifndef POLLER_H_H_ #define POLLER_H_H_ #include <map> #include "EventHandler.h" class Poller { public: Poller() {} virtual ~Poller() {} virtual int waitEvent(std::map<Handler, EventHandler*>& handlers, int timeout = 0) = 0; virtual int regist(Handler handler) = 0; virtual int remove(Handler handler) = 0; private: }; #endif // !POLLER_H_H_

Dispatcher.h

#ifndef DISPATHER_H_H_ #define DISPATHER_H_H_ #include "EventHandler.h" #include "Poller.h" #include <map> class Dispatcher { public: static Dispatcher* getInstance() { return instance; } int registHandler(EventHandler* handler); void removeHander(EventHandler* handler); void dispatchEvent(int timeout = 0); private: //单例模式 Dispatcher(); ~Dispatcher(); //只声明不实现,用于禁止拷贝构造和赋值构造 Dispatcher(const Dispatcher&); Dispatcher& operator=(const Dispatcher&); private: Poller* _poller; std::map<Handler, EventHandler*> _handlers; private: static Dispatcher* instance; }; #endif // !DISPATHER_H_H_

Dispatcher.cpp

#include "Dispatcher.h" #include "EpollPoller.h" #include <assert.h> Dispatcher* Dispatcher::instance = new Dispatcher(); Dispatcher::Dispatcher() { _poller = new (std::nothrow)epollPoller(); assert(NULL != _poller); } Dispatcher::~Dispatcher() { std::map<Handler, EventHandler*>::iterator iter = _handlers.begin(); for (; iter != _handlers.end(); ++iter) { delete iter->second; } if (_poller) delete _poller; } int Dispatcher::registHandler(EventHandler* handler) { Handler h = handler->getHandler(); if (_handlers.end() == _handlers.find(h)){ _handlers.insert(std::make_pair(h, handler)); } return _poller->regist(h); } void Dispatcher::removeHander(EventHandler* handler) { Handler h = handler->getHandler(); std::map<Handler, EventHandler*>::iterator iter = _handlers.find(h); if (iter != _handlers.end()) { delete iter->second; _handlers.erase(iter); } _poller->remove(h); } void Dispatcher::dispatchEvent(int timeout) { _poller->waitEvent(_handlers, timeout); }

ListenHandler.h

#ifndef LISTEN_HANDLER_H_ #define LISTEN_HANDLER_H_ #include "EventHandler.h" class ListenHandler : public EventHandler { public: ListenHandler(Handler fd); ~ListenHandler(); Handler getHandler() { return listenFd; } void handleRead(); void handleWirte(); void handleError(); private: Handler listenFd; }; #endif // !LISTEN_HANDLER_H_

ListenHandler.cpp

#include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <assert.h> #include <iostream> #include "Dispatcher.h" #include "ClientHandler.h" #include "ListenHandler.h" ListenHandler::ListenHandler(Handler fd) : listenFd(fd) { } ListenHandler::~ListenHandler() { close(listenFd); } void ListenHandler::handleRead() { struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(sockaddr_in); int connFd = ::accept(listenFd, (sockaddr*)&peeraddr, &peerlen); if (-1 == connFd) { return; } std::cout << "ip=" << inet_ntoa(peeraddr.sin_addr) << " port=" << ntohs(peeraddr.sin_port) << " fd:" << connFd << std::endl; EventHandler* h = new(std::nothrow) ClientHandler(connFd); assert(NULL != h); Dispatcher::getInstance()->registHandler(h); } void ListenHandler::handleWirte() { //nothing todo } void ListenHandler::handleError() { //nothing todo }

ClientHandler.h

#ifndef CLIENT_HANDLER_H_ #define CLIENT_HANDLER_H_ #include "EventHandler.h" class ClientHandler : public EventHandler { public: ClientHandler(Handler fd); ~ClientHandler(); Handler getHandler() { return clientFd; } virtual void handleRead(); virtual void handleWirte(); virtual void handleError(); private: Handler clientFd; char revBuff[1024]; }; #endif // !CLIENT_HANDLER_H_

ClientHandler.cpp

#include <unistd.h> #include <sys/socket.h> #include <stdio.h> #include <assert.h> #include <iostream> #include<string.h> #include "ClientHandler.h" ClientHandler::ClientHandler(Handler fd) : clientFd(fd) { memset(revBuff, 0, sizeof(revBuff)); } ClientHandler::~ClientHandler() { close(clientFd); } void ClientHandler::handleRead() { if (read(clientFd, revBuff, sizeof(revBuff))) { std::cout <<"recv client:"<<clientFd<<":"<< revBuff << std::endl; write(clientFd, revBuff, strlen(revBuff)); memset(revBuff, 0, sizeof(revBuff)); } else { std::cout << "client close fd:" << clientFd << std::endl; close(clientFd); } } void ClientHandler::handleWirte() { //nothing todo } void ClientHandler::handleError() { //nothing todo std::cout << "client close:" << clientFd << std::endl; }

EpollPoller.h

#ifndef EPOLL_POLLER_H_ #define EPOLL_POLLER_H_ #include <unistd.h> #include <sys/epoll.h> #include <vector> #include "Poller.h" class epollPoller : public Poller { public: epollPoller(); ~epollPoller(); int waitEvent(std::map<Handler, EventHandler*>& handlers, int timeout = 0); int regist(Handler handler); int remove(Handler handler); private: typedef std::vector<struct epoll_event> EventList; EventList _events; int epollFd; }; #endif // !EPOLL_POLLER_H_

EpollPoller.cpp

#include <iostream> #include <errno.h> #include <assert.h> #include "EpollPoller.h" typedef std::vector<struct epoll_event> EventList; epollPoller::epollPoller() : _events(16), epollFd(::epoll_create(100000)) { } epollPoller::~epollPoller() { close(epollFd); } int epollPoller::waitEvent(std::map<Handler, EventHandler*>& handlers, int timeout) { std::cout << "start wait"<< std::endl; int nready = epoll_wait(epollFd, &_events[0], static_cast<int>(_events.size()), -1); if (-1 == nready) { std::cout << "WARNING: epoll_wait error " << errno << std::endl; return -1; } for (int i = 0; i < nready; i++) { Handler handle = _events[i].data.fd; if ((EPOLLHUP | EPOLLERR) & _events[i].events) { assert(handlers[handle] != NULL); (handlers[handle])->handleError(); } else { if ((EPOLLIN)& _events[i].events) { assert(handlers[handle] != NULL); (handlers[handle])->handleRead(); } if (EPOLLOUT & _events[i].events) { (handlers[handle])->handleWirte(); } } } if (static_cast<size_t>(nready) == _events.size()) { _events.resize(_events.size() * 2); } return nready; } int epollPoller::regist(Handler handler) { struct epoll_event ev; ev.data.fd = handler; ev.events |= EPOLLIN; ev.events |= EPOLLET; if (epoll_ctl(epollFd, EPOLL_CTL_ADD, handler, &ev) != 0) { if (errno == ENOENT) { if (epoll_ctl(epollFd, EPOLL_CTL_ADD, handler, &ev) != 0) { std::cout << "epoll_ctl add error " << errno << std::endl; return -1; } } } return 0; } int epollPoller::remove(Handler handler) { struct epoll_event ev; if (epoll_ctl(epollFd, EPOLL_CTL_DEL, handler, &ev) != 0) { std::cout << "epoll_ctl del error" << errno << std::endl; return -1; } return 0; }

main.cpp

#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <iostream> #include <errno.h> #include "Dispatcher.h" #include "ListenHandler.h" #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) ERR_EXIT("socket error"); int fd_flags = ::fcntl(listenfd, F_GETFL, 0); fd_flags |= FD_CLOEXEC; fd_flags |= O_NONBLOCK; fcntl(listenfd, F_SETFD, fd_flags); struct sockaddr_in seraddr; seraddr.sin_family = AF_INET; seraddr.sin_port = htons(5188); seraddr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) < 0) ERR_EXIT("bind"); if (listen(listenfd, 5) < 0) ERR_EXIT("listen"); EventHandler* handler = new ListenHandler(listenfd); Dispatcher::getInstance()->registHandler(handler); while (true) { Dispatcher::getInstance()->dispatchEvent(1000); } return 0; }

这个程序只是一个简单例子,同步处理I/O,效率不是很高。如果实际开发中,还有很多可以完善和优化的部分,比如自己实现I/O的接受和发送缓存区,接收到的数据首先放到接收缓冲区进行解析来处理粘包等问题,对I/O事件发送丢到线程池处理读和写的异步处理方式。比如文件描述符是一个递增的整数,可以用数组存储,下标为文件描述符,这样能提高检索效率。一般服务器会有一个事件线程,从I/O中接收的数据,封装成事件投递到事件线程去处理。为了充分利用多核引入多线程或者多进程,比如Nginx就是多线程方式实现的I/O模式,memcached是多线程的I/O模式

更多服务器架构和控制器架构(服务器产品结构设计)相关信息请关注本站,本文仅仅做为展示!