【slighttpd】基于lighttpd架构的Server项目实战(4)—简单的echo服务器
轉載地址:https://blog.csdn.net/jiange_zh/article/details/50636536
在這一部分,我們將對上一篇中的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) {//ipv4listen_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))) {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./includemaster:src/master.o src/worker.o src/listener.o src/connection.o src/main.og++ -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.hg++ -g -o $@ -c $< $(CFLAGS)src/worker.o:src/worker.cpp include/worker.h include/util.hg++ -g -o $@ -c $< $(CFLAGS)src/listener.o:src/listener.cpp include/listener.h include/util.hg++ -g -o $@ -c $< $(CFLAGS)src/connection.o:src/connection.cpp include/connection.h include/util.hg++ -g -o $@ -c $< $(CFLAGS)src/main.o:src/main.cpp include/master.hg++ -g -o $@ -c $< $(CFLAGS)clean:rm -f src/*.o master我用python寫了個用于測試的客戶端:
#python2.7.6 #coding=utf-8import socketif __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 messagesockfd.close()另外一個用于測試的:
#python2.7.6 #coding=utf-8import socket import time import threadinghttp_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的請求與處理!
總結
以上是生活随笔為你收集整理的【slighttpd】基于lighttpd架构的Server项目实战(4)—简单的echo服务器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【slighttpd】基于lighttp
- 下一篇: 【slighttpd】基于lighttp