我想按照自己的理解写一个”半同步半异步”的服务器模型,和常规的模式相同,分为三层:
最上层:使用epoll监控所有的socketfd,如果某个fd发生IO事件就将其加入中间层的任务队列。
中间层:任务队列,它里面保存发生事件的epoll_fd。
最底层:线程池,如果任务队列中有fd,就用一个线程处理。
这个模型非常简单,半同步半异步的含义是:
- 半同步指的是在最上层和中间层之间是同步的,如果有连接事件或者IO读写事件,马上能将其添加到任务队列中。
- 半异步指的是将任务添加到任务队列中并不一定马上处理,或者说有可能IO操作非常耗时,不一定能马上完成。
于是我满怀激情画出了下面的图:
接着我开始实施了,我打算用c++写,先完成第一层和第二层:
首先我定义了server.h
:
#ifndef _SERVER_H
#define _SERVER_H
#include<queue>
#include<sys/epoll.h>
#include<netinet/in.h>
#include"thpoll.h"
#define BACKLOG 1024 //listen 的第二个参数 backlog
#define EPOLL_SIZE 1024 //epoll_create 向内核中注册的兴趣事件的个数,虽然在内核2.6之后没用
#define SERV_PORT 8888
class server {
public:
server() = default;
void init(thpoll &);
void run(thpoll &);
void thread_func(thpoll &);
private:
int listen_fd; //socket返回
int epoll_fd; //epoll_create 返回
int conn_fd; //accept返回
int nfds; //epoll_wait 返回
int popfd; //从消息队列中pop出fd
socklen_t conn_len;
struct sockaddr_in serv_addr,conn_addr;
struct epoll_event ev, evs[EPOLL_SIZE];
std::queue<int> MessageQueue;
};
#endif
1:错误一:server = default
,= default
表示默认的构造方法,这是在c++ 11
中新的写法。但是需要这样写server() = default
,注意:括号不能少
。
默认构造函数:如果我们定义一个类,没有写任何的构造函数,则编译器大多数情况下
会默认为我们合成一个构造函数,如果我们在定义变量时类内初始化
了,则相应变量的值就是我们初始化的值。如果我们需要定义别的带参数的构造函数,那么定义默认构造函数的意义在于:我们在定义带参数的构造函数情况下还需要一个不带参数的构造函数。
然后继续server.cpp
,这时候我想起来有关函数错误的处理,自己写个error
类吧,也方便管理,于是我写了这样的error.h
#ifndef _ERROR_H
#define _ERROR_H
#include<errno.h>
class error {
public:
error() = default;
error(const std::string errmsg,int errline) {
msg = errmsg;
line = errline;
}
void show() {
std::cerr << line << " : ";
perror(msg.c_str());
}
void set(const std::string errmsg, const int errline) {
msg = errmsg;
line = errline;
}
private:
std::string msg = "";
int line = 0;
};
#endif
到了server.cpp
中处理错误的时候::
if((listen_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
error myerror().set("socket",__LINE__).show();
}
错误一大把,谁让之前java
写的太随意,现在终于吃到了苦果:
2:错误二:error myerror(); 这是返回值为error,并且不带参数的函数声明,如果想定义一个默认初始化的对象,应该这样:error myerror;
3:错误三:.set("socket",__LINE__).show();
在set
中没有任何返回,却又继续调用了.show
方法。要想这样使用必须让set方法返回*this
。
如果我们需要类成员函数的一个方法返回这个对象本身然后接着下面调用另一个方法,返回值必须是这个对象的引用,不然只是拷贝了这个对象,以后失效。比如.move()
方法将对象移动到某个位置,.set()
方法设置对象移动到某个位置之后的动作,如果这样使用.move().set()
,那我们应该这么声明move
:
类名 &move() {
return *this;
}
终于改好了这个error
类,接下来完成了epoll部分的代码,可是我突然发现,我的任务队列应该怎么才能知道其中有任务呢,最后想,必须用一个线程监控它的情况才可以…,还好c++11
中std::thread
,于是server.cpp中加入了run()
函数,它是这样的:
void server::run(thpoll& mythpoll)
{
server myserver;
std::thread t(&server::thread_func,&myserver,mythpoll);
t.detach();
}
void server::thread_func(thpoll& mythpoll)
{
while(1) {
if(MessageQueue.empty() == false) { // 如果队列中有东西
std::cout << "拿出了 fd : " << MessageQueue.front() << std::endl;
MessageQueue.pop();
}
sleep(1);
}
}
线程一旦启动就执行thread_func
函数,while(1)
循环检测MessageQueue
是否为空,如果不为空就pop
出来一个fd
,然后我计划是把fd在做处理。接着做了测试,发现如果服务器有连接,会把fd加入到队列中,然后立马pop出来。我觉得自己离成功又进了一步。