Webserver笔记

三个主要的类EventLoop、Channel、Poller

EventLoop
// 如果当前线程就是创建此EventLoop的线程,就调用callback,否则就放入等待执行函数区
void EventLoop::runInLoop(Functor&& cb) {
  if (isInLoopThread())
    cb();
  else
    queueInLoop(std::move(cb));
}

// 将此函数放入等待执行函数区,如果当前是跨线程or正在调用等待的函数,则唤醒事件循环
void EventLoop::queueInLoop(Functor&& cb) {
  {
    MutexLockGuard lock(mutex_);
    pendingFunctors_.emplace_back(std::move(cb));
  }

  if (!isInLoopThread() || callingPendingFunctors_) wakeup();
}

// 关键函数,首先从poll中取出所有的事件,并依次处理,再执行等待的函数,再处理超时的函数
void EventLoop::loop() {
  assert(!looping_);
  assert(isInLoopThread());
  looping_ = true;
  quit_ = false;
  // LOG_TRACE << "EventLoop " << this << " start looping";
  std::vector<SP_Channel> ret;
  while (!quit_) {
    // cout << "doing" << endl;
    ret.clear();
    ret = poller_->poll();
    eventHandling_ = true;
    for (auto& it : ret) it->handleEvents();
    eventHandling_ = false;
    doPendingFunctors();
    poller_->handleExpired();
  }
  looping_ = false;
}

wakeup函数是手动触发写事件,对应的是wakeupFd_描述符

Channel
 private:
  typedef std::function<void()> CallBack;
  EventLoop *loop_;
  int fd_;
  __uint32_t events_;
  __uint32_t revents_;
  __uint32_t lastEvents_;

  // 方便找到上层持有该Channel的对象
  std::weak_ptr<HttpData> holder_;
  
每个Channel持有一个文件描述符fd,正在监听(感兴趣的)事件,实际发生的事件,以及各个事件的回调函数的Function对象

总的来说,Channel就是对fd事件的封装,包括注册它的事件以及回调。EventLoop通过调用Channel::handleEvent()来执行Channel的读写事件。Channel::handleEvent()的实现也很简单,就是比较以已经发生的事件(由Poller返回),来调用对应的回调函数(读、写、错误)
Poller
class Epoll {
 public:
  Epoll();
  ~Epoll();
  void epoll_add(SP_Channel request, int timeout);
  void epoll_mod(SP_Channel request, int timeout);
  void epoll_del(SP_Channel request);
  std::vector<std::shared_ptr<Channel>> poll();
  std::vector<std::shared_ptr<Channel>> getEventsRequest(int events_num);
  void add_timer(std::shared_ptr<Channel> request_data, int timeout);
  int getEpollFd() { return epollFd_; }
  void handleExpired();

 private:
  static const int MAXFDS = 100000;
  int epollFd_;
  std::vector<epoll_event> events_;
  std::shared_ptr<Channel> fd2chan_[MAXFDS];
  std::shared_ptr<HttpData> fd2http_[MAXFDS];
  TimerManager timerManager_;
};

Poller的主要成员变量就三个
    epollFd_:epoll_create方法返回的epoll句柄
    events_:存放epoll_wait()方法返回的活动事件。是一个vector
    fd2chan_:存放每个Channel

// 分发处理函数
std::vector<SP_Channel> Epoll::getEventsRequest(int events_num) {
  std::vector<SP_Channel> req_data;
  for (int i = 0; i < events_num; ++i) {
    // 获取有事件产生的描述符
    int fd = events_[i].data.fd;

    SP_Channel cur_req = fd2chan_[fd];

    if (cur_req) {
      cur_req->setRevents(events_[i].events);
      cur_req->setEvents(0);
      // 加入线程池之前将Timer和request分离
      // cur_req->seperateTimer();
      req_data.push_back(cur_req);
    } else {
      LOG << "SP cur_req is invalid";
    }
  }
  return req_data;
}

上述函数最关键,其余的函数无非就是epoll add 、mod、del的封装

Server类

Server.cpp

这里处理新连接,感觉应该先把while放在开头。。。while循环结束以后,还得acceptChannel_->setEvents(EPOLLIN | EPOLLET);是因为,使用的是epoll边沿模式,只有事件状态改变了才会有通知!!!所以得重新设置一下,不然再有新连接都不会通知了。

void Server::handNewConn() {
  struct sockaddr_in client_addr;
  memset(&client_addr, 0, sizeof(struct sockaddr_in));
  socklen_t client_addr_len = sizeof(client_addr);
  int accept_fd = 0;
  while ((accept_fd = accept(listenFd_, (struct sockaddr *)&client_addr,
                             &client_addr_len)) > 0) {
    EventLoop *loop = eventLoopThreadPool_->getNextLoop();
    LOG << "New connection from " << inet_ntoa(client_addr.sin_addr) << ":"
        << ntohs(client_addr.sin_port);
    // cout << "new connection" << endl;
    // cout << inet_ntoa(client_addr.sin_addr) << endl;
    // cout << ntohs(client_addr.sin_port) << endl;
    /*
    // TCP的保活机制默认是关闭的
    int optval = 0;
    socklen_t len_optval = 4;
    getsockopt(accept_fd, SOL_SOCKET,  SO_KEEPALIVE, &optval, &len_optval);
    cout << "optval ==" << optval << endl;
    */
    // 限制服务器的最大并发连接数
    if (accept_fd >= MAXFDS) {
      close(accept_fd);
      continue;
    }
    // 设为非阻塞模式
    if (setSocketNonBlocking(accept_fd) < 0) {
      LOG << "Set non block failed!";
      // perror("Set non block failed!");
      return;
    }

    setSocketNodelay(accept_fd);
    // setSocketNoLinger(accept_fd);

    shared_ptr<HttpData> req_info(new HttpData(loop, accept_fd));
    req_info->getChannel()->setHolder(req_info);
    loop->queueInLoop(std::bind(&HttpData::newEvent, req_info));
  }
  acceptChannel_->setEvents(EPOLLIN | EPOLLET);
}

LogStream.h

// 返回data_ char数组的数据末尾地址
const char* end() const { return data_ + sizeof data_; }

Logging.cpp

//定义一个 struct timeval 类型的变量 tv,用于存储当前的时间信息。
//定义一个 time_t 类型的变量 time,用于存储当前的时间戳。
//定义一个字符数组 str_t,用于存储格式化后的时间字符串。
//调用 gettimeofday 函数,获取当前的时间信息,并将其存储在 tv 变量中。
//将 tv 变量中的时间戳赋值给 time 变量。
//调用 localtime 函数,将 time 变量转换为本地时间,并将结果存储在 p_time 变量中。
//调用 strftime 函数,将 p_time 变量中的本地时间格式化为一个字符串,并将其存储在 str_t 数组中。
//将格式化后的时间字符串添加到日志信息中,并将其存储在 stream_ 变量中。
void Logger::Impl::formatTime()
{
    struct timeval tv;
    time_t time;
    char str_t[26] = {0};
    gettimeofday (&tv, NULL);
    time = tv.tv_sec;
    struct tm* p_time = localtime(&time);   
    strftime(str_t, 26, "%Y-%m-%d %H:%M:%S\n", p_time);
    stream_ << str_t;
}

FileUtil.cpp

//stream:一个指向 FILE 类型的指针,表示要设置缓冲区的文件流。
//buf:一个指向字符数组的指针,表示用户提供的缓冲区。
//size:一个 size_t 类型的变量,表示缓冲区的大小。
//当你调用 setbuffer 函数时,文件流将使用用户提供的缓冲区进行 I/O 操作。这样,在进行文件读写时
//数据首先被存储在缓冲区,然后再从缓冲区写入文件或从文件读取到缓冲区。这种方式可以减少实时读写操作的开销,提高性能。
AppendFile::AppendFile(string filename) : fp_(fopen(filename.c_str(), "ae")) {
  // 用户提供缓冲区
  setbuffer(fp_, buffer_, sizeof buffer_);
}

HttpData.cpp

mime这个map在h和cpp中都声明了,感觉cpp中的声明是没有必要的?
std::unordered_map<std::string, std::string> MimeType::mime;  // 16行

Timer这个文件包含两个类

TimerNode类主要维护一个http请求和超时时间,以及是否删除等等。
TimerManager类包含一个优先级队列,用来存放TimerNode对象
// 表示队列存放的数据类型是SPTimerNode,std::deque<SPTimerNode>表示队列的实现容器是deque,TimerCmp代表比较器
std::priority_queue<SPTimerNode, std::deque<SPTimerNode>, TimerCmp> timerNodeQueue;

void TimerNode::update(int timeout) 更新超时时间

 // 这个函数的注释有点看不懂,我感觉就是被删除或者无效了,直接从堆里面删了啊
void TimerManager::handleExpiredEvent() {
  // MutexLockGuard locker(lock);
  while (!timerNodeQueue.empty()) {
    SPTimerNode ptimer_now = timerNodeQueue.top();
    if (ptimer_now->isDeleted())
      timerNodeQueue.pop();
    else if (ptimer_now->isValid() == false)
      timerNodeQueue.pop();
    else
      break;
  }
}

base类

noncopyable类

通过将拷贝构造函数和赋值运算符声明为私有成员函数并不提供实现,noncopyable 类禁止了派生类的对象进行拷贝构造和赋值操作。这样可以确保派生类的对象在使用过程中不会意外地进行拷贝或赋值,从而避免潜在的错误。

class noncopyable {
 protected:
  noncopyable() {}
  ~noncopyable() {}

 private:
  noncopyable(const noncopyable&);
  const noncopyable& operator=(const noncopyable&);
};
Util.cpp
主要是对读写函数进行了封装,可以保证数据能够被读写完。

// 这段代码是用来处理 SIGPIPE 信号的函数。在 Unix/Linux 系统中,当向一个已关闭的 socket 进行写操作时,系统会发送 SIGPIPE 信号给进程,通知它发生了管道破裂(Broken Pipe)的情况。默认情况下,如果进程没有对 SIGPIPE 信号进行处理,那么进程会被终止。
void handle_for_sigpipe() {
  struct sigaction sa;
  memset(&sa, '\0', sizeof(sa));
  sa.sa_handler = SIG_IGN;
  sa.sa_flags = 0;
  if (sigaction(SIGPIPE, &sa, NULL)) return;
}

// 设置 sa 结构体的 sa_handler 字段为 SIG_IGN,表示忽略 SIGPIPE 信号。 
// 调用 sigaction 函数,将 SIGPIPE 信号的处理方式设置为忽略,即当进程接收到 SIGPIPE 信号时不做任何处理。


