Acceptor 用于 accept 一个 TCP 连接,accept 接受成功后通知 TCP 连接的使用者。Acceptor 主要是供 TcpServer 使用的,其生命期由后者控制。一个 Acceptor 相当于持有服务端的一个 socket 描述符,该 socket 可以 accept 多个 TCP 客户连接,这个 accept 操作就是 Acceptor 实现的。 这里用到了一些封装好的 socket 和地址结构,如 class InetAddress 表示 sockaddr_in 的封装,如可以通过ip地址和port端口生成一个sockaddr_in; class Socket封装了部分关于socket套接字的操作,如Socket::bindAddress(InetAddress&) 将socket和一个sockaddr_in地址绑定,Socket::accept(InetAddress& peerAddr)将一个socket允许连接一个客户端地址peerAddr,Socket::listen()监听socket,Socket::shutdownWrite 继续阅读 >>


杜肖孟 18/01/12 22:43:03
手动管理的弊端 在简单的程序中,我们不大可能忘记释放 new 出来的指针,但是随着程序规模的增大,我们忘了 delete 的概率也随之增大。在 C++ 中 new 出来的指针,赋值意味着引用的传递,当赋值运算符同时展现出“值拷贝”和“引用传递”两种截然不同的语义时,就很容易导致“内存泄漏”。 手动管理内存带来的更严重的问题是,内存究竟要由谁来分配和释放呢?指针的赋值将同一对象的引用散播到程序各处,但是该对象的释放却只能发生一次。当在代码中用完了一个资源指针,该不该释放 delete 掉它?这个资源极有可能同时被多个对象拥有着,而这些对象中的任何一个都有可能在之后使用该资源,其余指向这个对象的指针就变成了“野指针”;那如果不 delete 呢?也许你就是这个资源指针的唯一使用者,如果你用完不 delete,内存就泄漏了。 资源的拥有者是系统,当我们需要时便向系统申请资源,当我们不需要时就让系统自己收回去(Garbage Collection)。当我们自己处理的时候,就容易出现各种各样的问题。 C++ 中的智能指针 为了让用户免去手动 delete 资源的烦恼,不少类库采用了 继续阅读 >>


杜肖孟 18/01/11 23:39:30
主要涉及到的类和实现文件有: Endian.h 提供了字节序转换的函数。 Socket.h/Socket.cc socketfd 的封装,提供了绑定地址、开始listen、接受连接等操作,并可设置套接字选项。 InetAddress.h/InetAddress.cc 套接字地址的封装,提供了多种方式初始化一个地址,还提供方法从地址中拿到 ip 和 port。 SocketsOps.h/SocketsOps.cc 封装了 socket 相关的一些操作,提供给 Socket 和 InetAddress 用。 这部分就是基本的 TCP 套接字编程和套接字选项的知识,代码逻辑也很简单,推荐看下 UNP卷一 的相关章节。 下面逐一看下这几个相关的文件。 字节序转换部分(Endian.h) #ifndef MUDUO_NET_ENDIAN_H #define MUDUO_NET_ENDIAN_H #include <stdint.h> #include <endian.h> namespace muduo { namespace net { na 继续阅读 >>


杜肖孟 18/01/09 15:14:11
eventfd 介绍 Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的。 接口 #include <sys/eventfd.h> int eventfd(unsigned int initval, int flags); 这个函数会创建一个事件对象(eventfd object),返回一个文件描述符,用来实现进程或线程间的等待/通知(wait/notify)机制。内核为这个对象维护了一个无符号的64位整形计数器 counter,用第一个参数(initval)初始化这个计数器,创建时一般可将其设为0,后面有例子测试这个参数产生的效果。 flags 可以使用三个宏: EFD_CLOEXEC:给这个新的文件描述符设置 FD_CLOEXEC 标志,即 close-on-exec,这样在调用 exec 后会自动关闭文件描述符。因为通常执行另一个程序后,会用全新的程序替换子进程的正文,数据,堆和栈等,原来的文件描述符变量也不存在了,这样就没法关闭没用的文件描述符了。 EFD_NONBLOCK:设置文件描述符为 继续阅读 >>


