webserver(1)

Web Server(1)

主要内容+技术重难点

一、代码+注释,可以运行通过(初始版1)

locker.h 多线程必须加锁操作

#ifndef LOCKER_H

#define LOCKER_H

#include <pthread.h>
#include <exception>
#include <semaphore.h>

// 线程同步机制封装类

// 互斥锁类
class locker {
public:
    locker() {
        if (pthread_mutex_init(&m_mutex, NULL) != 0) {
            throw std::exception();
        }
    }
    ~locker() {
        pthread_mutex_destroy(&m_mutex);
    }

    bool lock() {
        return pthread_mutex_lock(&m_mutex) == 0;
    }

    bool unlock() {
        return pthread_mutex_unlock(&m_mutex) == 0;
    }

    pthread_mutex_t * get() {
        return &m_mutex;
    }

private:
    pthread_mutex_t m_mutex;
};

// 条件变量类
class cond {
public:
    cond() {
        if (pthread_cond_init(&m_cond, NULL) != 0) {
            throw std::exception();
        }
    }
    ~cond() {
        pthread_cond_destroy(&m_cond);
    }
    bool wait(pthread_mutex_t * mutex) {
        return pthread_cond_wait(&m_cond, mutex) == 0;
    }
    bool timedwait(pthread_mutex_t * mutex, struct timespec t) {
        return pthread_cond_timedwait(&m_cond, mutex, &t) == 0;
    }
    bool signal() {
        return pthread_cond_signal(&m_cond) == 0;
    }
    bool broadcast() {
        return pthread_cond_broadcast(&m_cond) == 0;
    }

private:
    pthread_cond_t m_cond;
};

// 信号量类
class sem {
public:
    sem() {
        if( sem_init( &m_sem, 0, 0 ) != 0 ) {
            throw std::exception();
        }
    }
    sem(int num) {
        if( sem_init( &m_sem, 0, num ) != 0 ) {
            throw std::exception();
        }
    }
    ~sem() {
        sem_destroy( &m_sem );
    }
    // 等待信号量
    bool wait() {
        return sem_wait( &m_sem ) == 0;
    }
    // 增加信号量
    bool post() {
        return sem_post( &m_sem ) == 0;
    }
private:
    sem_t m_sem;
};

#endif

threadpool.h 线程池综合管理,单个操作开销大风险高

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <pthread.h>
#include <list>
#include "locker.h"
#include <exception>
#include <cstdio>

// 线程池类,定义成模板类是为了复用,模板参数T是任务类
template<typename T>
class threadpool {
public:
    threadpool(int thread_number = 8, int max_requests = 10000);
    ~threadpool();
    bool append(T* request);

private:
    static void* worker(void * arg);
    void run();

private:
    // 线程的数量
    int m_thread_number;
    // 线程池数组,大小为m_thread_number;
    pthread_t * m_threads;
    // 请求队列中最多允许的,等待处理的请求数量
    int m_max_requests;
    // 请求队列
    std::list< T*> m_workqueue;
    // 互斥锁
    locker m_queuelocker;
    // 信号量用来判断是否有任务需要处理
    sem m_queuestat;
    // 是否结束线程
    bool m_stop;
};

template<typename T>
threadpool<T>::threadpool(int thread_number, int max_requests) :
    m_thread_number(thread_number), m_max_requests(max_requests),
    m_stop(false), m_threads(NULL) {

    if ((thread_number <= 0) || (max_requests <= 0)) {
        throw std::exception();
    }
    m_threads = new pthread_t[m_thread_number];
    if (!m_threads) {
        throw std::exception();
    }
    // 创建thread_number个线程,并将它们设置成线程脱离
    for (int i = 0; i < thread_number; ++ i) {
        printf("create the %dth trhead\n", i);
        if (pthread_create(m_threads + i, NULL, worker, this) != 0) {
            delete [] m_threads;
            throw std::exception();
        }
        if (pthread_detach(m_threads[i])) {
            delete[] m_threads;
            throw std::exception();
        }
    }
}

template<typename T>
threadpool<T>::~threadpool() {
    delete[] m_threads;
    m_stop = true;
}

template<typename T>
bool threadpool<T>::append(T * request) {
    m_queuelocker.lock();
    if (m_workqueue.size() > m_max_requests) {
        m_queuelocker.unlock();
        return false;
    }
    m_workqueue.push_back(request);
    m_queuelocker.unlock();
    m_queuestat.post();
    return true;
}

template<typename T>
void* threadpool<T>::worker(void * arg) {
    threadpool * pool = (threadpool *) arg;
    pool->run();
    return pool;
}