// 将socket文件描述符设置成非阻塞
int setSocketNonBlocking(int fd) {
  int flag = fcntl(fd, F_GETFL, 0);
  if (flag == -1) return -1;

  flag |= O_NONBLOCK;
  if (fcntl(fd, F_SETFL, flag) == -1) return -1;
  return 0;
}

// 设置套接字的 SO_LINGER 选项,控制套接字关闭时的行为。SO_LINGER 选项允许应用程序控制在关闭套接字时的行为,特别是在还有数据未发送完毕的情况下。这样设置的作用是在关闭套接字时,如果还有未发送完的数据,套接字会最多等待指定的超时时间(30 秒),然后强制关闭连接并丢弃未发送完的数据。这样可以确保连接的及时关闭,避免连接处于半关闭状态或者等待数据发送完毕而导致的延迟。
void setSocketNoLinger(int fd) {
  struct linger linger_;
  linger_.l_onoff = 1;
  linger_.l_linger = 30;
  setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&linger_,
             sizeof(linger_));
}

日志

FileUtil提供了AppendFile类,将文件指针和缓冲区关联起来,可向缓冲区中append数据,也可以flush到实际的文件中

LogFile类,自带一个锁,和一个AppendFile类,提供缓冲区append能力,每append1024次,就flush到磁盘一次

LogStream自带一个FixedBuffer类,FixedBuffer其实就是一个缓冲区类,模板指定大小,提供一些追加操作;提供链式调用,提供向缓冲区中追加str、char、整型、浮点数等 前端

AsyncLogging就是启动一个线程函数,专门将缓冲区写入到日志系统中,但是不懂为什么要在线程函数中latch_.countDown();一次?

Logging是最上层的类,每次调用LOG<<的时候,就会生成一个Logger类,Logger类里面有一个AsyncLogging静态变量,每次析构的时候,就会启动这个类

总结

1、由于doPendingFunctors()调用的Functor可能再调用queueInLoop(cb),这时queueInLoop()就必须wakeup(),否则这些新加的
cb就不能被及时调用了。muduo这里没有反复执行doPendingFunctors()直到pendingFunctors_为空,这是有意的,否则IO线程有可能陷入死循
环,无法处理IO事件。为什么是这样,能解释一下吗?

在 Muduo 中,doPendingFunctors() 函数用于执行队列中的待处理函数。当 IO 线程在处理事件时,可能会有新的回调函数被添加到队列中。为了确保这些新添加的回调函数能够被及时执行,Muduo 在每次处理完一个事件后,都会调用 doPendingFunctors() 函数来执行队列中的待处理函数。然而,如果在执行 doPendingFunctors() 函数时,新的回调函数又被添加到队列中,那么就有可能导致 IO 线程陷入死循环,无法处理 IO 事件。为了避免这种情况,Muduo 在每次调用 doPendingFunctors() 函数时,只会执行队列中的一定数量的回调函数。这个数量由 kPendingFunctorCount 常量定义,通常设置为 16。

通俗来说,就是执行额外任务的时候,可能会不断的有额外任务加进来,如果每次都要把额外任务执行完才进行下一步的话,IO线程就不能及时处理新的回调函数了,只能一直在执行doPendingFunctors()函数。
2、为什么quit的时候,如果是其他线程调用quit函数,必须调用wakeup去唤醒IO线程呢?
void EventLoop::quit() {
  quit_ = true;
  if (!isInLoopThread()) {
    wakeup();
  }
}


在 Muduo 中,EventLoop::quit() 函数用于停止事件循环。当调用 quit() 函数时,事件循环会在下一个事件处理完成后终止。在这种情况下,如果 IO 线程调用了 quit() 函数,那么 IO 线程会在处理完当前事件后终止事件循环,不需要调用 wakeup() 函数来唤醒 IO 线程。

这是因为,当 IO 线程调用 quit() 函数时,它已经在事件循环中,并且已经处理了一个事件。在这种情况下,IO 线程会在处理完当前事件后,自然地进入下一个事件处理,然后发现事件循环已经终止,从而退出事件循环。因此,在这种情况下,不需要调用 wakeup() 函数来唤醒 IO 线程。

然而,如果在其他线程中调用 quit() 函数,那么需要调用 wakeup() 函数来唤醒 IO 线程,以确保 IO 线程能够及时终止事件循环。这是因为在其他线程中调用 quit() 函数时,IO 线程可能正在等待事件,而不是处理事件。在这种情况下,调用 wakeup() 函数可以唤醒 IO 线程,使其立即处理 quit() 函数,从而终止事件循环。

Epoll常见事件

  1. EPOLLIN:用于监听文件描述符上的可读事件。当文件描述符上有数据可读时,epoll 会触发这个事件。这通常用于接收网络数据或从其他文件描述符读取数据。
  2. EPOLLOUT:用于监听文件描述符上的可写事件。当文件描述符可以写入数据时,epoll 会触发这个事件。这通常用于发送网络数据或向其他文件描述符写入数据。
  3. EPOLLET:表示使用边缘触发模式。边缘触发模式与默认的水平触发模式不同,它只在状态发生变化时触发事件。这可以提高事件处理的效率,但需要更谨慎地处理事件。
  4. EPOLLPRI:表示有紧急数据可读。这个事件通常用于带外数据的处理,例如TCP的紧急指针。当文件描述符上有紧急数据可读时,epoll 会触发这个事件。
  5. EPOLLERR:表示发生了错误。当文件描述符上发生错误时,epoll 会触发这个事件。这可能是由于文件描述符关闭、连接中断或其他错误导致的。当 EPOLLERR 事件触发时,你需要检查文件描述符的错误状态,并采取相应的措施。
  6. EPOLLHUP:表示文件描述符挂起。当文件描述符的另一端关闭连接时,epoll 会触发这个事件。这意味着你可能需要关闭文件描述符并清理相关资源。当 EPOLLHUP 事件触发时,你需要检查文件描述符的状态,并采取相应的措施。
  7. EPOLLONESHOT:表示仅触发一次事件。当使用这个标志时,epoll 只会触发一次事件,然后需要重新注册事件才能再次触发。这可以避免不必要的事件触发,提高效率。当你需要在特定条件下仅处理一次事件时,可以使用 EPOLLONESHOT 标志。
  8. EPOLLRDHUP表示对方已经关闭了写端的连接,但读端仍然是开放的,这时可以继续读取数据
3、epoll在边沿模式下,必须要将套接字设置为非阻塞模式,但是,这样就会引发另外的一个bug,在非阻塞模式下,循环地将读缓冲区数据读到本地内存中,当缓冲区数据被读完了,调用的read()/recv()函数还会继续从缓冲区中读数据,此时函数调用就失败了,返回-1,对应的全局变量 errno 值为 EAGAIN 或者 EWOULDBLOCK如果打印错误信息会得到如下的信息:Resource temporarily unavailable

4、访问根页面的时候,浏览器总是会自动的访问favicon.ico这个图标,通过get请求区获取,这是浏览器的默认行为!!!>

疑问

  • 为什么HttpData.cpp中处理连接的函数,有这么一段

                if ((events_ & EPOLLIN) && (events_ & EPOLLOUT)) {
                    events_ = __uint32_t(0);
                    events_ |= EPOLLOUT;
                }
                // events_ |= (EPOLLET | EPOLLONESHOT);
                events_ |= EPOLLET;
    

    为什么要置为0,然后设置边沿模式?

  • wakeupFd_的作用是?

    其实就是线程间通信
    
    
    事件循环的休眠:在事件驱动的程序中,EventLoop负责处理IO事件,如文件描述符的读写事件。当没有事件发生时,EventLoop会进入休眠状态,以节省资源。
    
    线程间通信:在其他线程需要通知EventLoop处理某些任务或事件时,它们需要一种机制来唤醒EventLoop。
    
    wakeupFd的作用:wakeupFd_就是用于这个目的。它可以被其他线程写入一个字节,从而触发EventLoop从休眠状态唤醒,并处理等待的事件。
    
    实现细节:wakeupFd_通常是一个eventfd(在Linux系统中)。当其他线程需要唤醒EventLoop时,它会向wakeupFd_写入一个字节。EventLoop在休眠时会定期检查wakeupFd_是否有数据可读。一旦检测到数据,它就会从休眠状态唤醒,并处理这些数据。
    
    效率:使用wakeupFd_而不是传统的轮询方法,可以提高效率。因为EventLoop不需要不断地检查是否有事件发生,而是在事件发生时被直接唤醒。
    
    wakeupFd_是EventLoop类中用于线程间通信和唤醒的一个关键机制,允许EventLoop在需要处理事件时从休眠状态快速唤醒。
    
  • Server里面,handNewConn和handThisConn的区别是什么?这里还有一个疑问,**acceptChannel_**有必要设置连接处理函数嘛?因为它绑定的是监听文件描述符,只处理新连接。

    handNewConn 函数用于处理新的连接请求。当有新的客户端连接到达时,handNewConn 函数会被调用。在这个函数中,我们首先接受新的连接,然后创建一个新的 HttpData 对象来处理这个连接。接着,我们将新的连接添加到事件循环中,以便在连接上发生事件时能够正确处理。
    
    handThisConn 函数用于处理已存在的连接上的事件。当已存在的连接上发生读、写或其他事件时,handThisConn 函数会被调用。在这个函数中,我们需要根据事件类型来处理相应的事件,例如读取客户端发送的数据、发送响应给客户端等。
    
    总之,handNewConn 和 handThisConn 的主要区别在于它们处理的事件类型不同。handNewConn 用于处理新的连接请求,而 handThisConn 用于处理已存在的连接上的事件。
    
  • 应该只有主loop唤醒从loop的操作,只涉及到主从loop的通信,不涉及到从loop之间的通信。

main函数中,先创建一个mainloop,然后创建一个server,mainloop为server中的一个变量。然后启动server,开启mainloop的loop循环。在server中会启动线程池,线程池又会开启4个从loop,但是从loop不是通过mainloop一样的方式启动的,是利用线程的方式,在线程threadfunc中启动的,这其中涉及到一些同步、锁相关的东西,重点是countdownlatch。

