C/S模型
传统C/S,一端作为客户端,一端作为服务器,这里不做多介绍。
P2P模型
peer 2 peer ,每台机器使用服务的同时也提供服务,通俗的讲,没有绝对客户端和服务端的概念,当下云计算的模型就是这样实现的,但他具有一个缺点,就是难以被发现,所以通常加上一个发现服务器。
服务器编程框架
通常分为四块,
IO处理模块
若是单个服务器,用于处理用户链接和收发
若是服务器集群,作为传入服务器,实现负载均衡
逻辑处理单元
若是单个服务器,线程或进程形式存在
若是服务器集群,逻辑服务器形式存在
网络存储单元
若是单个服务器,本地数据库文件或者缓存
若是服务器集群,数据库服务器
请求队列
若是单个服务器,各个单元通信
若是服务器集群,永久的TCP
IO模型
阻塞I/O
阻塞读写函数
I/O复用
I/O服用也是阻塞的,但可以同时监视多个I/O,对于I/O本身是非阻塞的
SIGIO信号
信号触发读写事件,用于用户的读写操作。程序没有阻塞阶段
异步IO
内核执行读写操作并触发读写事件的完成,程序没有阻塞阶段
两种高效的事件处理模式
服务器通常要处理的三件事情,I/O事件、信号、定时器
Reactor:有数据来了通知我(主线程),然后唤醒工作线程执行读写。
1.主线程往epoll内核事件表注册socket上的读就绪事件
2.调用epoll_wait等待socket的读写事件
3.当socket上的数据可读时候,epoll_wait通知主线程,主线程将socket刻度事件放入请求队列;
4.睡眠在请求队列的某个工作线程被唤醒,它从socket读取数据,并处理客户的请求,然后往epoll内核这侧该socket的写就绪事件‘
5.主线程继续调用sokcet的可写;
6.可写的情况也是一样的
Proactor:你(内核)给我取10字节数据,取完了通知我。
1.主线程调用aio_read函数向内核注册读写事件,并告诉内核用户缓冲区的位置。以及读写操作时如何通知应用程序
2.主线程继续处理其他逻辑
3.当socket上数据被读入到用户缓冲区时候,内核向应用程序发一个信号,已通知应用程序有数据可用
4.应用程序预先定义好的信号函数选择一个工作线程去处理。工作线程处理用户请求后,调用aio_write函数注册内核写事件,并告诉缓冲区的位置
5.当内核处理完毕时候向主线程发送一个信号,通知其处理完毕
7.主线程进行后续事件的处理,如关闭当前socket
模拟proactor模式
两种高效的并发模式
半同步和半异步模式
异步线程只有一个,就是主线程,负责监听链接,再把读写事件放到epoll内核事件
剩下的是同步线程,主要负责用户数据的处理;
衍生的是版反应堆模型,用一个队列维护被封装成对象;
会出现,加锁效率低,同一时间只能处理一个客户请求的问题;
而半同步半异步是在主线程收到链接后,将注册epoll读写的事件放到工作线程里,这样就可以在同一时间处理大量请求;
领导者和追随者
由四个部分组成,句柄集,线程集,事件处理器,具体事件处理器
句柄集,负责I/0资源,管理多个句柄,使用wait_for_event()来监听IO,并将其中的就绪事件通知给领导者,领导者用绑定好句柄的事件处理器来处理事件
线程集,有三种角色,分别是领导者、追随者、正在处理事件的线程
事件处理器,拥有多个回调函数,绑定句柄后,当有新的事件发生时候,调用回调函数。
具体事件处理器,是事件处理器的派生类
说白了就是:
大哥在的时候,大哥当家。负责家里的管理;
大哥不在的时候(大哥执行任务),二哥当家,负责家里的管理;
依次类推,增加执行效率;
优点是,不需要线程之间的数据交互,减少cpu切换线程所花费的时间;
缺点是,一个线程只能处理一个socket的io事件;
有限状态机
while (state != STATE_E)
{
switch( state)
{
case STATE_A:
process_A();
state = STATE_B;
break;
case STATE_B:
process_B();
state = STATE_D;
break;
case STATE_C:
process_C();
state = STATE_E;
break;
case STATE_D:
process_D();
state = STATE_E;
break;
}
case STATE_E:
process_E();
break;
}
提高服务器性能的其他建议
除了上述提的外,还可以用池子,减少数据复制,减少上下问的切换和锁;