template<typename T>
void threadpool<T>::run() {
    while(!m_stop) {
        m_queuestat.wait();
        m_queuelocker.lock();
        if(m_workqueue.empty()){
            m_queuelocker.unlock();
            continue;
        }

        T* request = m_workqueue.front();
        m_workqueue.pop_back();
        m_queuelocker.unlock();

        if (!request) {
            continue;
        }
        request->process();
    }
}

#endif

http_conn.h 连接获取响应操作细节

#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H

#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include "locker.h"
#include <sys/uio.h>

class http_conn {
public:
  // 所有的socket上的事件都被注册到一个epoll对象中
  static int m_epollfd; 
  // 统计用户的数量
  static int m_user_count;
  http_conn() {}
  ~http_conn() {}

  void process(); // 处理客户端的请求
  void init(int sockfd, const sockaddr_in & addr); // 初始化连接
  void close_conn(); // 关闭连接
  bool read(); // 非阻塞地读
  bool write(); // 非阻塞地写
private:
  int m_sockfd; // 该HTTP连接的socket
  sockaddr_in m_address; // 通信socket地址
};

#endif

http_conn.cpp 详细写出实现

#include "http_conn.h"

int http_conn::m_epollfd = -1;
int http_conn::m_user_count = 0;

//设置文件描述符非阻塞
void setnonblocking(int fd) {
  int old_flag = fcntl(fd, F_GETFL);
  int new_flag = old_flag | O_NONBLOCK;
  fcntl(fd, F_SETFL, new_flag);
}

// 添加需要监听的文件描述符到epoll中
void addfd(int epollfd, int fd, bool one_shot) {
  epoll_event event;
  event.data.fd = fd;
  event.events = EPOLLIN | EPOLLRDHUP;
  // 2.6.17后版本,对方异常连接断开,可以在底层处理,不需要移交上层
  if (one_shot) {
    event.events | EPOLLONESHOT;
  }
  epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
  // 设置文件描述符非阻塞
  setnonblocking(fd);
}

// 从epoll中删除文件描述符
/**
 * @brief 即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题
 * 比如一个线程在读取某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读
 * (EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据,于是就出现了两个线程同时操作一个socket的局面
 * 一个socket连接在任一时刻都只被一个线程处理,可以使用epoll的EPOLLONESHOT事件实现
 * 
 * 对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件
 * 
 * @param epollfd 
 * @param fd 
 */
void removefd(int epollfd, int fd){
  epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
  close(fd);
}