浏览器输入网址的时候,浏览器首先会建立一个tcp连接,建立以后,会根据网址,自动生成一个http request。server的loop就是mainloop,这个loop会绑定一个acceptchannel,这个channel是专门负责建立连接的,新连接到来的时候,在Server::handNewConn()中会建立连接,然后选一个子loop分发这个连接的文件描述符和httpdata。再把这个HttppData对象和newEvent方法绑定,调用子loop的queueInLoop函数。这个时候,由于不是在当前线程,所以会执行唤醒操作,因为子loop可能会阻塞在前面的loop循环中的epoll_wait上,默认是监听这个wakeupFd_这个文件描述符的,唤醒以后,loop循环就会执行void EventLoop::doPendingFunctors()这个函数,执行前面所说的绑定的方法,在这个方法中,会将这个新建立的channel添加到epoll中。

建立了连接以后,接下来浏览器每次访问的时候,就会传递一个请求,然后前面每个子loop由于已经监听了这个连接,每次传递请求的时候,就会可读就绪。可读的时候就是解析请求体,然后返回响应,再可写就绪,将数据发给浏览器。

最后,为了整理一下自己的注释,还是给出patch代码吧

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..86f4157
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.vscode/
+build/
+debug/
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 03a159b..160cff5 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,61 @@
         "editor.suggest.insertMode": "replace",
         "editor.semanticHighlighting.enabled": true,
         "editor.tabSize": 4
-    }
+    },
+    "files.associations": {
+        "cctype": "cpp",
+        "clocale": "cpp",
+        "cmath": "cpp",
+        "cstdarg": "cpp",
+        "cstddef": "cpp",
+        "cstdio": "cpp",
+        "cstdlib": "cpp",
+        "ctime": "cpp",
+        "cwchar": "cpp",
+        "cwctype": "cpp",
+        "array": "cpp",
+        "atomic": "cpp",
+        "bit": "cpp",
+        "*.tcc": "cpp",
+        "chrono": "cpp",
+        "condition_variable": "cpp",
+        "cstdint": "cpp",
+        "deque": "cpp",
+        "map": "cpp",
+        "unordered_map": "cpp",
+        "vector": "cpp",
+        "exception": "cpp",
+        "algorithm": "cpp",
+        "functional": "cpp",
+        "iterator": "cpp",
+        "memory": "cpp",
+        "memory_resource": "cpp",
+        "numeric": "cpp",
+        "optional": "cpp",
+        "random": "cpp",
+        "ratio": "cpp",
+        "string": "cpp",
+        "string_view": "cpp",
+        "system_error": "cpp",
+        "tuple": "cpp",
+        "type_traits": "cpp",
+        "utility": "cpp",
+        "fstream": "cpp",
+        "future": "cpp",
+        "initializer_list": "cpp",
+        "iosfwd": "cpp",
+        "iostream": "cpp",
+        "istream": "cpp",
+        "limits": "cpp",
+        "mutex": "cpp",
+        "new": "cpp",
+        "ostream": "cpp",
+        "sstream": "cpp",
+        "stdexcept": "cpp",
+        "streambuf": "cpp",
+        "thread": "cpp",
+        "cinttypes": "cpp",
+        "typeinfo": "cpp"
+    },
+    "C_Cpp.errorSquiggles": "enabled"
 }
\ No newline at end of file
diff --git a/Channel.h b/Channel.h
index f6132f0..185aac5 100755
--- a/Channel.h
+++ b/Channel.h
@@ -58,22 +58,28 @@ class Channel {
     void setConnHandler(CallBack &&connHandler) { connHandler_ = connHandler; }
 
     void handleEvents() {
+        // 处理事件之前都清除了channel中内部状态,表示不再监听?但是真正清除得在epoll_ctl中删除,这里置为0是为了保持一致性等
         events_ = 0;
+        // 客户端关闭连接且没有可读事件
         if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) {
             events_ = 0;
             return;
         }
+        // 错误事件
         if (revents_ & EPOLLERR) {
             if (errorHandler_) errorHandler_();
             events_ = 0;
             return;
         }
+        // 可读 紧急可读 对方关闭连接但仍可读
         if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
             handleRead();
         }
+        // 可写
         if (revents_ & EPOLLOUT) {
             handleWrite();
         }
+        // 处理连接
         handleConn();
     }
     void handleRead();
diff --git a/Epoll.cpp b/Epoll.cpp
index 6ef3328..b9099b7 100755
--- a/Epoll.cpp
+++ b/Epoll.cpp
@@ -78,7 +78,7 @@ void Epoll::epoll_del(SP_Channel request) {
     fd2http_[fd].reset();
 }
 
