【slighttpd】基于lighttpd架构的Server项目实战(4)—简单的echo服务器

本文详细介绍了如何将Master和Worker拓展为一个简单的Echo服务器,包括Listener和Connection类的设计与实现。从初始化监听套接字、接受连接到数据的读写处理,再到状态机机制的引入,一步步构建了Echo服务器的基础架构。
摘要由CSDN通过智能技术生成

在这一部分,我们将对上一篇中的master-worker进行拓展,成为一个简单的echo服务器。

这一步我们需要添加两个类:Listener和Connection;

Listener的职责:

  1. 创建监听套接字;
  2. 注册监听套接字事件;
  3. 在监听事件的回调函数中进行accept并创建新连接;

其头文件如下:

/*************************************************************************
    > File Name: listener.h
    > Author: Jiange
    > Mail: jiangezh@qq.com 
    > Created Time: 2016年01月27日 星期三 19时46分34秒
 ************************************************************************/

#ifndef _LISTENER_H
#define _LISTENER_H

#include <string>

#include "event2/event.h"
#include "event2/util.h"

#include "util.h"

class Worker;

class Listener
{
    public:
        Listener(const std::string &ip, unsigned short port);
        ~Listener();

        bool InitListener(Worker *worker);
        void AddListenEvent();

        static void ListenEventCallback(evutil_socket_t fd, short event, void *arg);

        Worker             *listen_worker;
        evutil_socket_t     listen_sockfd;
        struct sockaddr_in  listen_addr;
        struct event       *listen_event;
        uint64_t            cnt_connection;
};


#endif

接下来看看具体实现:

/*************************************************************************
    > File Name: listener.cpp
    > Author: Jiange
    > Mail: jiangezh@qq.com 
    > Created Time: 2016年01月27日 星期三 19时48分56秒
 ************************************************************************/

#include "listener.h"
#include "worker.h"
#include "connection.h"

#include<iostream>

Listener::Listener(const std::string &ip, unsigned short port)
{
    //ipv4
    listen_addr.sin_family      = AF_INET;
    listen_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    listen_addr.sin_port        = htons(port);
    listen_event                = NULL;
    cnt_connection  = 0;
    std::cout << "Init listener" << std::endl;
}


Listener::~Listener()
{
    if (listen_event)
    {
        event_free(listen_event);
        close(listen_sockfd);
    }
    std::cout << "Listener closed" << std::endl;
}