// 修改文件描述符,重置socket上EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
// 不修改,只触发一次
void modfd(int epollfd, int fd, int ev) {
  epoll_event event;
  event.data.fd = fd;
  event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
  epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

// 初始化连接
void http_conn::init(int sockfd, const sockaddr_in & addr) {
  m_sockfd = sockfd;
  m_address = addr;
  // 端口复用
  int reuse = 1;
  setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
  // 添加到epoll对象中
  addfd(m_epollfd, sockfd, true);
  m_user_count++;//总用户数加1
}

// 关闭连接
void http_conn::close_conn() {
  if (m_sockfd != -1) {
    removefd(m_epollfd, m_sockfd); // 移除
    m_sockfd = -1; // 值赋为-1,就没用了
    m_user_count--; // 关闭一个连接,用户数量减一
  }
}

bool http_conn::read() {
  printf("一次性读完数据");
  return true;
}

bool http_conn::write() {
  printf("一次性写完数据");
  return true;
}

// 由线程池中地工作线程调用,这是处理HTTP请求的入口函数
void http_conn::process() {
  // 解析HTTP请求
  printf("parse request, create response\n");
  // 生成响应
}

main.cpp 主函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include "locker.h"
#include "threadpool.h"
#include <signal.h>
#include "http_conn.h"

#define MAX_FD 65535 // 最大数量
#define MAX_EVENT_NUMBER 10000 // 同时最大监听的事件数量

//添加信号捕捉
void addsig(int sig, void(handler)(int)) {
  struct sigaction sa;
  memset(&sa, '\0', sizeof sa);
  sa.sa_handler = handler;
  sigfillset(&sa.sa_mask);
  sigaction(sig, &sa, NULL);
}

// 添加文件描述符到epoll中
extern void addfd(int epollfd, int fd, bool one_shot);
// 从epoll中删除文件描述符
extern void removefd(int epollfd, int fd);
// 修改文件描述符
extern void modfd(int epollfd, int fd, int ev);

int main(int argc, char* argv[]) {
  if (argc <= 1) {
    printf("按照如下格式运行: %s port_number\n", basename(argv[0]));
    exit(-1);
  }
  // 获取端口号
  int port = atoi(argv[1]);
  // 对SIGPIE信号进行处理
  addsig(SIGPIPE, SIG_IGN);
  // 创建线程池,初始化线程池
  threadpool<http_conn> * pool = NULL;
  try {
    pool = new threadpool<http_conn>;
  } catch(...) {
    exit(-1);
  }

  // 创建一个数组用于保存所有的客户端信息
  http_conn * users = new http_conn[ MAX_FD ];
  // 创建监听的套接字
  int listenfd = socket(PF_INET, SOCK_STREAM, 0);
  // 设置端口复用
  int reuse = 1;
  setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
  // 绑定
  struct sockaddr_in address;
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons(port);
  bind(listenfd, (struct sockaddr*)&address, sizeof(address));
  // 监听
  listen(listenfd, 5);
  // 使用epoll多路复用,创建epoll对象,事件数组,检测到以后写进去,添加监听文件描述符
  epoll_event events[MAX_EVENT_NUMBER];
  int epollfd = epoll_create(5);
  // 将监听的文件描述符添加到epoll对象中
  addfd(epollfd, listenfd, false);
  http_conn::m_epollfd = epollfd;

  while (true) {
    // 主线程循环检测哪些线程发生
    int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
    if ((num < 0) && (errno != EINTR)) {
      // epoll调用失败
      printf("epoll failure\n");
      break;
    }
    // 循环遍历数组
    for (int i = 0; i < num; i ++ ) {
      int sockfd = events[i].data.fd;
      if (sockfd == listenfd) {
        // 有客户端连接进来
        struct sockaddr_in client_address;
        socklen_t client_addrlen = sizeof(client_address);
        int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlen);
        if (http_conn::m_user_count >= MAX_FD) {
          // 目前连接数满了
          // 给客户端写一个信息:服务器内部正忙。
          close(connfd);
          continue;
        }
        // 将新的客户的数据初始化,放到数组中
        users[connfd].init(connfd, client_address);
      }
      else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
        // 对方异常断开或者错误等事件
        // 关闭连接
        users[sockfd].close_conn();
      }
      else if (events[i].events & EPOLLIN) {
        // 判断是否有读的事件发生,一次性把所有读出来
        if (users[sockfd].read()) {
          // 一次性把所有数据都读出来
          pool->append(users + sockfd);
        }
        else {
          // 没读到数据,失败,关闭连接
          users[sockfd].close_conn();
        }
      }
      else if (events[i].events & EPOLLOUT) {
        if (!users[sockfd].write()) {
          // 一次性写完所有数据
          users[sockfd].close_conn(); // 写失败了关闭连接
        }
      }
    }
  }

  close(epollfd);
  close(listenfd);
  delete [] users;
  delete pool;

  return 0;
}

1.Web Server 定义

一个软件程序,或硬件计算机。功能是通过 HTTP 协议与客户端,进行通信,来接收、存储、处理来自客户端 HTTP 请求 Request,并对请求做出 HTTP 响应 Response,返回请求的内容(文件、网页等)或返回 Error 信息。

2.用户如何与服务器通信

浏览器,域名/ip:port,浏览器域名解析成相应 IP 地址或直接根据 ip 地址发送 Http 请求,这一过程首先进行 TCP 的三次握手建立与目标 Web 服务器的连接,然后 HTTP 协议生成针对 Web 服务器的 HTTP 请求报文,通过 TCP,IP 等协议发送到目标 Web Server.

3.WebServer 如何接受 HTTP 请求报文

socket 监听
sys/socket.h

/* 创建监听socket文件描述符 */
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
/* 创建监听socket的TCP/IP的IPV4 socket地址 */
struct sockaddr_in address;

connect() accept() epoll listenfd

Reactor 模式:要求主线程(I/O 处理单元)只负责监听文件描述符上是否有事件发生(可读、可写),若有,则立即通知工作线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程处理。

Proactor 模式:将所有的 I/O 操作都交给主线程和内核来处理(进行读、写),工作线程仅负责处理逻辑,如主线程读完成后 users[sockfd].read(),选择一个工作线程来处理客户请求 pool->append(users + sockfd)。

同步(阻塞)I/O:在一个线程中,CPU 执行代码的速度极快,然而,一旦遇到 IO 操作,如读写文件、发送网络数据时,就需要等待 IO 操作完成,才能继续进行下一步操作。这种情况称为同步 IO。

异步(非阻塞)I/O:当代码需要执行一个耗时的 IO 操作时,它只发出 IO 指令,并不等待 IO 结果,然后就去执行其他代码了。一段时间后,当 IO 返回结果时,再通知 CPU 进行处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值