Linux 高性能服务器网络编程
参考自《高性能服务器编程》,主要用于学习网络编程模块
Linux服务器程序规范
日志
rsyslogd : 此守护进程既能接收用户进程输出的日志,又能接收内核进程。用户进程调用syslog函数生成系统日志,输出到/dev/log 中
调试信息:/var/log/debug文件
普通信息:/var/log/messages
内核信息:/var/log/kern.log
配置文件是/etc/rsyslog.conf
#include <syslog.h>
void syslog( int priority, const char *message, ...);
#priority
#include <syslog.h>
#define LOG_EMERG 0 //系统不可以
#define LOG_ALERT 1 //报警,立即采取动作
#define LOG_CRIT 2 // 非常严重的情况
#define LOG_ERR 3 // 错误
#define LOG_WARNGIN 4 // 警告
#define LOG_NOTICE 5 // 通告
#define LOG_INFO 6 // 信息
#define LOG_DEBUG 7 // 调试
#include <sys/types.h>
#include <unistd.h>
uid_t getuid(); //获取真实用户ID
uid_t geteuid(); //获取有效用户ID
uid_t getgid(); //获取真实组ID
uid_t getegid(); //获取有效组ID
守护进程代码例子
bool daemonize()
{
pid_t pid = fork();
if ( pid < 0 )
{
return false;
}
else if ( pid > 0 )
{
exit( 0 );
}
umask( 0 );
pid_t sid = setsid();
if ( sid < 0 )
{
return false;
}
if ( ( chdir( "/" ) ) < 0 )
{
/* Log the failure */
return false;
}
close( STDIN_FILENO );
close( STDOUT_FILENO );
close( STDERR_FILENO );
open( "/dev/null", O_RDONLY );
open( "/dev/null", O_RDWR );
open( "/dev/null", O_RDWR );
return true;
}
高性能服务器框架(核心)
本章主要讨论c/s框架,不考虑bs/p2p框架
基本框架
服务器功能模块描述
模块 | 单个服务器程序 | 服务器机群 |
---|---|---|
I/O处理单元 | 处理客户连接,读写网络数据 | 作为接入服务器实现负载均衡 |
逻辑单元 | 业务进程或者线程 | 逻辑服务器 |
网络存储单元 | 本地数据库,文件或者缓存 | 数据库服务器 |
请求队列 | 各单位之间的通信方式 | 各服务器之间的永久TCP连接 |
IO模型
- 阻塞IO:系统调用无法立即完成而被操作系统挂起,直到等待的事件发生为止
例如:connect函数在调用后阻塞,直到对端发生确认报文为止
可能被阻塞系统调用包括accept,send,recv - 非阻塞IO:系统调用立即返回,不管事件是否发生。如果事件没有立即发生则返回-1, 设置errno
其中对于accept, send, recv来说errno如下
设置值 | 含义 |
---|---|
EAGAIN | 再来一次 |
EWOULDBLOCK | 期望阻塞 |
对于connect errno则被设置EINPROGRESS(在处理中)
故在事件已经发生的情况下操作非阻塞才能提高效率,一般和IO复用和SIGIO信号一起使用
注:设置非阻塞方法(1)在创建sock是设置SOCK_NONBLOCK(2)使用fcntl函数
-
IO复用:应用程序通过I/O复用函数向内核注册一组事件,内核通过IO复用函数把其中就绪的事件通知应用程序。linux常用select, poll, epoll函数(后续会对这三个函数详解),IO复用本身就是阻塞的,具有同时监听多个IO事件的能力,因此能够提高效率
-
SIGIO信号:最早的IO通知机制,通过进程捕获SIGIO信号,接收SIGIO的信号处理函数触发
-
异步IO:读写操作总是立即返回的,不管I/O是否阻塞,因为真正读写操作被内核接管
高效的事件处理模式(Reactor/Proactor)
Reactor模式(同步IO模型)
要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程(逻辑处理单元),主线程不做任何实质性工作,而读写数据,处理连接都在工作线程中完成
工作流程(使用epoll_wait):
(1)主线程往epoll内核事件注册读就绪事件
(2)主线程调用epoll_wait等待有数据可读
(3)当socket有数据可读时,主线程通知主线程,主线程将socket可读事件放入请求队列
(4)激活请求队列的某个工作线程,从socket中读数据并且处理客户请求,并且向epoll事件中注册写就绪事件
(5)主线程调用epoll_wait等待有数据可写
(6)当socket上有数据可写,epoll_wait通知主线程,主线程将socket可写事件放入请求队列
(7)唤醒请求队列上的某个工作线程,往socket写入服务器处理客户端请求的结果
Proactor(异步IO模型)
将所有的IO操作都交给主线程和内核处理,工作线程仅仅只是负责业务逻辑,更加符合服务器编程框架
使用异步IO模型(aio_read 和 aio_write为例)工作流程是:
1)主线程调用aio_read函数向内核注册socket上的读完成事件,并且告诉内核缓冲区的位置,以及读操作完成是如何通知应用程序(使用sigevent)
2)主线程继续处理其他逻辑
3)当socket上的数据读入用户缓冲区后,内核将向应用程序发生一个信号,以通知应用程序数据可用
4)应用程序预先定义号信号处理函数选择一个工作线程来处理客户端请求。工作线程处理完请求调用aio_write向内核注册socket写完成事件,并且告诉内核缓冲区的位置,以及写操作完成是如何通知应用程序(使用sigevent)
5)主线程继续处理其他逻辑
6)当用户缓冲区有数据写入socket后,内核将向应用程序发生一个信号,以通知应用程序数据已经发生完毕
7)应用程序预先定义号信号处理函数选择一个工作线程来处理善后处理,比如是否关闭socket
注:epoll_wait是用来仅仅监听socket上连接请求事件,不能用来检测连接socket的读写事件
两种高效的并发模式
目的是让程序“同时”执行多个任务,并发模式是指IO处理单元和多个逻辑单元之间协调完成任务的方法。服务器主要有两种并发编程模式:半同步/半异步 和 领导者/追随者模式
半同步/半异步
IO模式中的同步和异步是应用程序向内核注册的是就绪事件还是完成事件以及IO操作是用应用程序还是内核处理。而并发模型中的同步是按照一定顺序执行;异步是执行需要系统事件来驱动,常见的事件有信号和中断
异步线程:用于处理IO请求
同步线程:用于请求服务
领导者/追随者(略)
其他提高服务性能的方式
池
以硬件资源换其运行效率。池是一组资源的集合,在服务器启动的时候就完全创建好并且初始化,这称为静态资源分配
数据复制
高性能服务器之间应该避免不必要的数据复制,尤其是数据复制发生在用户代码和内核之间。
上下文切换和锁机制
并发程序必须考虑上下文切换的问题。即进程和线程切换是产生的开销。使用多线程服务的一个优点是不同的线程可以同时运行在不同的CPU上。当线程数量不大于CPU的数量时,上下文切换就不是问题了
锁:通常被认为是服务器效率低下的一个因素。如果服务器必须要使用锁机制,可以用读写锁,当所有的工作线程读取一块共享内存时不会增加额外的系统开销,只要在每个线程进行写操作时才会锁住这块区域