bool Listener::InitListener(Worker *worker)
{
    if (-1 == (listen_sockfd = socket(AF_INET, SOCK_STREAM, 0)))
    {
        return false;
    }

    //非阻塞
    evutil_make_socket_nonblocking(listen_sockfd);

    int reuse = 1;

    //重用
    setsockopt(listen_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    if (0 != bind(listen_sockfd, (struct sockaddr*)&listen_addr, sizeof(listen_addr)))
    {
        return false;
    }
    if (0 != listen(listen_sockfd, 5))
    {
        return false;
    }

    listen_worker = worker;
    return true;
}

/* 这里单独作为一个函数,而不是合并到上面函数中。
 * 因为InitListener是在fork之前调用的,此时
 * worker的w_base还未赋值;
 */
void Listener::AddListenEvent()
{
    //echo先从客户端读取数据,故此处监听读
    listen_event  = event_new(listen_worker->w_base, listen_sockfd, EV_READ | EV_PERSIST, Listener::ListenEventCallback, this);
    event_add(listen_event, NULL);
}

void Listener::ListenEventCallback(evutil_socket_t sockfd, short event, void *arg)
{
    evutil_socket_t con_fd;
    struct sockaddr_in con_addr;
    socklen_t addr_len  = sizeof(con_addr);
    if (-1 == (con_fd = accept(sockfd, (struct sockaddr*)&con_addr, &addr_len)))
    {
        //std::cout << "Thundering herd" <<std::endl;
        return ;
    }

    Listener *listener  = (Listener*)arg;
    Connection *con     = new Connection();

    con->con_sockfd = con_fd;

    pid_t pid = getpid();
    std::cout << "listen accept: " << con->con_sockfd << " by process " << pid <<std::endl;

    if (!con->InitConnection(listener->listen_worker))
    {
        Connection::FreeConnection(con);
        return ;
    }

    con->con_worker->con_map[con->con_sockfd] = con;
    ++listener->cnt_connection;

}

接下来看看Connection:

一个Connection实例即代表一个连接,它维护从listener的回调函数那里accept得到的套接字,并在该套接字上监听读写事件,进行request和response。

/*************************************************************************
    > File Name: connection.h
    > Author: Jiange
    > Mail: jiangezh@qq.com 
    > Created Time: 2016年01月27日 星期三 20时10分35秒
 ************************************************************************/

#ifndef _CONNECTION_H
#define _CONNECTION_H

#include <string>
#include <queue>

#include "event2/event.h"
#include "event2/util.h"

#include "util.h"

class Worker;

class Connection
{
    public:
        Connection();
        ~Connection();

        bool InitConnection(Worker *worker);

        static void ConEventCallback(evutil_socket_t fd, short event, void *arg);

        Worker             *con_worker;

        evutil_socket_t     con_sockfd;
        struct event       *read_event;
        struct event       *write_event;

        std::string         con_inbuf;
        std::string         con_intmp;
        std::string         con_outbuf;

        static void FreeConnection(Connection *con);


    private:
        void WantRead();
        void NotWantRead();
        void WantWrite();
        void NotWantWrite(); 

};

#endif

这里两个event分别负责读写事件,比一个event效率高一些。

在回调函数的设计中,本来打算使用两个回调函数:一个处理读,一个处理写。

不过其实可以合并到同一个回调函数里,所以还是在一个函数中处理,并增加4个函数,来进行监听事件的切换,这样做更有利于后面状态机的拓展:

void Connection::WantRead()
{
    event_add(read_event, NULL); 
}

void Connection::NotWantRead()
{
    event_del(read_event);
}

void Connection::WantWrite()
{
    event_add(write_event, NULL);
}

void Connection::NotWantWrite() 
{
    event_del(write_event);
}

除以上函数外其他部分的具体实现:

/*************************************************************************
    > File Name: connection.cpp
    > Author: Jiange
    > Mail: jiangezh@qq.com 
    > Created Time: 2016年01月28日 星期四 12时06分22秒
 ************************************************************************/

#include "connection.h"
#include "worker.h"

#include<iostream>

Connection::Connection()
{
    con_worker = NULL;

    read_event = NULL;
    write_event= NULL;
}

Connection::~Connection()
{
    if (read_event && write_event)
    {
        event_free(read_event);
        event_free(write_event);
        std::cout << con_sockfd << " closed" << std::endl;
        close(con_sockfd);
    }
}

/* 删除worker中相应的con,并释放该con */
void Connection::FreeConnection(Connection *con)
{
    Worker *worker = con->con_worker;

    if (con->read_event && con->write_event)
    {
        Worker::ConnectionMap::iterator con_iter = worker->con_map.find(con->con_sockfd);
        worker->con_map.erase(con_iter);
    }

    delete con;
}

bool Connection::InitConnection(Worker *worker)
{
    con_worker = worker;

    try
    {   
        //这里不能开太大,会爆内存!
        //后期可能需要在内存的使用上进行优化~
        con_intmp.reserve(10 * 1024);
        con_inbuf.reserve(10 * 1024);
        con_outbuf.reserve(10 * 1024);

        evutil_make_socket_nonblocking(con_sockfd);
        //test:监听读事件,从客户端读,然后回显
        read_event = event_new(con_worker->w_base, con_sockfd, EV_PERSIST | EV_READ, Connection::ConEventCallback, this);
        write_event = event_new(con_worker->w_base, con_sockfd, EV_PERSIST | EV_WRITE, Connection::ConEventCallback, this);
    }
    catch(std::bad_alloc)
    {
        std::cout << "InitConnection():bad_alloc" <<std::endl;
    }
    WantRead();

    return true;
}

/* 循环读写
 * 注意,在读的时候,此处ret为0时,可能是空字符串之类的
 * 所以在这里暂不做处理
 */
void Connection::ConEventCallback(evutil_socket_t sockfd, short event, void *arg)
{

    Connection *con = (Connection*)arg;

    if (event & EV_READ) 
    {
        int cap = con->con_intmp.capacity();
        int ret = read(sockfd, &con->con_intmp[0], cap);

        if (ret == -1)
        {
            if (errno != EAGAIN && errno != EINTR)
            {
                FreeConnection(con);
                return;
            }
        }
        else if (ret == 0)
        {
            FreeConnection(con); 
            return;
        }
        else
        {
            con->con_inbuf.clear();
            con->con_inbuf.append(con->con_intmp.c_str(), ret);
        }
        con->con_outbuf = con->con_inbuf;
        con->NotWantRead();
        con->WantWrite();
    }

    if (event & EV_WRITE)
    {
        int ret = write(sockfd, con->con_outbuf.c_str(), con->con_outbuf.size());

        if (ret == -1)
        {
            if (errno != EAGAIN && errno != EINTR)
            {
                FreeConnection(con);
                return;
            }
        }
        con->NotWantWrite();
        con->WantRead();
    }   
}

介绍完listener和connection之后,我们需要相应地调整master和worker的代码:

1.修改两者的构造函数:

Master::Master(const std::string &ip, unsigned short port)
    :worker(ip, port)
{
    //……
}

Worker::Worker(const std::string &ip, unsigned short port)
        :listener(ip, port)
{
    //……
}

2.在master中开始创建监听套接字:

bool Master::StartMaster()
{
    std::cout << "Start Master" << std::endl;

    if (!worker.listener.InitListener(&worker))
    {
        return false;
    }
    //……
}

3.在worker中增加listener和connection map成员:

class Master;
class Connection;

class Worker
{
    public:
        typedef std::map<evutil_socket_t, Connection*> ConnectionMap;
        //……
        Listener            listener;
        ConnectionMap       con_map;
};

4.worker析构函数释放持有的连接:

Worker::~Worker()
{
    //……

    if (w_base)
    {
        ConnectionMap::iterator con_iter = con_map.begin();
        while (con_iter != con_map.end())
        {
            Connection *con = con_iter->second;
            delete con;
            ++con_iter;
        }
        event_base_free(w_base);
    }
}

5.worker增加监听event事件:

void Worker::Run()
{
    w_base = event_base_new();
    listener.AddListenEvent();
    //……
    return;
}

6.修改main函数中的master构造;

最后,附上一个使用到的头文件:

/*************************************************************************
    > File Name: util.h
    > Author: Jiange
    > Mail: jiangezh@qq.com 
    > Created Time: 2016年01月28日 星期四 10时39分22秒
 ************************************************************************/

#ifndef _UTIL_H
#define _UTIL_H

#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdint.h>

#endif

至此,我们简单的echo服务器就大功告成啦~

下面给出makefile:

THIRD_LIBS=-levent
LIBS=-ldl
CFLAGS=-I./include

master:src/master.o src/worker.o src/listener.o src/connection.o src/main.o
        g++ -g -o $@ src/master.o src/worker.o src/listener.o src/connection.o src/main.o $(THIRD_LIBS) $(LIBS)

src/master.o:src/master.cpp include/master.h
        g++ -g -o $@ -c $< $(CFLAGS)

src/worker.o:src/worker.cpp include/worker.h include/util.h
        g++ -g -o $@ -c $< $(CFLAGS)

src/listener.o:src/listener.cpp include/listener.h include/util.h
        g++ -g -o $@ -c $< $(CFLAGS)

src/connection.o:src/connection.cpp include/connection.h include/util.h
        g++ -g -o $@ -c $< $(CFLAGS)

src/main.o:src/main.cpp include/master.h
        g++ -g -o $@ -c $< $(CFLAGS)

clean:
        rm -f src/*.o master

我用python写了个用于测试的客户端:

#python2.7.6
#coding=utf-8

import socket

if __name__ == "__main__":
    sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sockfd.connect(('localhost', 8000))
    message = ""
    while 1:
        message = raw_input("Please input:")
        sockfd.send(message)
        message = sockfd.recv(8000)
        print message
    sockfd.close()

另外一个用于测试的:

#python2.7.6
#coding=utf-8

import socket
import time
import threading

http_request = "POST /test_server HTTP/1.1\r\nHost:test.py\r\nContent-Length:5\r\n\r\nHello"

def make_a_request():
    sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sockfd.connect(('localhost', 8000))
    sockfd.sendall(http_request)
    sockfd.recv(8000)
    sockfd.close()

if __name__ == "__main__":
    thread_list = []

    start_time = time.time()

    for i in range(0, 1000):
        thread = threading.Thread(target = make_a_request)
        thread_list.append(thread)
        thread.start()

    for thread in thread_list:
        thread.join()

    print "Time used for 1000 request: ", time.time() - start_time

另外,由于是多进程,所以需要测试一下并发下的运转情况~
(可以使用webbench~)

本程序在github的源代码

接下来,我们将引入状态机机制,并开始进行一些http的请求与处理!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值