-// 返回活跃事件数
+// 返回活跃事件数,超时时间为10s
 std::vector<SP_Channel> Epoll::poll() {
     while (true) {
         int event_count = epoll_wait(epollFd_, &*events_.begin(),
@@ -100,6 +100,8 @@ std::vector<SP_Channel> Epoll::getEventsRequest(int events_num) {
 
         SP_Channel cur_req = fd2chan_[fd];
 
+        // fd跟channel自始至终都是绑定的,不可能出现有fd没有channel的情况,修改channel的实际发生事件,同时将感兴趣事件置为0
+        // 最后返回channels集合
         if (cur_req) {
             cur_req->setRevents(events_[i].events);
             cur_req->setEvents(0);
diff --git a/EventLoop.cpp b/EventLoop.cpp
index 7da000b..6f4dcc8 100755
--- a/EventLoop.cpp
+++ b/EventLoop.cpp
@@ -20,9 +20,11 @@ int createEventfd() {
         LOG << "Failed in eventfd";
         abort();
     }
+    // std::cout << "evtfd :" << evtfd << std::endl;
     return evtfd;
 }
 
+// 这里相当于 主 从loop均用一个模板?
 EventLoop::EventLoop()
     : looping_(false),
       poller_(new Epoll()),
@@ -33,8 +35,8 @@ EventLoop::EventLoop()
       threadId_(CurrentThread::tid()),
       pwakeupChannel_(new Channel(this, wakeupFd_)) {
     if (t_loopInThisThread) {
-        // LOG << "Another EventLoop " << t_loopInThisThread << " exists in this
-        // thread " << threadId_;
+        std::cout << "Another EventLoop " << t_loopInThisThread
+                  << "exists in this thread " << threadId_ << std::endl;
     } else {
         t_loopInThisThread = this;
     }
@@ -42,9 +44,11 @@ EventLoop::EventLoop()
     pwakeupChannel_->setEvents(EPOLLIN | EPOLLET);
     pwakeupChannel_->setReadHandler(bind(&EventLoop::handleRead, this));
     pwakeupChannel_->setConnHandler(bind(&EventLoop::handleConn, this));
+    // 肯定要注册,持续监听这个channel的
     poller_->epoll_add(pwakeupChannel_, 0);
 }
 
+// 这个函数貌似没有意义?只是将pwakeupChannel_上的事件修改为fd对应监听的事件,是为了同步?
 void EventLoop::handleConn() {
     // poller_->epoll_mod(wakeupFd_, pwakeupChannel_, (EPOLLIN | EPOLLET |
     // EPOLLONESHOT), 0);
@@ -58,7 +62,9 @@ EventLoop::~EventLoop() {
     t_loopInThisThread = NULL;
 }
 
+// 异步通信机制,通知当前eventloop需要处理某个任务,只写入一个字节,wakeup以后,就可以读了,同时能够继续监听?
 void EventLoop::wakeup() {
+    std::cout << "调用了wakeup函数" << std::endl;
     uint64_t one = 1;
     ssize_t n = writen(wakeupFd_, (char*)(&one), sizeof one);
     if (n != sizeof one) {
@@ -66,6 +72,8 @@ void EventLoop::wakeup() {
     }
 }
 
+// 这个回调函数绑定在了Channel类中的读回调处理函数,即发生可读事件的时候,对应的channel就调用这个函数
+// 正常可读的时候,应该是能够直接监测到的,但是如果有紧急任务啥的?如果这个线程阻塞了?就需要wakeup唤醒一下?
 void EventLoop::handleRead() {
     uint64_t one = 1;
     ssize_t n = readn(wakeupFd_, &one, sizeof one);
@@ -73,6 +81,8 @@ void EventLoop::handleRead() {
         LOG << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
     }
     // pwakeupChannel_->setEvents(EPOLLIN | EPOLLET | EPOLLONESHOT);
+    // 确保 EventLoop 在读取 wakeupFd_
+    // 上的数据后,如果还有数据可读,能够再次触发读事件
     pwakeupChannel_->setEvents(EPOLLIN | EPOLLET);
 }
 
@@ -83,6 +93,7 @@ void EventLoop::runInLoop(Functor&& cb) {
         queueInLoop(std::move(cb));
 }
 
+// 处理额外的任务,如果不是当前线程,或者正在处理额外任务的时候,需要唤醒
 void EventLoop::queueInLoop(Functor&& cb) {
     {
         MutexLockGuard lock(mutex_);
@@ -125,6 +136,7 @@ void EventLoop::doPendingFunctors() {
     callingPendingFunctors_ = false;
 }
 
+// quit只是简单的将quit_变量置为true,代表退出
 void EventLoop::quit() {
     quit_ = true;
     if (!isInLoopThread()) {
diff --git a/EventLoop.h b/EventLoop.h
index 9384696..b8af6a8 100755
--- a/EventLoop.h
+++ b/EventLoop.h
@@ -47,6 +47,7 @@ class EventLoop {
     mutable MutexLock mutex_;
     std::vector<Functor> pendingFunctors_;
     bool callingPendingFunctors_;
+    // 其实就是int
     const pid_t threadId_;
     shared_ptr<Channel> pwakeupChannel_;
 
diff --git a/EventLoopThread.cpp b/EventLoopThread.cpp
index f8b4886..e2da35f 100755
--- a/EventLoopThread.cpp
+++ b/EventLoopThread.cpp
@@ -24,13 +24,16 @@ EventLoop* EventLoopThread::startLoop() {
     thread_.start();
     {
         MutexLockGuard lock(mutex_);
-        // 一直等到threadFun在Thread里真正跑起来
+        // 一直等到下面的threadFunc在Thread里真正跑起来,注意这里是cond_.wait,不是latch_
+        // 也就是loop_初始化了再返回,下面会notify
         while (loop_ == NULL) cond_.wait();
     }
     return loop_;
 }
 
 void EventLoopThread::threadFunc() {
+    // 无参构造了,最后真正跑的loop其实也就是loop_指向的loop
+    // 养成指针用完就NULL的习惯
     EventLoop loop;
 
     {
diff --git a/EventLoopThreadPool.cpp b/EventLoopThreadPool.cpp
index 43041fc..5e0e8ce 100755
--- a/EventLoopThreadPool.cpp
+++ b/EventLoopThreadPool.cpp
@@ -2,6 +2,7 @@
 // @Email xxbbb@vip.qq.com
 #include "EventLoopThreadPool.h"
 
+// 初始化传入一个主loop,其余均为从loop
 EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, int numThreads)
     : baseLoop_(baseLoop), started_(false), numThreads_(numThreads), next_(0) {
     if (numThreads_ <= 0) {
@@ -10,6 +11,8 @@ EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, int numThreads)
     }
 }
 
+// 主loop一定是在当前线程中执行的,然后开启子loop线程,依次启动
+// 保存对应的EventLoopThread线程 和 启动起来的EventLoop
 void EventLoopThreadPool::start() {
     baseLoop_->assertInLoopThread();
     started_ = true;
@@ -20,6 +23,8 @@ void EventLoopThreadPool::start() {
     }
 }
 
+// 取下一个Loop的时候,都必须判断是不是在主Loop的这个线程
+// 换句话说,只有主Loop有资格?
 EventLoop *EventLoopThreadPool::getNextLoop() {
     baseLoop_->assertInLoopThread();
     assert(started_);
diff --git a/HttpData.cpp b/HttpData.cpp
index 1d125cb..e223832 100755
--- a/HttpData.cpp
+++ b/HttpData.cpp
@@ -156,11 +156,13 @@ void HttpData::seperateTimer() {
     }
 }
 
+// 浏览器输入网址以后,浏览器会构建一个request,读出来存在inBuffer_中
 void HttpData::handleRead() {
     __uint32_t &events_ = channel_->getEvents();
     do {
         bool zero = false;
         int read_num = readn(fd_, inBuffer_, zero);
+        // 这里是放一些请求体request什么的
         LOG << "Request: " << inBuffer_;
         if (connectionState_ == H_DISCONNECTING) {
             inBuffer_.clear();
@@ -323,6 +325,7 @@ void HttpData::handleConn() {
     }
 }
 
+// 解析请求行,得到http版本,文件名,请求方法
 URIState HttpData::parseURI() {
     string &str = inBuffer_;
     string cop = str;
@@ -333,6 +336,7 @@ URIState HttpData::parseURI() {
     }
     // 去掉请求行所占的空间,节省空间
     string request_line = str.substr(0, pos);
+    // std::cout << "request_line-->" << request_line << std::endl;
     if (str.size() > pos + 1)
         str = str.substr(pos + 1);
     else
@@ -379,7 +383,7 @@ URIState HttpData::parseURI() {
         }
         pos = _pos;
     }
-    // cout << "fileName_: " << fileName_ << endl;
+    cout << "fileName_: " << fileName_ << endl;
     // HTTP 版本号
     pos = request_line.find("/", pos);
     if (pos < 0)
@@ -400,6 +404,7 @@ URIState HttpData::parseURI() {
     return PARSE_URI_SUCCESS;
 }
 
+// 每一行末尾必须是/r/n  最后一行末尾是/r/n/r/n
 HeaderState HttpData::parseHeaders() {
     string &str = inBuffer_;
     int key_start = -1, key_end = -1, value_start = -1, value_end = -1;
@@ -489,6 +494,7 @@ HeaderState HttpData::parseHeaders() {
     return PARSE_HEADER_AGAIN;
 }
 
+// 根据请求类型,得到response
 AnalysisState HttpData::analysisRequest() {
     if (method_ == METHOD_POST) {
         // ------------------------------------------------------
@@ -535,7 +541,7 @@ AnalysisState HttpData::analysisRequest() {
             filetype = MimeType::getMime("default");
         else
             filetype = MimeType::getMime(fileName_.substr(dot_pos));
-
+        std::cout << "fileName_-->" << fileName_ << std::endl;
         // echo test
         if (fileName_ == "hello") {
             outBuffer_ =
@@ -554,7 +560,8 @@ AnalysisState HttpData::analysisRequest() {
             ;
             return ANALYSIS_SUCCESS;
         }
-
+        // stat函数是用于获取文件或目录状态的系统调用。
+        // 它提供了关于文件或目录的详细信息,包括文件类型、权限、所有者、大小、最后访问和修改时间等。
         struct stat sbuf;
         if (stat(fileName_.c_str(), &sbuf) < 0) {
             header.clear();
@@ -569,16 +576,19 @@ AnalysisState HttpData::analysisRequest() {
         outBuffer_ += header;
 
         if (method_ == METHOD_HEAD) return ANALYSIS_SUCCESS;
-
+        // 只读方式打开
         int src_fd = open(fileName_.c_str(), O_RDONLY, 0);
         if (src_fd < 0) {
             outBuffer_.clear();
             handleError(fd_, 404, "Not Found!");
             return ANALYSIS_ERROR;
         }
+        // 这里实际上就是内存映射,多进程通信
         void *mmapRet =
             mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
         close(src_fd);
+        // 检查 mmap 是否返回
+        // MAP_FAILED,这是一个特殊的指针,表示映射失败。如果映射失败,可能是由于文件不存在或其他原因
         if (mmapRet == (void *)-1) {
             munmap(mmapRet, sbuf.st_size);
             outBuffer_.clear();
@@ -594,6 +604,9 @@ AnalysisState HttpData::analysisRequest() {
     return ANALYSIS_ERROR;
 }
 
+// 分别发送头部和正文,以确保客户端能够正确解析 HTTP响应。
+// 这是实现高性能网络服务器的一种最佳实践,因为它可以确保客户端能够正确处理
+// HTTP 响应
 void HttpData::handleError(int fd, int err_num, string short_msg) {
     short_msg = " " + short_msg;
     char send_buff[4096];
diff --git a/HttpData.h b/HttpData.h
index a205151..9a456e7 100755
--- a/HttpData.h
+++ b/HttpData.h
@@ -102,6 +102,7 @@ class HttpData : public std::enable_shared_from_this<HttpData> {
     ProcessState state_;
     ParseState hState_;
     bool keepAlive_;
+    // 存储解析响应头里面的headers
     std::map<std::string, std::string> headers_;
     std::weak_ptr<TimerNode> timer_;
 
diff --git a/Main.cpp b/Main.cpp
index 45933e4..7141d2a 100755
--- a/Main.cpp
+++ b/Main.cpp
@@ -10,7 +10,7 @@
 
 int main(int argc, char *argv[]) {
     int threadNum = 4;
-    int port = 80;
+    int port = 8080;
     std::string logPath = "./WebServer.log";
 
     // parse args
@@ -45,6 +45,7 @@ int main(int argc, char *argv[]) {
 #endif
     EventLoop mainLoop;
     Server myHTTPServer(&mainLoop, threadNum, port);
+    LOG << "正常输出log!";
     myHTTPServer.start();
     mainLoop.loop();
     return 0;
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..c0b76cb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# A C++ High Performance Web Server
+
+[![Build Status](https://travis-ci.org/linyacool/WebServer.svg?branch=master)](https://travis-ci.org/linyacool/WebServer)
+[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT)
+
+  
+## Introduction  
+
+本项目为C++11编写的Web服务器,解析了get、head请求,可处理静态资源,支持HTTP长连接,支持管线化请求,并实现了异步日志,记录服务器运行状态。  
+
+
+
+| Part Ⅰ | Part Ⅱ | Part Ⅲ | Part Ⅳ | Part Ⅴ | Part Ⅵ |
+| :--------: | :---------: | :---------: | :---------: | :---------: | :---------: |
+| [并发模型](https://github.com/linyacool/WebServer/blob/master/并发模型.md)|[连接的维护](https://github.com/linyacool/WebServer/blob/master/连接的维护.md)|[版本历史](https://github.com/linyacool/WebServer/blob/master/%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2.md) | [测试及改进](https://github.com/linyacool/WebServer/blob/master/测试及改进.md) | [项目目的](https://github.com/linyacool/WebServer/blob/master/%E9%A1%B9%E7%9B%AE%E7%9B%AE%E7%9A%84.md) | [面试问题](https://github.com/linyacool/WebServer/blob/master/%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98.md)
+
+## Envoirment  
+* OS: Ubuntu 14.04
+* Complier: g++ 4.8
+
+## Build
+
+	./build.sh
+
+## Usage
+
+	./WebServer [-t thread_numbers] [-p port] [-l log_file_path(should begin with '/')]
+
+## Technical points
+* 使用Epoll边沿触发的IO多路复用技术,非阻塞IO,使用Reactor模式
+* 使用多线程充分利用多核CPU,并使用线程池避免线程频繁创建销毁的开销
+* 使用基于小根堆的定时器关闭超时请求
+* 主线程只负责accept请求,并以Round Robin的方式分发给其它IO线程(兼计算线程),锁的争用只会出现在主线程和某一特定线程中
+* 使用eventfd实现了线程的异步唤醒
+* 使用双缓冲区技术实现了简单的异步日志系统
+* 为减少内存泄漏的可能,使用智能指针等RAII机制
+* 使用状态机解析了HTTP请求,支持管线化
+* 支持优雅关闭连接
+ 
+## Model
+
+并发模型为Reactor+非阻塞IO+线程池,新连接Round Robin分配,详细介绍请参考[并发模型](https://github.com/linyacool/WebServer/blob/master/并发模型.md)
+![并发模型](https://github.com/linyacool/WebServer/blob/master/datum/model.png)
+
+## 代码统计
+
+![cloc](https://github.com/linyacool/WebServer/blob/master/datum/cloc.png)
+
+
+## Others
+除了项目基本的代码,进服务器进行压测时,对开源测试工具Webbench增加了Keep-Alive选项和测试功能: 改写后的[Webbench](https://github.com/linyacool/WebBench)
+
diff --git a/Server.cpp b/Server.cpp
index e4acb4e..2606aa0 100755
--- a/Server.cpp
+++ b/Server.cpp
@@ -11,6 +11,11 @@
 #include "Util.h"
 #include "base/Logging.h"
 
+// count用来测试有多少个请求,while只会执行一次!
+int Server::count = 0;
+
+// 主loop绑定一个acceptChannel,然后创建一个socket监听指定端口
+// 并将该文件描述符设置给acceptChannel,并设置为非阻塞
 Server::Server(EventLoop *loop, int threadNum, int port)
     : loop_(loop),
       threadNum_(threadNum),
@@ -44,12 +49,17 @@ void Server::handNewConn() {
     int accept_fd = 0;
     while ((accept_fd = accept(listenFd_, (struct sockaddr *)&client_addr,
                                &client_addr_len)) > 0) {
+        count++;
+        std::cout << "执行了" << count << std::endl;
         EventLoop *loop = eventLoopThreadPool_->getNextLoop();
         LOG << "New connection from " << inet_ntoa(client_addr.sin_addr) << ":"
             << ntohs(client_addr.sin_port);
-        // cout << "new connection" << endl;
-        // cout << inet_ntoa(client_addr.sin_addr) << endl;
-        // cout << ntohs(client_addr.sin_port) << endl;
+        std::cout << "-----start-----" << std::endl;
+        std::cout << "accept_fd-->" << accept_fd << std::endl;
+        std::cout << "&loop-->" << loop << std::endl;
+        std::cout << "new connection" << std::endl;
+        std::cout << inet_ntoa(client_addr.sin_addr) << std::endl;
+        std::cout << ntohs(client_addr.sin_port) << std::endl;
         /*
         // TCP的保活机制默认是关闭的
         int optval = 0;
@@ -72,9 +82,13 @@ void Server::handNewConn() {
         setSocketNodelay(accept_fd);
         // setSocketNoLinger(accept_fd);
 
+        // 这里建立新的连接以后生成一个HttpData,绑定选中的loop和新连接的fd
+        // 然后完成HttpData和channel的双向绑定?
+        // 最后将这个HttpData对象和它的事件处理函数绑定在一起,作为回调函数传入从loop的队列中
         shared_ptr<HttpData> req_info(new HttpData(loop, accept_fd));
         req_info->getChannel()->setHolder(req_info);
         loop->queueInLoop(std::bind(&HttpData::newEvent, req_info));
+        std::cout << "----- end -----" << std::endl;
     }
     acceptChannel_->setEvents(EPOLLIN | EPOLLET);
 }
\ No newline at end of file
diff --git a/Server.h b/Server.h
index bb6ead8..bcec56c 100755
--- a/Server.h
+++ b/Server.h
@@ -14,7 +14,11 @@ class Server {
     EventLoop *getLoop() const { return loop_; }
     void start();
     void handNewConn();
-    void handThisConn() { loop_->updatePoller(acceptChannel_); }
+    void handThisConn() {
+        std::cout << "handThisConn执行了" << std::endl;
+        loop_->updatePoller(acceptChannel_);
+    }
+    static int count;
 
    private:
     EventLoop *loop_;
diff --git a/Timer.cpp b/Timer.cpp
index 66bd06d..0a95dbc 100755
--- a/Timer.cpp
+++ b/Timer.cpp
@@ -11,7 +11,7 @@ TimerNode::TimerNode(std::shared_ptr<HttpData> requestData, int timeout)
     : deleted_(false), SPHttpData(requestData) {
     struct timeval now;
     gettimeofday(&now, NULL);
-    // 以毫秒计
+    // 以毫秒计,这里取余没看懂
     expiredTime_ =
         (((now.tv_sec % 10000) * 1000) + (now.tv_usec / 1000)) + timeout;
 }
diff --git a/Timer.h b/Timer.h
index b8f7b78..90aeefd 100755
--- a/Timer.h
+++ b/Timer.h
@@ -13,6 +13,7 @@
 
 class HttpData;
 
+// 每个Node有一个httpData以及超时时间
 class TimerNode {
    public:
     TimerNode(std::shared_ptr<HttpData> requestData, int timeout);
@@ -38,6 +39,7 @@ struct TimerCmp {
     }
 };
 
+// Manager里面有个优先级队列,放的就是一个个Node,根据超时时间排序
 class TimerManager {
    public:
     TimerManager();
diff --git a/Util.cpp b/Util.cpp
index faebb04..1cc1c81 100755
--- a/Util.cpp
+++ b/Util.cpp
@@ -35,6 +35,7 @@ ssize_t readn(int fd, void *buff, size_t n) {
     return readSum;
 }
 
+// EINTR表示系统调用被信号中断,
 ssize_t readn(int fd, std::string &inBuffer, bool &zero) {
     ssize_t nread = 0;
     ssize_t readSum = 0;
@@ -144,6 +145,10 @@ ssize_t writen(int fd, std::string &sbuff) {
     return writeSum;
 }
 
+// 初始化sa结构体所有字段为0
+// 将sa.sa_handler设置为SIG_IGN,这意味着当SIGPIPE信号发生时,系统将忽略该信号,不会执行任何默认动作。
+// 最后,该函数调用sigaction系统调用来注册新的信号处理程序。
+// 该函数的作用是告诉操作系统忽略SIGPIPE信号,这样在管道破裂时程序不会因为收到该信号而终止
 void handle_for_sigpipe() {
     struct sigaction sa;
     memset(&sa, '\0', sizeof(sa));
diff --git a/base/AsyncLogging.cpp b/base/AsyncLogging.cpp
index 978258d..ee57fdf 100755
--- a/base/AsyncLogging.cpp
+++ b/base/AsyncLogging.cpp
@@ -27,6 +27,7 @@ AsyncLogging::AsyncLogging(std::string logFileName_, int flushInterval)
     buffers_.reserve(16);
 }
 
+// 前端buf写到后端buf中
 void AsyncLogging::append(const char* logline, int len) {
     MutexLockGuard lock(mutex_);
     if (currentBuffer_->avail() > len)
@@ -45,6 +46,7 @@ void AsyncLogging::append(const char* logline, int len) {
 
 void AsyncLogging::threadFunc() {
     assert(running_ == true);
+    // 这里疑惑为什么还要countdown一次?
     latch_.countDown();
     LogFile output(basename_);
     BufferPtr newBuffer1(new Buffer);
@@ -76,6 +78,7 @@ void AsyncLogging::threadFunc() {
 
         assert(!buffersToWrite.empty());
 
+        // 如果缓冲区个数多于25,就丢失到只有2个缓冲区,这里数据会丢失
         if (buffersToWrite.size() > 25) {
             // char buf[256];
             // snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger
diff --git a/base/AsyncLogging.h b/base/AsyncLogging.h
index d678a04..b432ba3 100755
--- a/base/AsyncLogging.h
+++ b/base/AsyncLogging.h
@@ -22,11 +22,13 @@ class AsyncLogging : noncopyable {
     void start() {
         running_ = true;
         thread_.start();
+        // 上面已经执行过一次wait,这里我感觉是不需要再执行的?但是多次执行也不会有啥影响
         latch_.wait();
     }
 
     void stop() {
         running_ = false;
+        // 这里为什么要唤醒呢?因为buffers_.empty()的时候,会阻塞
         cond_.notify();
         thread_.join();
     }
@@ -36,6 +38,7 @@ class AsyncLogging : noncopyable {
     typedef FixedBuffer<kLargeBuffer> Buffer;
     typedef std::vector<std::shared_ptr<Buffer>> BufferVector;
     typedef std::shared_ptr<Buffer> BufferPtr;
+    // 刷新日志到文件的时间间隔,默认为2s,有两个缓冲区,当前和下一个,最多可以存16个缓冲区
     const int flushInterval_;
     bool running_;
     std::string basename_;
diff --git a/base/CountDownLatch.cpp b/base/CountDownLatch.cpp
index 8a2395b..0b98fe4 100755
--- a/base/CountDownLatch.cpp
+++ b/base/CountDownLatch.cpp
@@ -5,6 +5,7 @@
 CountDownLatch::CountDownLatch(int count)
     : mutex_(), condition_(mutex_), count_(count) {}
 
+// 这里是不是会一直调用wait函数?wait会阻塞当前的线程,直到被唤醒
 void CountDownLatch::wait() {
     MutexLockGuard lock(mutex_);
     while (count_ > 0) condition_.wait();
diff --git a/base/CurrentThread.h b/base/CurrentThread.h
index 035f7f3..bfd87ce 100755
--- a/base/CurrentThread.h
+++ b/base/CurrentThread.h
@@ -10,6 +10,7 @@ extern __thread char t_tidString[32];
 extern __thread int t_tidStringLength;
 extern __thread const char* t_threadName;
 void cacheTid();
+// 用于获取当前线程的ID,检查t_cachedTid==0,为0这执行cacheTid();否则返回ID
 inline int tid() {
     if (__builtin_expect(t_cachedTid == 0, 0)) {
         cacheTid();
diff --git a/base/FileUtil.cpp b/base/FileUtil.cpp
index 2a3ad26..42f6407 100755
--- a/base/FileUtil.cpp
+++ b/base/FileUtil.cpp
@@ -8,16 +8,19 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <unistd.h>
-
+#include <iostream>
 using namespace std;
 
 AppendFile::AppendFile(string filename) : fp_(fopen(filename.c_str(), "ae")) {
-    // 用户提供缓冲区
+    // 用户提供缓冲区,与fp_指针关联起来
+    // 打印一下filename
+    std::cout<< "filename = " << filename << std::endl;
     setbuffer(fp_, buffer_, sizeof buffer_);
 }
 
 AppendFile::~AppendFile() { fclose(fp_); }
 
+// 封装了write,还有异常处理
 void AppendFile::append(const char* logline, const size_t len) {
     size_t n = this->write(logline, len);
     size_t remain = len - n;
@@ -33,8 +36,10 @@ void AppendFile::append(const char* logline, const size_t len) {
     }
 }
 
+// 将缓冲区内的数据立马写入到实际的文件中?
 void AppendFile::flush() { fflush(fp_); }
 
+// 将logline中的len字节写入到文件指针fp_指向的缓冲区中,返回实际写入的字节。不相等代表发生了截断,比如缓冲区溢出
 size_t AppendFile::write(const char* logline, size_t len) {
     return fwrite_unlocked(logline, 1, len, fp_);
 }
\ No newline at end of file
diff --git a/base/LogFile.h b/base/LogFile.h
index 4f8db3b..1e345c9 100755
--- a/base/LogFile.h
+++ b/base/LogFile.h
@@ -27,6 +27,7 @@ class LogFile : noncopyable {
     const int flushEveryN_;
 
     int count_;
+    // 这个类自带一个锁???
     std::unique_ptr<MutexLock> mutex_;
     std::unique_ptr<AppendFile> file_;
 };
\ No newline at end of file
diff --git a/base/LogStream.cpp b/base/LogStream.cpp
index 95dc328..531aef5 100755
--- a/base/LogStream.cpp
+++ b/base/LogStream.cpp
@@ -13,7 +13,10 @@
 const char digits[] = "9876543210123456789";
 const char* zero = digits + 9;
 
-// From muduo
+// From muduo size_t表示long unsigned int
+// 这个函数的将这个值转换成一个字符串并存储在字符数组buf中。函数的返回值是转换后的字符串的长度
+// 下面调用的时候,都是整型调用,但是这里好像有精度丢失?
+// 而不使用 C++ 标准库中的 itoa 或 snprintf 等函数。这在某些情况下可能有性能优势
 template <typename T>
 size_t convert(char buf[], T value) {
     T i = value;
@@ -86,6 +89,7 @@ LogStream& LogStream::operator<<(unsigned long long v) {
     return *this;
 }
 
+// 写入浮点数为char类型,保留12位小数点
 LogStream& LogStream::operator<<(double v) {
     if (buffer_.avail() >= kMaxNumericSize) {
         int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v);
diff --git a/base/LogStream.h b/base/LogStream.h
index 68258b6..32891ce 100755
--- a/base/LogStream.h
+++ b/base/LogStream.h
@@ -12,6 +12,7 @@ class AsyncLogging;
 const int kSmallBuffer = 4000;
 const int kLargeBuffer = 4000 * 1000;
 
+// FixedBuffer其实就是一个缓冲区类,模板指定大小
 template <int SIZE>
 class FixedBuffer : noncopyable {
    public:
@@ -19,6 +20,7 @@ class FixedBuffer : noncopyable {
 
     ~FixedBuffer() {}
 
+    // 将buf指针中len字节的数据,复制到cur_指针指向的缓冲区中
     void append(const char* buf, size_t len) {
         if (avail() > static_cast<int>(len)) {
             memcpy(cur_, buf, len);
@@ -27,8 +29,10 @@ class FixedBuffer : noncopyable {
     }
 
     const char* data() const { return data_; }
+    // 当前的长度
     int length() const { return static_cast<int>(cur_ - data_); }
 
+    // 当前的指针
     char* current() { return cur_; }
     int avail() const { return static_cast<int>(end() - cur_); }
     void add(size_t len) { cur_ += len; }
@@ -37,6 +41,7 @@ class FixedBuffer : noncopyable {
     void bzero() { memset(data_, 0, sizeof data_); }
 
    private:
+   // 缓冲区的末尾指针
     const char* end() const { return data_ + sizeof data_; }
 
     char data_[SIZE];
@@ -47,6 +52,7 @@ class LogStream : noncopyable {
    public:
     typedef FixedBuffer<kSmallBuffer> Buffer;
 
+    // true就追加一个1?
     LogStream& operator<<(bool v) {
         buffer_.append(v ? "1" : "0", 1);
         return *this;
diff --git a/base/Logging.cpp b/base/Logging.cpp
index 6fe601d..398042b 100755
--- a/base/Logging.cpp
+++ b/base/Logging.cpp
@@ -23,6 +23,7 @@ void once_init() {
 }
 
 void output(const char* msg, int len) {
+    // 这里就确保了AsyncLogger_只会start一次
     pthread_once(&once_control_, once_init);
     AsyncLogger_->append(msg, len);
 }
@@ -38,13 +39,17 @@ void Logger::Impl::formatTime() {
     char str_t[26] = {0};
     gettimeofday(&tv, NULL);
     time = tv.tv_sec;
+    // 将time中的秒,转换为地区时间,保存在tm结构体中
     struct tm* p_time = localtime(&time);
+    // 将tm格式化为字符串,保存在str_t中
     strftime(str_t, 26, "%Y-%m-%d %H:%M:%S\n", p_time);
+    // 这里应该是保存在缓冲区了,最后会flush到文件中
     stream_ << str_t;
 }
 
 Logger::Logger(const char* fileName, int line) : impl_(fileName, line) {}
 
+// 析构的时候再flush一次?
 Logger::~Logger() {
     impl_.stream_ << " -- " << impl_.basename_ << ':' << impl_.line_ << '\n';
     const LogStream::Buffer& buf(stream().buffer());
diff --git a/base/Thread.cpp b/base/Thread.cpp
index f19389e..8af27f8 100755
--- a/base/Thread.cpp
+++ b/base/Thread.cpp
@@ -17,6 +17,7 @@
 #include "CurrentThread.h"
 using namespace std;
 
+// 声明线程局部存储变量
 namespace CurrentThread {
 __thread int t_cachedTid = 0;
 __thread char t_tidString[32];
@@ -24,6 +25,7 @@ __thread int t_tidStringLength = 6;
 __thread const char* t_threadName = "default";
 }  // namespace CurrentThread
 
+// 获取当前线程的PID
 pid_t gettid() { return static_cast<pid_t>(::syscall(SYS_gettid)); }
 
 void CurrentThread::cacheTid() {
@@ -48,13 +50,17 @@ struct ThreadData {
 
     void runInThread() {
         *tid_ = CurrentThread::tid();
+        // 看看tid_  和 pthreadId_是多少
+        std::cout << "tid_ = " << *tid_ << std::endl;
         tid_ = NULL;
         latch_->countDown();
         latch_ = NULL;
 
+        // prctl系统调用设置线程名称
         CurrentThread::t_threadName = name_.empty() ? "Thread" : name_.c_str();
         prctl(PR_SET_NAME, CurrentThread::t_threadName);
 
+        // 调用线程函数,执行完了以后名称设置为已完成
         func_();
         CurrentThread::t_threadName = "finished";
     }
@@ -90,14 +96,20 @@ void Thread::setDefaultName() {
     }
 }
 
+// 这里有个疑惑,startThread和latch_.wait()的执行先后问题
+// 有没有可能latch_.wait()的时候已经为null了?是把指针置为null!
 void Thread::start() {
     assert(!started_);
     started_ = true;
     ThreadData* data = new ThreadData(func_, name_, &tid_, &latch_);
+    // 如果线程创建成功,返回0,线程一创建了就阻塞,创建以后会给pthreadId_一个初始化的值
+    std::cout << "创建前pthreadId_ = " << this->pthreadId_ << std::endl;
     if (pthread_create(&pthreadId_, NULL, &startThread, data)) {
         started_ = false;
         delete data;
     } else {
+        std::cout << "创建后pthreadId_ = " << this->pthreadId_ << std::endl;
+        std::cout << "Thread name-->" << this->name_ << std::endl;
         latch_.wait();
         assert(tid_ > 0);
     }
diff --git a/base/Thread.h b/base/Thread.h
index 35c802c..1baf2b1 100755
--- a/base/Thread.h
+++ b/base/Thread.h
@@ -12,6 +12,9 @@
 #include "CountDownLatch.h"
 #include "noncopyable.h"
 
+// tid_是操作系统级别的线程ID,而pthreadId_是pthread库级别的线程标识符
+// 了解每个线程的tid_可以帮助跟踪和调试,而pthreadId_则用于线程的同步和管理。
+
 class Thread : noncopyable {
    public:
     typedef std::function<void()> ThreadFunc;
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..a979aa5
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -x
+
+SOURCE_DIR=`pwd`
+BUILD_DIR=${BUILD_DIR:-../build}
+BUILD_TYPE=${BUILD_TYPE:-Debug}
+
+mkdir -p $BUILD_DIR/$BUILD_TYPE \
+    && cd $BUILD_DIR/$BUILD_TYPE \
+    && cmake \
+            -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+            $SOURCE_DIR \
+    && make $*
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100755
index 0000000..d24d5bd
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.html b/index.html
new file mode 100755
index 0000000..1c8a3a1
--- /dev/null
+++ b/index.html
@@ -0,0 +1 @@
+Hello World !
\ No newline at end of file
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 0000000..a15cdfb
Binary files /dev/null and b/screenshot.png differ
diff --git a/tests/HTTPClient.cpp b/tests/HTTPClient.cpp
index f7219eb..bd18903 100755
--- a/tests/HTTPClient.cpp
+++ b/tests/HTTPClient.cpp
@@ -16,7 +16,7 @@ using namespace std;
 
 #define MAXSIZE 1024
 #define IPADDRESS "127.0.0.1"
-#define SERV_PORT 8888
+#define SERV_PORT 8080
 #define FDSIZE 1024
 #define EPOLLEVENTS 20
 
@@ -52,13 +52,13 @@ int main(int argc, char *argv[]) {
     const char *p = " ";
     if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
         setSocketNonBlocking1(sockfd);
-        cout << "1:" << endl;
+        cout << "first:" << endl;
         ssize_t n = write(sockfd, p, strlen(p));
         cout << "strlen(p) = " << strlen(p) << endl;
         sleep(1);
         n = read(sockfd, buff, 4096);
         cout << "n=" << n << endl;
-        printf("%s", buff);
+        cout << buff << endl;
         close(sockfd);
     } else {
         perror("err1");
@@ -70,13 +70,13 @@ int main(int argc, char *argv[]) {
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
         setSocketNonBlocking1(sockfd);
-        cout << "2:" << endl;
+        cout << "second:" << endl;
         ssize_t n = write(sockfd, p, strlen(p));
         cout << "strlen(p) = " << strlen(p) << endl;
         sleep(1);
         n = read(sockfd, buff, 4096);
         cout << "n=" << n << endl;
-        printf("%s", buff);
+        cout << buff << endl;
         close(sockfd);
     } else {
         perror("err2");
@@ -88,18 +88,18 @@ int main(int argc, char *argv[]) {
     // Host: 192.168.52.135:8888
     // Content-Type: application/x-www-form-urlencoded
     // Connection: Keep-Alive
-    p = "GET / HTTP/1.1\r\nHost: 192.168.52.135:8888\r\nContent-Type: "
+    p = "GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nContent-Type: "
         "application/x-www-form-urlencoded\r\nConnection: Keep-Alive\r\n\r\n";
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
         setSocketNonBlocking1(sockfd);
-        cout << "3:" << endl;
+        cout << "third:" << endl;
         ssize_t n = write(sockfd, p, strlen(p));
         cout << "strlen(p) = " << strlen(p) << endl;
         sleep(1);
         n = read(sockfd, buff, 4096);
         cout << "n=" << n << endl;
-        printf("%s", buff);
+        cout << buff << endl;
         close(sockfd);
     } else {
         perror("err3");
diff --git "a/\345\271\266\345\217\221\346\250\241\345\236\213.md" "b/\345\271\266\345\217\221\346\250\241\345\236\213.md"
new file mode 100755
index 0000000..d64d3db
--- /dev/null
+++ "b/\345\271\266\345\217\221\346\250\241\345\236\213.md"
@@ -0,0 +1,55 @@
+# 并发模型
+
+程序使用Reactor模型,并使用多线程提高并发度。为避免线程频繁创建和销毁带来的开销,使用线程池,在程序的开始创建固定数量的线程。使用epoll作为IO多路复用的实现方式。
+
+## 线程
+一般而言,多线程服务器中的线程可分为以下几类:  
+
+* IO线程(负责网络IO)
+* 计算线程(负责复杂计算)
+* 第三方库所用线程
+
+本程序中的Log线程属于第三种,其它线程属于IO线程,因为Web静态服务器计算量较小,所以没有分配计算线程,减少跨线程分配的开销,让IO线程兼顾计算任务。除Log线程外,每个线程一个事件循环,遵循One loop per thread。
+
+## 并发模型
+本程序使用的并发模型如下图所示:
+
+![并发模型](https://github.com/linyacool/WebServer/blob/master/datum/model.png)
+
+MainReactor只有一个,负责响应client的连接请求,并建立连接,它使用一个NIO Selector。在建立连接后用Round Robin的方式分配给某个SubReactor,因为涉及到跨线程任务分配,需要加锁,这里的锁由某个特定线程中的loop创建,只会被该线程和主线程竞争。
+
+SubReactor可以有一个或者多个,每个subReactor都会在一个独立线程中运行,并且维护一个独立的NIO Selector。
+
+当主线程把新连接分配给了某个SubReactor,该线程此时可能正阻塞在多路选择器(epoll)的等待中,怎么得知新连接的到来呢?这里使用了eventfd进行异步唤醒,线程会从epoll_wait中醒来,得到活跃事件,进行处理。
+
+我学习了muduo库中的runInLoop和queueInLoop的设计方法,这两个方法主要用来执行用户的某个回调函数,queueInLoop是跨进程调用的精髓所在,具有极大的灵活性,我们只需要绑定好回调函数就可以了,我仿照muduo实现了这一点。
+
+## epoll工作模式
+epoll的触发模式在这里我选择了ET模式,muduo使用的是LT,这两者IO处理上有很大的不同。ET模式要比LE复杂许多,它对用户提出了更高的要求,即每次读,必须读到不能再读(出现EAGAIN),每次写,写到不能再写(出现EAGAIN)。而LT则简单的多,可以选择也这样做,也可以为编程方便,比如每次只read一次(muduo就是这样做的,这样可以减少系统调用次数)。
+
+## 定时器
+
+每个SubReactor持有一个定时器,用于处理超时请求和长时间不活跃的连接。muduo中介绍了时间轮的实现和用stl里set的实现,这里我的实现直接使用了stl里的priority_queue,底层是小根堆,并采用惰性删除的方式,时间的到来不会唤醒线程,而是每次循环的最后进行检查,如果超时了再删,因为这里对超时的要求并不会很高,如果此时线程忙,那么检查时间队列的间隔也会短,如果不忙,也给了超时请求更长的等待时间。
+
+## 核心结构
+
+程序中的每一个类和结构体当然都必不可少,其中能体现并发模型和整体架构的,我认为是有两个:
+
+* Channel类:Channel是Reactor结构中的“事件”,它自始至终都属于一个EventLoop,负责一个文件描述符的IO事件,在Channel类中保存这IO事件的类型以及对应的回调函数,当IO事件发生时,最终会调用到Channel类中的回调函数。因此,程序中所有带有读写时间的对象都会和一个Channel关联,包括loop中的eventfd,listenfd,HttpData等。
+* EventLoop:One loop per thread意味着每个线程只能有一个EventLoop对象,EventLoop即是时间循环,每次从poller里拿活跃事件,并给到Channel里分发处理。EventLoop中的loop函数会在最底层(Thread)中被真正调用,开始无限的循环,直到某一轮的检查到退出状态后从底层一层一层的退出。
+
+## Log
+Log的实现了学习了muduo,Log的实现分为前端和后端,前端往后端写,后端往磁盘写。为什么要这样区分前端和后端呢?因为只要涉及到IO,无论是网络IO还是磁盘IO,肯定是慢的,慢就会影响其它操作,必须让它快才行。  
+
+这里的Log前端是前面所述的IO线程,负责产生log,后端是Log线程,设计了多个缓冲区,负责收集前端产生的log,集中往磁盘写。这样,Log写到后端是没有障碍的,把慢的动作交给后端去做好了。
+
+后端主要是由多个缓冲区构成的,集满了或者时间到了就向文件写一次。采用了muduo介绍了“双缓冲区”的思想,实际采用4个多的缓冲区(为什么说多呢?为什么4个可能不够用啊,要有备无患)。4个缓冲区分两组,每组的两个一个主要的,另一个防止第一个写满了没地方写,写满或者时间到了就和另外两个交换**指针**,然后把满的往文件里写。
+
+与Log相关的类包括FileUtil、LogFile、AsyncLogging、LogStream、Logging。
+其中前4个类每一个类都含有一个append函数,Log的设计也是主要围绕这个**append**函数展开的。
+
+* FileUtil是最底层的文件类,封装了Log文件的打开、写入并在类析构的时候关闭文件,底层使用了标准IO,该append函数直接向文件写。
+* LogFile进一步封装了FileUtil,并设置了一个循环次数,每过这么多次就flush一次。
+* AsyncLogging是核心,它负责启动一个log线程,专门用来将log写入LogFile,应用了“双缓冲技术”,其实有4个以上的缓冲区,但思想是一样的。AsyncLogging负责(定时到或被填满时)将缓冲区中的数据写入LogFile中。
+* LogStream主要用来格式化输出,重载了<<运算符,同时也有自己的一块缓冲区,这里缓冲区的存在是为了缓存一行,把多个<<的结果连成一块。
+* Logging是对外接口,Logging类内涵一个LogStream对象,主要是为了每次打log的时候在log之前和之后加上固定的格式化的信息,比如打log的行、文件名等信息。
\ No newline at end of file
diff --git "a/\346\265\213\350\257\225\345\217\212\346\224\271\350\277\233.md" "b/\346\265\213\350\257\225\345\217\212\346\224\271\350\277\233.md"
new file mode 100755
index 0000000..7f4ee8f
--- /dev/null
+++ "b/\346\265\213\350\257\225\345\217\212\346\224\271\350\277\233.md"
@@ -0,0 +1,50 @@
+# 测试及改进
+
+## 测试环境
+* OS:Ubuntu 14.04
+* 内存:8G
+* CPU:I7-4720HQ
+
+## 测试方法
+* 理想的测试环境是两台计算机,带宽无限,现在的网卡虽然都是千兆网卡,但是普通家用的网线都是5类双绞线,最高100Mbps,在linux下用ethtool可以看到网卡的速度被限制为100Mbsp,无法更改为更高的,经测试很容易跑满带宽,因此退而选择本地环境。
+* 使用工具Webbench,开启1000客户端进程,时间为60s
+* 分别测试短连接和长连接的情况
+* 关闭所有的输出及Log
+* 为避免磁盘IO对测试结果的影响,测试响应为内存中的"Hello World"字符加上必要的HTTP头
+* 我的最终版本中很多方面借鉴了muduo的思路,muduo中也提供了一个简单的HTTP echo测试,因此我将与muduo进行一个小小的对比,我修改了muduo测试的代码,使其echo相同的内容,关闭muduo的所有输出及Log
+* 线程池开启4线程
+* 因为发送的内容很少,为避免发送可能的延迟,关闭Nagle算法
+
+
+## 测试结果及分析
+测试截图放在最后  
+
+| 服务器 | 短连接QPS | 长连接QPS | 
+| - | :-: | -: | 
+| WebServer | 126798| 335338 | 
+| Muduo | 88430 | 358302 | 
+
+* 首先很明显的一点是长链接能处理的请求数是短连接的三四倍,因为没有了连接建立和断开的开销,不需要频繁accept和shutdown\close等系统调用,也不需要频繁建立和销毁对应的结构体。
+* 我的服务器在最后的版本中,没有改进输入输出Buffer,用了效率低下的string,muduo用的是设计良好的vector<char>,我将在后续改进这一点。这也造成了在长连接的情况下,我的server逊于muduo。虽说边沿触发效率高一点,但是还是比不过在Buffer上性能的优化的。
+* 短链接的情况下,我的服务器要超过Muduo很多。原因在于:Muduo采用水平触发方式(Linux下用epoll),并且做法是每次Acceptor只accept一次就返回,面对突然的并发量,必然会因为频繁的epoll_wait耽误大量的时间,而我的做法是用while包裹accept,一直accept到不能再accept。当然,如果同时连接的请求很少,陈硕在书中也提到过,假如一次只有一个连接,那么我的方式就会多一次accpet才能跳出循环,但是这样的代价似乎微不足道啊,换来的效率却高了不少。
+* 空闲时,Server几乎不占CPU,短连接时,各线程的CPU负载比较均衡,长连接时,主线程负载0,线程池的线程负载接近100%,因为没有新的连接需要处理。各种情况均正常。
+* 没有严格的考证,测试时发现,HTTP的header解析的结果用map比用unordered_map快,网上的博客里有很多人做了测试,我在做实验的时候大致也发现了。主要是因为数据量太小,一个HTTP请求头才几个头部字段,建立unordered_map的成本要比map高,数据量小,复杂度根本体现不出来。
+
+
+
+## 测试结果截图
+* WebServer短连接测试  
+![shortWeb](https://github.com/linyacool/WebServer/blob/master/datum/WebServer.png)
+* muduo短连接测试  
+![shortMuduo](https://github.com/linyacool/WebServer/blob/master/datum/muduo.png)
+* WebServer长连接测试  
+![keepWeb](https://github.com/linyacool/WebServer/blob/master/datum/WebServerk.png)
+* muduo长连接测试  
+![keepMuduo](https://github.com/linyacool/WebServer/blob/master/datum/muduok.png)
+* WebServer空闲负载  
+![idle](https://github.com/linyacool/WebServer/blob/master/datum/idle.png)
+* WebServer短连接CPU负载  
+![short](https://github.com/linyacool/WebServer/blob/master/datum/close.png)
+* WebServer长连接CPU负载  
+![keep](https://github.com/linyacool/WebServer/blob/master/datum/keepalive.png)
+
diff --git "a/\347\211\210\346\234\254\345\216\206\345\217\262.md" "b/\347\211\210\346\234\254\345\216\206\345\217\262.md"
new file mode 100755
index 0000000..36176ca
--- /dev/null
+++ "b/\347\211\210\346\234\254\345\216\206\345\217\262.md"
@@ -0,0 +1,61 @@
+# 版本历史
+从0.1最初形成到一点一点改进到0.6,到最终看了muduo,痛下决心重写,最终的版本完全从头再来了,但有了前面的经验,写起来顺畅了不少,但花了比之前所有加起来还要长的时间
+## 0.1
+
+第一版是看了很多Github上别人写的服务器,以及博客上的一些总结,结合自己的理解写出来的。模型结构如下:
+
+* 使用了epoll边沿触发+EPOLLONESHOT+非阻塞IO
+* 使用了一个固定线程数的线程池
+* 实现了一个任务队列,由条件变量触发通知新任务的到来
+* 实现了一个小根堆的定时器及时剔除超时请求,使用了STL的优先队列来管理定时器
+* 解析了HTTP的get、post请求,支持长短连接
+* mime设计为单例模式
+* 线程的工作分配为:
+    * 主线程负责等待epoll中的事件,并把到来的事件放进任务队列,在每次循环的结束剔除超时请求和被置为删除的时间结点
+    * 工作线程阻塞在条件变量的等待中,新任务到来后,某一工作线程会被唤醒,执行具体的IO操作和计算任务,如果需要继续监听,会添加到epoll中  
+
+* 锁的使用有两处:
+    * 第一处是任务队列的添加和取操作,都需要加锁,并配合条件变量,跨越了多个线程。
+    * 第二处是定时器结点的添加和删除,需要加锁,主线程和工作线程都要操作定时器队列。
+
+
+
+第一版的服务器已经相对较完整了,该有的功能都已经具备了
+
+## 0.2
+
+在第一版的基础上,优化了代码结构,自己设计了RAII锁机制,使锁能够自动释放,并修复了一些小的bug
+
+## 0.3
+
+* 几乎全部的裸指针被智能指针替代
+* 利用weak_ptr解决时间结点和HTTP类互指的问题
+* 任务队列的任务结构从函数指针+参数指针转换为C++11的function  
+
+这一版还是花了不少时间的,毕竟对象的生命周期不由自己控制了
+
+## 0.4
+
+这个时候买了陈硕的《Linux多线程服务端编程》书,看了一部分,从前面几章获得启发
+* 为不提供拷贝构造和赋值运算符的类添加了noncopyable基类
+* 重写了RAII机制的锁,学习muduo中的做法
+* 重写了单例模式,将双重加锁改为更简单而安全的pthread_once()
+
+## 0.5
+
+* 修复了一些bug,稍微调整了类的结构
+* 封装了条件变量
+
+## 0.6
+
+* 仿照muduo,写了4个缓冲区的异步Log日志,没有区分优先级,其它基本都具备了
+
+## WebServer(重构)
+
+不知道该给自己的服务器取什么名字好,随便叫个吧……最后一版被我改的面目全非,也是下了很大的决心。之前的版本无非修修补补,算是自我检讨的过程,但是闭门造车并不可取,于是我把陈硕的《Linux多线程服务端编程》看完了,书上虽然贴了部分源码,但我看的还是朦朦胧胧,很多地方不明白,花了几天时间把源码看了,不懂的地方再回过来看书,总算是弄明白了。看了大牛的代码,再看自己的……哎我重写总行了吧
+
+顺便吐槽一下自己,之前在知乎上看到陈硕一直推销自己的书,我还觉得这人好功利,后来被师兄推荐,看了一下目录,觉得可以参考一下就买了。没想到书就一点一点这么看完了……还看了好几遍,源码也是看了好几遍……
+
+当然,这不是最终篇,可改进的地方还有很多,绝对不敢说看了几本书就敢说自己写的东西比大牛写的好,我还会继续改进自己的server的
+
+最后版本的东西没有在这里介绍,写在了模型结构里,这里只想写一下自己的心路历程,记录一下小白成长之路~
\ No newline at end of file
diff --git "a/\350\277\236\346\216\245\347\232\204\347\273\264\346\212\244.md" "b/\350\277\236\346\216\245\347\232\204\347\273\264\346\212\244.md"
new file mode 100755
index 0000000..d4562bb
--- /dev/null
+++ "b/\350\277\236\346\216\245\347\232\204\347\273\264\346\212\244.md"
@@ -0,0 +1,33 @@
+# 连接维护(针对非阻塞IO)
+
+### 建立连接
+
+* 建立连接的过程  
+连接的建立比较简单,server端通过socket(),bind(),listen(),并使用epoll ET模式监听listenfd的读请求,当TCP连接完成3次握手后,会触发listenfd的读事件,应用程序调用accept(),会检查已完成的连接队列,如果队列里有连接,就返回这个连接,出错或连接为空时返回-1。此时,已经可以进行正常的读写操作了。 当然,因为是ET模式,accept()要一直循环到就绪连接为空。
+* 分析  
+之所以说建立连接的过程比较简单,是因为数据的通信已经由操作系统帮我们完成了,这里的通信是指3次握手的过程,这个过程不需要应用程序参与,当应用程序感知到连接时,此时该连接已经完成了3次握手的过程,accept就好了。另一个原因是一般情况下,连接的建立都是client发起的,server端被动建立连接就好了,也不会出现同时建立的情况。
+* 限制  
+假设server只监听一个端口,一个连接就是一个四元组(原ip,原port,对端ip, 对端port),那么理论上可以建立2^48个连接,可是,fd可没有这么多(操作系统限制、用户进程限制)。当连接满了,如果空等而不连接,那么就绪队列也满了后,会导致新连接无法建立。这里的做法我参考了muduo,准备一个空的文件描述符,accept()后直接close(),这样对端不会收到RST,至少可以知道服务器正在运行。
+
+### 关闭连接
+
+相对于连接的建立,关闭连接则复杂的多,远不是一个close()那么简单,关闭连接要优雅。
+
+##### 什么时候关闭连接?
+通常server和client都可以主动发Fin来关闭连接  
+
+* 对于client(非Keep-Alive),发送完请求后就可以shutdown()写端,然后收到server发来的应答,最后close掉连接。也可以不shutdown()写,等读完直接close。对于Keep-Alive的情况,就要看client的心情了,收到消息后可以断,也可以不断,server应该保证不主动断开。
+
+* 对于server端,毫无疑问应该谨慎处理以上所有情况。具体说来:
+> * 出现各种关于连接的错误时,可以直接close()掉
+> * 短连接超时的请求,可以close(),也可以不关
+> * 长连接对方长时间没有请求(如果没有保活机制),可以close(),也可以不关
+> * client发出Fin,server会收到0字节,通常不能判断client是close了还是shutdown,这时server应当把消息发完,然后才可以close(),如果对方调用的是close,会收到RST,server能感知到,就可以立即close了
+> * 短连接正常结束,server可以close,也可以不close,大多数的实现是不close的(对HTTP1.1而言)
+
+
+##### EPOLLIN触发但是read()返回0的情况
+
+这种情况通常有两个原因:
+> * 对端已经关闭了连接,这时再写该fd会出错,此时应该关闭连接
+> * 对端只是shutdown()了写端,告诉server我已经写完了,但是还可以接收信息。server应该在写完所有的信息后再关闭连接。更优雅的做法是透明的传递这个行为,即server顺着关闭读端,然后发完数据后关闭。
diff --git "a/\351\201\207\345\210\260\347\232\204\345\233\260\351\232\276.md" "b/\351\201\207\345\210\260\347\232\204\345\233\260\351\232\276.md"
new file mode 100755
index 0000000..fa9b901
--- /dev/null
+++ "b/\351\201\207\345\210\260\347\232\204\345\233\260\351\232\276.md"
@@ -0,0 +1,18 @@
+# 遇到的困难
+
+## 1. 如何设计各个线程个任务
+其实我觉的实现上的困难都不算真正的困难吧,毕竟都能写出来,无非是解决bug花的时间的长短。  
+我遇到的最大的问题是不太理解One loop per thread这句话吧,翻译出来不就是每个线程一个循环,我最开始写的也是一个线程一个循环啊,muduo的实现和我的有什么区别呢?还有怎么设计才能减少竞态?
+
+带着这些问题我看了《Linux多线程服务端编程》,并看完了muduo的源码,这些问题自然而然就解决了
+
+
+## 2. 异步Log几秒钟才写一次磁盘,要是coredump了,这段时间内产生的log我去哪找啊?
+
+其实这个问题非常简单了,也没花多少时间去解决,但我觉的非常好玩。coredump了自然会保存在core文件里了,无非就是把它找出来的问题了,在这里记录一下。
+
+当然这里不管coredump的原因是什么,我只想看丢失的log。所以模拟的话在某个地方abort()就行
+
+多线程调试嘛,先看线程信息,info thread,找到我的异步打印线程,切换进去看bt调用栈,正常是阻塞在条件变量是wait条件中的,frame切换到threadFunc(这个函数是我的异步log里面的循环的函数名),剩下的就是print啦~不过,我的Buffer是用智能指针shared_ptr包裹的,直接->不行,gdb不识别,优化完.get()不让用,可能被inline掉了,只能直接从shared_ptr源码中找到_M_ptr成员来打印。
+
+![gdb](https://github.com/linyacool/WebServer/blob/master/datum/gdb.png)
\ No newline at end of file
diff --git "a/\351\241\271\347\233\256\347\233\256\347\232\204.md" "b/\351\241\271\347\233\256\347\233\256\347\232\204.md"
new file mode 100755
index 0000000..e3fdb49
--- /dev/null
+++ "b/\351\241\271\347\233\256\347\233\256\347\232\204.md"
@@ -0,0 +1,21 @@
+# 项目目的
+---
+### 最初的想法
+本项目是我在三星电子(中国)研发中心实习期间利用晚上和周末的时间完成的,实习期间我负责4K分辨率双鱼眼摄像头视频拼接的算法设计与实现,我希望能把其中的图像拼接部分的成果通过Web的方式展示出来,但因为涉及保密协议,不得不放弃这一想法。
+
+### Web服务器能够很好的贯穿所学的知识
+但是,Web服务器能够很好的贯穿之前所学的知识,之前看过的《C++ Primer》、《Effevtive C++》、《STL源码剖析》、《深度探索C++对象模型》、《TCP\IP详解卷1》、APUE、UNP,还包括了《后台开发核心技术与应用实践》等书,涵盖了  
+  
+* TCP、HTTP协议
+* 多进程多线程
+* IO
+* 锁
+* 通信
+* C++语法
+* 编程规范
+* Linux环境下各种工具的使用
+* 版本控制Git
+* Makefile和CMakeLists文件的编写
+* 自动化构建工具Travis CI的使用
+  
+最终的版本在很多方面学习了muduo网络库,在看完陈硕的《Linux多线程服务端编程》后,对照着书把muduo的源码读了几遍,并重构了自己的服务器,最终的很多想法借鉴了muduo的思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值