webServer-note

杂项

项目中为什么要处理SIGPIPE信号?

ref

Unix的五种IO模型?

  • 阻塞IO
  • 非阻塞IO,轮询什么时候可以读写(即什么时候数据从网卡抵达内核缓冲区),系统调用开销大
  • IO多路复用,为了使得一个线程可以同时处理多个请求
  • 信号驱动式IO,内核会告诉程序什么时候可以读写
  • 异步IO,内核会告诉程序什么时候读写好了

两种事件处理模式?

  • Reactor模式:主线程只负责监听读写事件,工作线程负责读写以及业务处理
  • Proactor模式:主线程负责监听以及读写,工作线程只负责业务处理
    还需要注意两种模式下,线程池的run函数写法不同

讲讲Select,poll,epoll的区别?

  • 对于select和poll,每次都要把文件描述符数组copy到内核,而epoll是每次添加或删除一个内核维护的文件描述符数组中的一个元素,则会导致如果短时间有多次活跃链接,会导致epoll效果比另两者效果差
  • select用数组维护fd,poll用链表维护,epoll在内核中用红黑树维护,并将就绪的fd放入ready-list返还给用户程序

怎么实现ET和LT的?

实现ET和LT需要注意ET模式下,多个socket请求连接时,epoll仍然只会发出一次连接事件,所以需要循环使用accept防止遗漏某些连接请求。同时ET模式决定了每次读写就要全部读完,而LT模式下可以不需要全部读写完

用了什么方法来优化呢?

维护了一个双向链表并使用定时器来淘汰不活跃的连接,注册SIGALRM信号与SIGTERM信号以及sig_handler函数,并创建管道用于接收SIG_ALRM信号,当一定时间后连接仍然不活跃,则将该socket对应的timer从链表中删除,其中删除是O(1)的操作,加入链表则为O(n),可以考虑使用小根堆使得删除与增加操作均为O(logn)

项目流程图

HTTP相关

HTTP报文

  • 请求报文:请求行+请求头部+空行+请求数据
    1
    2
    3
    4
    5
    6
    7
    POST /user HTTP/1.1                       // 请求行
    Host: www.user.com
    Content-Type: application/x-www-form-urlencoded
    Connection: Keep-Alive
    User-agent: Mozilla/5.0. // 以上是请求头
    (此处必须有一空行 | // 空行分割header和请求内容
    name=world // 请求体(可选,如get请求时可选)
  • 响应报文:状态行+响应头部+空行+响应数据
    1
    2
    3
    4
    HTTP/1.1 304 Not Modified
    Date:Sat, 15 Oct 2011 15:39:29
    (空行)
    (空响应体)

HTTP处理流程

  1. 连接处理,主线程用于连接请求,并根据事务模型决定是否读写完再交给工作线程
  2. 解析请求报文
    使用主从状态机解析报文,主状态机为FUNC:process_read,从状态机为FUNC:parse_line,主状态机有三个状态分别对应处理请求行,请求头,和请求数据,从状态机用于读取报文的一行数据(将其中的\r,\n替换成\0
  3. 返回响应报文
    使用iovec数组以及writev函数来支持多个缓冲区的写入,从而将响应头与响应数据一并写入到套接字中

日志系统

使用单例模式创建日志系统,并可以选择同步或异步写入(使用生产者-消费者模型封装一个阻塞队列)

单例模式

通用实现思路:私有化其构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例

  1. 经典的线程安全懒汉模式(懒指的是在用的时候才去尝试初始化)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    single* single::p = NULL;

    single* single::getinstance(){
    if (NULL == p){
    pthread_mutex_lock(&lock);
    if (NULL == p){
    p = new single;
    }
    pthread_mutex_unlock(&lock);
    }
    return p;
    }
  2. 局部静态变量之线程安全懒汉模式(C++11后可用)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class single{
    private:
    single(){}
    ~single(){}

    public:
    static single* getinstance();
    };

    single* single::getinstance(){
    static single obj;
    return &obj;
    }
  3. 饿汉模式
    程序开始时就定义了对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class single{
    private:
    static single* p;
    single(){}
    ~single(){}

    public:
    static single* getinstance();
    };

    single* single::p = new single();

    single* single::getinstance(){
    return p;
    }

    //测试方法
    int main(){

    single *p1 = single::getinstance();
    single *p2 = single::getinstance();

    if (p1 == p2)
    cout << "same" << endl;

    system("pause");
    return 0;
    }

多线程编程注意点

  1. pthread_cond_wait函数需要传入一个互斥锁作为参数,需要注意的是此函数内部会执行一次解锁,加锁过程,解锁是为了防止产生死锁。内部操作分为以下几步
  • 将线程放在条件变量的请求队列中,内部解锁
  • 线程等待被pthread_cond_broadcast信号或者pthread_cond_signal信号唤醒,唤醒后去竞争锁
  • 若竞争到互斥锁,则内部再次加锁
  1. 为什么在生产者-消费者模型中,消费者需要使用while
    一般来说,在多线程资源竞争的时候,在一个使用资源的线程里面(消费者)判断资源是否可用,不可用,便调用pthread_cond_wait,在另一个线程里面(生产者)如果判断资源可用的话,则调用pthread_cond_signal发送一个资源可用信号。
    wait成功之后,资源就一定可以被使用么?答案是否定的,如果同时有两个或者两个以上的线程正在等待此资源,wait返回后,资源可能已经被使用了。
    再具体点,有可能多个线程都在等待这个资源可用的信号,信号发出后只有一个资源可用,但是有A,B两个线程都在等待,B比较速度快,获得互斥锁,然后加锁,消耗资源,然后解锁,之后A获得互斥锁,但A回去发现资源已经被使用了,它便有两个选择,一个是去访问不存在的资源,另一个就是继续等待,那么继续等待下去的条件就是使用while,要不然使用if的话pthread_cond_wait返回后,就会顺序执行下去。
  2. 生产者-消费者模型以《Unix环境高级编程》为准
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #include <pthread.h>

    struct msg {
    struct msg *m_next;
    /* value...*/
    };

    struct msg* workq;
    pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

    void process_msg() {
    struct msg* mp;
    for (;;) {
    pthread_mutex_lock(&qlock);
    while (workq == NULL) {
    pthread_cond_wait(&qready, &qlock);
    }
    mp = workq;
    workq = mp->m_next;
    pthread_mutex_unlock(&qlock);
    /* now process the message mp */
    }
    }

    void enqueue_msg(struct msg* mp) {
    pthread_mutex_lock(&qlock);
    mp->m_next = workq;
    workq = mp;
    pthread_mutex_unlock(&qlock);
    /** 此时另外一个线程在signal之前,执行了process_msg,刚好把mp元素拿走*/
    pthread_cond_signal(&qready);
    /** 此时执行signal, 在pthread_cond_wait等待的线程被唤醒,
    但是mp元素已经被另外一个线程拿走,所以,workq还是NULL ,因此需要继续等待*/
    }

webServer-note
http://bugeater.space/2024/02/18/webServer-note/
Author
BugEater
Posted on
February 18, 2024
Licensed under