杜肖孟 18/01/08 23:22:55
muduo 的定时器功能由三个 class 实现,TimerId、Timer 和 TimerQueue。 TimerId 类 它唯一标识一个 Timer 定时器。TimerId Class 同时保存Timer* 和 sequence_,这个 sequence_ 是每个 Timer 对象有一个全局递增的序列号 int64_t sequence_,用原子计数器(AtomicInt64)生成。 它主要用于注销定时器,这样就可以区分地址相同的先后两个 Timer 对象。 namespace muduo { namespace net { class Timer; /// /// An opaque identifier, for canceling Timer. /// /* 带有唯一标识的Timer,主要用于取消Timer */ class TimerId : public muduo::copyable { public: TimerId() : timer_(NULL), sequence_(0) { } TimerId(Timer* 继续阅读 >>


杜肖孟 18/01/07 15:41:05
本文分析一下Reactor模式的实现,关键是三个类:Channel、Poller、EventLoop。 事件分发类 Channel Channel 是 selectable IO channel,负责注册与响应IO事件,包括注册给Poller的 fd 及其监听的事件,以及事件发生了所调的回调函数。 每个Channel对象自始至终只负责一个 fd 的事件分发,封装了一系列该 fd 对应的操作,使用了回调函数,包括可读、可写、关闭和错误处理四个。 首先给定Channel所属的 loop,及其要处理的 fd;接着注册 fd 上需要监听的事件,如果是常用的读写事件的话,可以直接调用接口函数enableReading或enableWriting来注册对应fd上的事件,disable*是销毁指定的事件;然后通过 set*Callback 来设置事件发生时的回调。 注册事件时函数调用关系,如下:Channel::update()->EventLoop::updateChannel(Channel*)->Poller::updateChannel(Channel*),最终向 继续阅读 >>


杜肖孟 18/01/06 18:36:02
考虑让我的 web server 增加对 PHP 的支持,这就要用到 php 解析器来将客户端请求的 php 文件解析为静态资源,再由我的 web server 将其返回到客户端,php-fpm 就可以来帮我们完成这个工作。可是我的 web server 如何与 php-fpm 通信呢? 接下来就是本文的主角:FastCGI 。 FastCGI 实现与测试代码:https://github.com/Tanswer/FastCGI Web Server 项目地址:https://github.com/Tanswer/Xserver Description C language through FastCGI protocol, through php-fpm, php file parsed into html files. How to use Preparation: please make sure you have installed php-fpm. Testing environment:CentOS 7 configuration 继续阅读 >>


杜肖孟 17/12/23 14:46:54
32 位的平台上,线性地址空间为固定的 4GB,并且由于采用了保护机制,Linux内核将这 4GB 分为两部分,线性地址较高的 1GB(0xC0000000 到 0xFFFFFFFF )为共享的内核空间;而较低的 3GB 为每个进程的用户空间。由于每个进程都不能直接访问内核空间,而是通过系统调用间接进入内核,因此所有的进程都共享内核空间。而每个进程都拥有各自的用户空间,各个进程之间不能互相访问彼此的用户空间。 一个进程的用户地址空间主要由两个数据结构来描述。一个是 mm_struct 结构,它对进程的整个用户空间进行描述,简称内存描述符;另一个是 vm_area_struct 结构,它对用户空间中各个区间( 代码区、数据区等 )进行描述。 进程用户空间的描述 内存描述符 每个进程只有一个 mm_struct 结构,在每个进程的 task_struct 结构中,有一个指向该结构的指针。 struct mm_struct { struct vm_area_struct *mmap; /* list of VMAs * 继续阅读 >>


杜肖孟 17/12/10 14:47:55
链接器之所以存在或者产生,基本上是由于程序开发的模块化。这里讲的模块,主要是编译概念上的模块,通常他们按照功能划分,比如一个.c或者.cpp文件就是一个编译单元,就是一个模块,编译后就产生一个.o目标文件。为了最终生成一个可执行文件、静态库或者动态库,就需要把各个编译单元按照特定的约定组合到一起。这里特定的约定指的就是“目标文件格式”,它定义了目标文件、库文件和可执行文件的格式,这里组合这一过程就叫做链接。 链接主要有以下几个任务:空间与地址分配,符号解析,重定位,昨天晚上回宿舍躺床上后,一直在想这几个过程,哪个先进行,哪个后进行,试图从目标文件开始理清链接进行的顺序,知直到生成可执行文件。但是我失败了,索性不管了,我大概清楚这几个过程都做了什么工作就好,如果纠结的失眠了第二天就起不来了=_=。用到的样例代码 在文末。 空间与地址分配 主流操作系统中,可执行文件都是基于虚拟地址空间的,即每个可执行文件都有相同且独立的地址空间,并且文件中各个段(代码段,数据段,以及进程空间中的堆栈段)都有相似的布局。在 Linux 下,ELF 可执行文件默认从地址 0x0 继续阅读 >>


杜肖孟 17/12/06 15:38:25
编译器编译源代码后生成的文件叫做目标文件,从结构上讲它是已经编译后的可执行文件格式,只是还没有经过链接,其中可能有些符号或有些地址还没有调整。它本身就是按照可执行文件格式存储的,跟真正的可执行文件在结构上稍有不同。 目标文件的格式: 现在 PC 平台流行的可执行文件格式主要是 Windows 下的 PE( Portable Executable,可移植可执行 ) 和 Linux 的 ELF( Executable and Linkable Format,可执行链接格式 ),它们都是 COFF (Common Object Fifle Format,一般目标文件格式 )格式的变种。从广义上看,目标文件和可执行文件的格式其实几乎是一样的,在 Linux 下,我们可以将他们统称为 ELF 文件;在 Windows 下可以统称为 PE-COFF 文件格式。 在 Linux 下使用 file 命令查看相应的文件格式: 目标文件是什么样的 目标文件是什么样的呢? 下面我们通过一个具体的例子来看一下。如不加说明,以下分析都是基于 32 位 x86 继续阅读 >>


杜肖孟 17/12/04 20:58:14