muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码,如果你对我之前的博客有兴趣,可以点击下面的连接:
muduo网络库源码复现笔记(一):base库的Timestamp.h
muduo网络库源码复现笔记(二):base库的Atomic.h
muduo网络库源码复现笔记(三):base库的Exception.h
muduo网络库源码复现笔记(四):base库的Thread.h和CurrentThread.h
muduo网络库源码复现笔记(五):base库的Mutex.h和Condition.h和CoutntDownLatch.h
muduo网络库源码复现笔记(六):base库的BlockingQueue.h和BoundedBlockingQueue.h
muduo网络库源码复现笔记(七):base库的ThreadPool.h
muduo网络库源码复现笔记(八):base库的Singleton.h
muduo网络库源码复现笔记(九):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十一):base库的StringPiece.h
muduo网络库源码复现笔记(十二):base库的LogStream.h
muduo网络库源码复现笔记(十三):base库的Logging.h
muduo网络库源码复现笔记(十四):base库的FileUtil.h
ProcessInfo.h里面定义实现了一些关于读取进程信息的函数。在讲解具体代码之前,让我们复习一下有关的知识。
许多现代的unix系统实现提供了一个/proc虚拟文件系统。这个文件系统驻留于/proc目录,并包含了各种用于展示内核信息的文件,并允许进程通过常规I/O系统读取。需要注意的是/proc下保存的文件与子目录并非存储与磁盘,而是由内核在进程访问此类信息时动态创建的。
对于系统中每个进程,内核提供了相应的目录,命名为/proc/PID,其中PID是进程ID。此目录下的文件与子目录包含了进程的相关信息。如查看/proc/1目录下文件可以查看init进程的信息。
/proc/PID/fd目录为进程打开的每个文件描述符包含了一个符号连接,每个符号连接的名称与描述符的数值相匹配。每个进程可以通过/proc/self来访问自己的/proc/PID目录。
Linux2.4增加了线程组的概念。线程组中一些属性对于线程是唯一的,所以Linux2.4在/proc/PID目录下增加了task子目录。针对进程中每个线程,内核提供/proc/PID/task/TID命名的子目录(TID是线程id)。
通常使用shell脚本来访问/proc目录下的文件,也可以使用I/O系统,但注意许多文件是只读的。
使用函数getpwnam()和getpwuid()的作用是从密码文件中获取记录。
struct passwd *getpwnam(const char *name)
struct passwd *getpwuid(uid_t uid)
参数name是一个登录名,getpwnam()会返回一个指针,指向如下类型结构
struct passwd
{
char * pw_name; /* Username, POSIX.1 */
char * pw_passwd; /* Password */
__uid_t pw_uid; /* User ID, POSIX.1 */
__gid_t pw_gid; /* Group ID, POSIX.1 */
char * pw_gecos; /* Real Name or Comment field */
char * pw_dir; /* Home directory, POSIX.1 */
char * pw_shell; /* Shell Program, POSIX.1 */
};
在ProcessInfo.h头文件中定义了一些处理进程信息的函数如下,函数作用如注释所示。下面讲解其中几个函数
namespace ProcessInfo
{
pid_t pid();//返回进程pid
string pidString();//将进程pid字符串化
uid_t uid();//返回uesrId
string username();//返回用户姓名
uid_t euid();
Timestamp startTime();//时间戳
string hostname();
//read /proc/self/status
string procStatus();//读取进程状态
int openedFiles();//统计进程打开的文件数目
int maxOpenFiles();
int numThreads();
std::vector threads();
}
这个函数用于查询进程允许打开的最大文件数。结构体rlimit是描述资源软硬限制的结构体。rlim_cur是软上限,rlim_max是硬上限。软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
getrlimit函数调用成功的话,返回零,则返回软上限;否则调用openedFiles()返回目前打开的文件数目,下面说一下openedFiles()。
int ProcessInfo::maxOpenFiles()
{
struct rlimit rl;
if(::getrlimit(RLIMIT_NOFILE,&rl))
{
return openedFiles();
}
else
{
return static_cast(rl.rlim_cur);
}
}
这个函数用于统计打开文件的数目,它的关键是被封装的scanDir函数。t_numOpenedFiles是一个全局变量,也是线程局部存储数据。
int ProcessInfo::openedFiles()
{
t_numOpenedFiles = 0;
scanDir("/proc/self/fd",fdDirFilter);
return t_numOpenedFiles;
}
scanDir的实现如下,struct dirent为了获取某文件夹目录内容,所使用的结构体。filter是一个函数指针,起到过滤作用,alphasort用于排序。
int scanDir(const char *dirpath,int (*filter)(const struct dirent *))
{
struct dirent** namelist = NULL;
int result = ::scandir(dirpath,&namelist,filter,alphasort);
assert(namelist == NULL);
return result;
}
关于filter可以自己定义,比如在openedFiles()用了fdDirFilter来获取/proc/self/fd下目录的个数。由于fd下的目录均以数字打头,我们可以定义
fdDirFilter:
int fdDirFilter(const struct dirent* d)
{
if(::isdigit(d->d_name[0]))
{
++t_numOpenedFiles;
}
return 0;
}