服务器端编程心得(二)—— Reactor模式
最近一直在看游双的《高性能linux服务器编程》一书,下载链接: http://download.csdn.net/detail/analogous_love/9673008
书上是这么介绍Reactor模式的:
按照这个思路,我写个简单的练习:
/**
*@desc: 用reactor模式练习服务器程序,main.cpp
*@author: zhangyl
*@date: 2016.11.23
*/ #include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> //for htonl() and htons()
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <signal.h> //for signal()
#include <pthread.h>
#include <semaphore.h>
#include <list>
#include <errno.h>
#include <time.h>
#include <sstream>
#include <iomanip> //for std::setw()/setfill()
#include <stdlib.h> #define WORKER_THREAD_NUM 5 #define min(a, b) ((a <= b) ? (a) : (b)) int g_epollfd = ;
bool g_bStop = false;
int g_listenfd = ;
pthread_t g_acceptthreadid = ;
pthread_t g_threadid[WORKER_THREAD_NUM] = { };
pthread_cond_t g_acceptcond;
pthread_mutex_t g_acceptmutex; pthread_cond_t g_cond /*= PTHREAD_COND_INITIALIZER*/;
pthread_mutex_t g_mutex /*= PTHREAD_MUTEX_INITIALIZER*/; pthread_mutex_t g_clientmutex; std::list<int> g_listClients; void prog_exit(int signo)
{
::signal(SIGINT, SIG_IGN);
::signal(SIGKILL, SIG_IGN);
::signal(SIGTERM, SIG_IGN); std::cout << "program recv signal " << signo << " to exit." << std::endl; g_bStop = true; ::epoll_ctl(g_epollfd, EPOLL_CTL_DEL, g_listenfd, NULL); //TODO: 是否需要先调用shutdown()一下?
::shutdown(g_listenfd, SHUT_RDWR);
::close(g_listenfd);
::close(g_epollfd); ::pthread_cond_destroy(&g_acceptcond);
::pthread_mutex_destroy(&g_acceptmutex); ::pthread_cond_destroy(&g_cond);
::pthread_mutex_destroy(&g_mutex); ::pthread_mutex_destroy(&g_clientmutex);
} bool create_server_listener(const char* ip, short port)
{
g_listenfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, );
if (g_listenfd == -)
return false; int on = ;
::setsockopt(g_listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
::setsockopt(g_listenfd, SOL_SOCKET, SO_REUSEPORT, (char *)&on, sizeof(on)); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(ip);
servaddr.sin_port = htons(port);
if (::bind(g_listenfd, (sockaddr *)&servaddr, sizeof(servaddr)) == -)
return false; if (::listen(g_listenfd, ) == -)
return false; g_epollfd = ::epoll_create();
if (g_epollfd == -)
return false; struct epoll_event e;
memset(&e, , sizeof(e));
e.events = EPOLLIN | EPOLLRDHUP;
e.data.fd = g_listenfd;
if (::epoll_ctl(g_epollfd, EPOLL_CTL_ADD, g_listenfd, &e) == -)
return false; return true;
} void release_client(int clientfd)
{
if (::epoll_ctl(g_epollfd, EPOLL_CTL_DEL, clientfd, NULL) == -)
std::cout << "release client socket failed as call epoll_ctl failed" << std::endl; ::close(clientfd);
} void* accept_thread_func(void* arg)
{
while (!g_bStop)
{
::pthread_mutex_lock(&g_acceptmutex);
::pthread_cond_wait(&g_acceptcond, &g_acceptmutex);
//::pthread_mutex_lock(&g_acceptmutex); //std::cout << "run loop in accept_thread_func" << std::endl; struct sockaddr_in clientaddr;
socklen_t addrlen;
int newfd = ::accept(g_listenfd, (struct sockaddr *)&clientaddr, &addrlen);
::pthread_mutex_unlock(&g_acceptmutex);
if (newfd == -)
continue; std::cout << "new client connected: " << ::inet_ntoa(clientaddr.sin_addr) << ":" << ::ntohs(clientaddr.sin_port) << std::endl; //将新socket设置为non-blocking
int oldflag = ::fcntl(newfd, F_GETFL, );
int newflag = oldflag | O_NONBLOCK;
if (::fcntl(newfd, F_SETFL, newflag) == -)
{
std::cout << "fcntl error, oldflag =" << oldflag << ", newflag = " << newflag << std::endl;
continue;
} struct epoll_event e;
memset(&e, , sizeof(e));
e.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
e.data.fd = newfd;
if (::epoll_ctl(g_epollfd, EPOLL_CTL_ADD, newfd, &e) == -)
{
std::cout << "epoll_ctl error, fd =" << newfd << std::endl;
}
} return NULL;
} void* worker_thread_func(void* arg)
{
while (!g_bStop)
{
int clientfd;
::pthread_mutex_lock(&g_clientmutex);
while (g_listClients.empty())
::pthread_cond_wait(&g_cond, &g_clientmutex);
clientfd = g_listClients.front();
g_listClients.pop_front();
pthread_mutex_unlock(&g_clientmutex); //gdb调试时不能实时刷新标准输出,用这个函数刷新标准输出,使信息在屏幕上实时显示出来
std::cout << std::endl; std::string strclientmsg;
char buff[];
bool bError = false;
while (true)
{
memset(buff, , sizeof(buff));
int nRecv = ::recv(clientfd, buff, , );
if (nRecv == -)
{
if (errno == EWOULDBLOCK)
break;
else
{
std::cout << "recv error, client disconnected, fd = " << clientfd << std::endl;
release_client(clientfd);
bError = true;
break;
} }
//对端关闭了socket,这端也关闭。
else if (nRecv == )
{
std::cout << "peer closed, client disconnected, fd = " << clientfd << std::endl;
release_client(clientfd);
bError = true;
break;
} strclientmsg += buff;
} //出错了,就不要再继续往下执行了
if (bError)
continue; std::cout << "client msg: " << strclientmsg; //将消息加上时间标签后发回
time_t now = time(NULL);
struct tm* nowstr = localtime(&now);
std::ostringstream ostimestr;
ostimestr << "[" << nowstr->tm_year + << "-"
<< std::setw() << std::setfill('') << nowstr->tm_mon + << "-"
<< std::setw() << std::setfill('') << nowstr->tm_mday << " "
<< std::setw() << std::setfill('') << nowstr->tm_hour << ":"
<< std::setw() << std::setfill('') << nowstr->tm_min << ":"
<< std::setw() << std::setfill('') << nowstr->tm_sec << "]server reply: "; strclientmsg.insert(, ostimestr.str()); while (true)
{
int nSent = ::send(clientfd, strclientmsg.c_str(), strclientmsg.length(), );
if (nSent == -)
{
if (errno == EWOULDBLOCK)
{
::sleep();
continue;
}
else
{
std::cout << "send error, fd = " << clientfd << std::endl;
release_client(clientfd);
break;
} } std::cout << "send: " << strclientmsg;
strclientmsg.erase(, nSent); if (strclientmsg.empty())
break;
}
} return NULL;
} void daemon_run()
{
int pid;
signal(SIGCHLD, SIG_IGN);
//1)在父进程中,fork返回新创建子进程的进程ID;
//2)在子进程中,fork返回0;
//3)如果出现错误,fork返回一个负值;
pid = fork();
if (pid < )
{
std:: cout << "fork error" << std::endl;
exit(-);
}
//父进程退出,子进程独立运行
else if (pid > ) {
exit();
}
//之前parent和child运行在同一个session里,parent是会话(session)的领头进程,
//parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养。
//执行setsid()之后,child将重新获得一个新的会话(session)id。
//这时parent退出之后,将不会影响到child了。
setsid();
int fd;
fd = open("/dev/null", O_RDWR, );
if (fd != -)
{
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
if (fd > )
close(fd); } int main(int argc, char* argv[])
{
short port = ;
int ch;
bool bdaemon = false;
while ((ch = getopt(argc, argv, "p:d")) != -)
{
switch (ch)
{
case 'd':
bdaemon = true;
break;
case 'p':
port = atol(optarg);
break;
}
} if (bdaemon)
daemon_run(); if (port == )
port = ; if (!create_server_listener("0.0.0.0", port))
{
std::cout << "Unable to create listen server: ip=0.0.0.0, port=" << port << "." << std::endl;
return -;
} //设置信号处理
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, prog_exit);
signal(SIGKILL, prog_exit);
signal(SIGTERM, prog_exit); ::pthread_cond_init(&g_acceptcond, NULL);
::pthread_mutex_init(&g_acceptmutex, NULL); ::pthread_cond_init(&g_cond, NULL);
::pthread_mutex_init(&g_mutex, NULL); ::pthread_mutex_init(&g_clientmutex, NULL); ::pthread_create(&g_acceptthreadid, NULL, accept_thread_func, NULL);
//启动工作线程
for (int i = ; i < WORKER_THREAD_NUM; ++i)
{
::pthread_create(&g_threadid[i], NULL, worker_thread_func, NULL);
} while (!g_bStop)
{
struct epoll_event ev[];
int n = ::epoll_wait(g_epollfd, ev, , );
if (n == )
continue;
else if (n < )
{
std::cout << "epoll_wait error" << std::endl;
continue;
} int m = min(n, );
for (int i = ; i < m; ++i)
{
//通知接收连接线程接收新连接
if (ev[i].data.fd == g_listenfd)
pthread_cond_signal(&g_acceptcond);
//通知普通工作线程接收数据
else
{
pthread_mutex_lock(&g_clientmutex);
g_listClients.push_back(ev[i].data.fd);
pthread_mutex_unlock(&g_clientmutex);
pthread_cond_signal(&g_cond);
//std::cout << "signal" << std::endl;
} } } return ;
}/
程序的功能一个简单的echo服务:客户端连接上服务器之后,给服务器发送信息,服务器加上时间戳等信息后返回给客户端。使用到的知识点有:
1. 条件变量
2.epoll的边缘触发模式
程序的大致框架是:
1. 主线程只负责监听侦听socket上是否有新连接,如果有新连接到来,交给一个叫accept的工作线程去接收新连接,并将新连接socket绑定到主线程使用epollfd上去。
2. 主线程如果侦听到客户端的socket上有可读事件,则通知另外五个工作线程去接收处理客户端发来的数据,并将数据加上时间戳后发回给客户端。
3. 可以通过传递-p port来设置程序的监听端口号;可以通过传递-d来使程序以daemon模式运行在后台。这也是标准linux daemon模式的书写方法。
程序难点和需要注意的地方是:
1. 条件变量为了防止虚假唤醒,一定要在一个循环里面调用pthread_cond_wait()函数,我在worker_thread_func()中使用了:
while (g_listClients.empty())
::pthread_cond_wait(&g_cond, &g_clientmutex);
在accept_thread_func()函数里面我没有使用循环,这样会有问题吗?
2. 使用条件变量pthread_cond_wait()函数的时候一定要先获得与该条件变量相关的mutex,即像下面这样的结构:
mutex_lock(...); while (condition is true)
::pthread_cond_wait(...); //这里可以有其他代码...
mutex_unlock(...); //这里可以有其他代码...
因为pthread_cond_wait()如果阻塞的话,它解锁相关mutex和阻塞当前线程这两个动作加在一起是原子的。
3. 作为服务器端程序最好对侦听socket调用setsocketopt()设置SO_REUSEADDR和SO_REUSEPORT两个标志,因为服务程序有时候会需要重启(比如调试的时候就会不断重启),如果不设置这两个标志的话,绑定端口时就会调用失败。因为一个端口使用后,即使不再使用,因为四次挥手该端口处于TIME_WAIT状态,有大约2min的MSL(Maximum Segment Lifetime,最大存活期)。这2min内,该端口是不能被重复使用的。你的服务器程序上次使用了这个端口号,接着重启,因为这个缘故,你再次绑定这个端口就会失败(bind函数调用失败)。要不你就每次重启时需要等待2min后再试(这在频繁重启程序调试是难以接收的),或者设置这种SO_REUSEADDR和SO_REUSEPORT立即回收端口使用。
其实,SO_REUSEADDR在windows上和Unix平台上还有些细微的区别,我在libevent源码中看到这样的描述:
int evutil_make_listen_socket_reuseable(evutil_socket_t sock)
{
#ifndef WIN32
int one = ;
/* REUSEADDR on Unix means, "don't hang on to this address after the
* listener is closed." On Windows, though, it means "don't keep other
* processes from binding to this address while we're using it. */
return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one,
(ev_socklen_t)sizeof(one));
#else
return ;
#endif
}
注意注释部分, 在Unix平台上设置这个选项意味着,任意进程可以复用该地址;而在windows,不要阻止其他进程复用该地址。也就是在在Unix平台上,如果不设置这个选项,任意进程在一定时间内,不能bind该地址;在windows平台上,在一定时间内,其他进程不能bind该地址,而本进程却可以再次bind该地址。
4. epoll_wait对新连接socket使用的是边缘触发模式EPOLLET(edge trigger),而不是默认的水平触发模式(level trigger)。因为如果采取水平触发模式的话,主线程检测到某个客户端socket数据可读时,通知工作线程去收取该socket上的数据,这个时候主线程继续循环,只要在工作线程没有将该socket上数据全部收完,或者在工作线程收取数据的过程中,客户端有新数据到来,主线程会继续发通知(通过pthread_cond_signal())函数,再次通知工作线程收取数据。这样会可能导致多个工作线程同时调用recv函数收取该客户端socket上的数据,这样产生的结果将会导致数据错乱。
相反,采取边缘触发模式,只有等某个工作线程将那个客户端socket上数据全部收取完毕,主线程的epoll_wait才可能会再次触发来通知工作线程继续收取那个客户端socket新来的数据。
5. 代码中有这样一行:
//gdb调试时不能实时刷新标准输出,用这个函数刷新标准输出,使信息在屏幕上实时显示出来
std::cout << std::endl;
如果不加上这一行,正常运行服务器程序,程序中要打印到控制台的信息都会打印出来,但是如果用gdb调试状态下,程序的所有输出就不显示了。我不知道这是不是gdb的一个bug,所以这里加上std::endl来输出一个换行符并flush标准输出,让输出显示出来。(std::endl不仅是输出一个换行符而且是同时刷新输出,相当于fflush()函数)。
程序我部署起来了,你可以使用linux的nc命令或自己写程序连接服务器来查看程序效果,当然也可以使用telnet命令,方法:
linux:
nc 120.55.94.78 12345
或
telnet 120.55.94.78 12345
然后就可以给服务器自由发送数据了,服务器会给你发送的信息加上时间戳返回给你。效果如图:
另外我将这个代码改写了成纯C++11版本,使用CMake编译,为了支持编译必须加上这-std=c++11:
CMakeLists.txt代码如下:
cmake_minimum_required(VERSION 2.8) PROJECT(myreactorserver) AUX_SOURCE_DIRECTORY(./ SRC_LIST)
SET(EXECUTABLE_OUTPUT_PATH ./) ADD_DEFINITIONS(-g -W -Wall -Wno-deprecated -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS= -DAC_HAS_INFO -DAC_HAS_WARNING -DAC_HAS_ERROR -DAC_HAS_CRITICAL -DTIXML_USE_STL -DHAVE_CXX_STDHEADERS ${CMAKE_CXX_FLAGS} -std=c++) INCLUDE_DIRECTORIES(
./
)
LINK_DIRECTORIES(
./
) set(
main.cpp
myreator.cpp
) ADD_EXECUTABLE(myreactorserver ${SRC_LIST}) TARGET_LINK_LIBRARIES(myreactorserver pthread)c
myreactor.h文件内容:
/**
*@desc: myreactor头文件, myreactor.h
*@author: zhangyl
*@date: 2016.12.03
*/
#ifndef __MYREACTOR_H__
#define __MYREACTOR_H__ #include <list>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable> #define WORKER_THREAD_NUM 5 class CMyReactor
{
public:
CMyReactor();
~CMyReactor(); bool init(const char* ip, short nport);
bool uninit(); bool close_client(int clientfd); static void* main_loop(void* p); private:
//no copyable
CMyReactor(const CMyReactor& rhs);
CMyReactor& operator = (const CMyReactor& rhs); bool create_server_listener(const char* ip, short port); static void accept_thread_proc(CMyReactor* pReatcor);
static void worker_thread_proc(CMyReactor* pReatcor); private:
//C11语法可以在这里初始化
int m_listenfd = ;
int m_epollfd = ;
bool m_bStop = false; std::shared_ptr<std::thread> m_acceptthread;
std::shared_ptr<std::thread> m_workerthreads[WORKER_THREAD_NUM]; std::condition_variable m_acceptcond;
std::mutex m_acceptmutex; std::condition_variable m_workercond ;
std::mutex m_workermutex; std::list<int> m_listClients;
}; #endif //!__MYREACTOR_H__/
myreactor.cpp文件内容:
/**
*@desc: myreactor实现文件, myreactor.cpp
*@author: zhangyl
*@date: 2016.12.03
*/
#include "myreactor.h"
#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> //for htonl() and htons()
#include <fcntl.h>
#include <sys/epoll.h>
#include <list>
#include <errno.h>
#include <time.h>
#include <sstream>
#include <iomanip> //for std::setw()/setfill()
#include <unistd.h> #define min(a, b) ((a <= b) ? (a) : (b)) CMyReactor::CMyReactor()
{
//m_listenfd = 0;
//m_epollfd = 0;
//m_bStop = false;
} CMyReactor::~CMyReactor()
{ } bool CMyReactor::init(const char* ip, short nport)
{
if (!create_server_listener(ip, nport))
{
std::cout << "Unable to bind: " << ip << ":" << nport << "." << std::endl;
return false;
} std::cout << "main thread id = " << std::this_thread::get_id() << std::endl; //启动接收新连接的线程
m_acceptthread.reset(new std::thread(CMyReactor::accept_thread_proc, this)); //启动工作线程
for (auto& t : m_workerthreads)
{
t.reset(new std::thread(CMyReactor::worker_thread_proc, this));
} return true;
} bool CMyReactor::uninit()
{
m_bStop = true;
m_acceptcond.notify_one();
m_workercond.notify_all(); m_acceptthread->join();
for (auto& t : m_workerthreads)
{
t->join();
} ::epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_listenfd, NULL); //TODO: 是否需要先调用shutdown()一下?
::shutdown(m_listenfd, SHUT_RDWR);
::close(m_listenfd);
::close(m_epollfd); return true;
} bool CMyReactor::close_client(int clientfd)
{
if (::epoll_ctl(m_epollfd, EPOLL_CTL_DEL, clientfd, NULL) == -)
{
std::cout << "close client socket failed as call epoll_ctl failed" << std::endl;
//return false;
} ::close(clientfd); return true;
} void* CMyReactor::main_loop(void* p)
{
std::cout << "main thread id = " << std::this_thread::get_id() << std::endl; CMyReactor* pReatcor = static_cast<CMyReactor*>(p); while (!pReatcor->m_bStop)
{
struct epoll_event ev[];
int n = ::epoll_wait(pReatcor->m_epollfd, ev, , );
if (n == )
continue;
else if (n < )
{
std::cout << "epoll_wait error" << std::endl;
continue;
} int m = min(n, );
for (int i = ; i < m; ++i)
{
//通知接收连接线程接收新连接
if (ev[i].data.fd == pReatcor->m_listenfd)
pReatcor->m_acceptcond.notify_one();
//通知普通工作线程接收数据
else
{
{
std::unique_lock<std::mutex> guard(pReatcor->m_workermutex);
pReatcor->m_listClients.push_back(ev[i].data.fd);
} pReatcor->m_workercond.notify_one();
//std::cout << "signal" << std::endl;
}// end if }// end for-loop
}// end while std::cout << "main loop exit ..." << std::endl; return NULL;
} void CMyReactor::accept_thread_proc(CMyReactor* pReatcor)
{
std::cout << "accept thread, thread id = " << std::this_thread::get_id() << std::endl; while (true)
{
int newfd;
struct sockaddr_in clientaddr;
socklen_t addrlen;
{
std::unique_lock<std::mutex> guard(pReatcor->m_acceptmutex);
pReatcor->m_acceptcond.wait(guard);
if (pReatcor->m_bStop)
break; //std::cout << "run loop in accept_thread_proc" << std::endl; newfd = ::accept(pReatcor->m_listenfd, (struct sockaddr *)&clientaddr, &addrlen);
}
if (newfd == -)
continue; std::cout << "new client connected: " << ::inet_ntoa(clientaddr.sin_addr) << ":" << ::ntohs(clientaddr.sin_port) << std::endl; //将新socket设置为non-blocking
int oldflag = ::fcntl(newfd, F_GETFL, );
int newflag = oldflag | O_NONBLOCK;
if (::fcntl(newfd, F_SETFL, newflag) == -)
{
std::cout << "fcntl error, oldflag =" << oldflag << ", newflag = " << newflag << std::endl;
continue;
} struct epoll_event e;
memset(&e, , sizeof(e));
e.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
e.data.fd = newfd;
if (::epoll_ctl(pReatcor->m_epollfd, EPOLL_CTL_ADD, newfd, &e) == -)
{
std::cout << "epoll_ctl error, fd =" << newfd << std::endl;
}
} std::cout << "accept thread exit ..." << std::endl;
} void CMyReactor::worker_thread_proc(CMyReactor* pReatcor)
{
std::cout << "new worker thread, thread id = " << std::this_thread::get_id() << std::endl; while (true)
{
int clientfd;
{
std::unique_lock<std::mutex> guard(pReatcor->m_workermutex);
while (pReatcor->m_listClients.empty())
{
if (pReatcor->m_bStop)
{
std::cout << "worker thread exit ..." << std::endl;
return;
} pReatcor->m_workercond.wait(guard);
} clientfd = pReatcor->m_listClients.front();
pReatcor->m_listClients.pop_front();
} //gdb调试时不能实时刷新标准输出,用这个函数刷新标准输出,使信息在屏幕上实时显示出来
std::cout << std::endl; std::string strclientmsg;
char buff[];
bool bError = false;
while (true)
{
memset(buff, , sizeof(buff));
int nRecv = ::recv(clientfd, buff, , );
if (nRecv == -)
{
if (errno == EWOULDBLOCK)
break;
else
{
std::cout << "recv error, client disconnected, fd = " << clientfd << std::endl;
pReatcor->close_client(clientfd);
bError = true;
break;
} }
//对端关闭了socket,这端也关闭。
else if (nRecv == )
{
std::cout << "peer closed, client disconnected, fd = " << clientfd << std::endl;
pReatcor->close_client(clientfd);
bError = true;
break;
} strclientmsg += buff;
} //出错了,就不要再继续往下执行了
if (bError)
continue; std::cout << "client msg: " << strclientmsg; //将消息加上时间标签后发回
time_t now = time(NULL);
struct tm* nowstr = localtime(&now);
std::ostringstream ostimestr;
ostimestr << "[" << nowstr->tm_year + << "-"
<< std::setw() << std::setfill('') << nowstr->tm_mon + << "-"
<< std::setw() << std::setfill('') << nowstr->tm_mday << " "
<< std::setw() << std::setfill('') << nowstr->tm_hour << ":"
<< std::setw() << std::setfill('') << nowstr->tm_min << ":"
<< std::setw() << std::setfill('') << nowstr->tm_sec << "]server reply: "; strclientmsg.insert(, ostimestr.str()); while (true)
{
int nSent = ::send(clientfd, strclientmsg.c_str(), strclientmsg.length(), );
if (nSent == -)
{
if (errno == EWOULDBLOCK)
{
std::this_thread::sleep_for(std::chrono::milliseconds());
continue;
}
else
{
std::cout << "send error, fd = " << clientfd << std::endl;
pReatcor->close_client(clientfd);
break;
} } std::cout << "send: " << strclientmsg;
strclientmsg.erase(, nSent); if (strclientmsg.empty())
break;
}
}
} bool CMyReactor::create_server_listener(const char* ip, short port)
{
m_listenfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, );
if (m_listenfd == -)
return false; int on = ;
::setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
::setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEPORT, (char *)&on, sizeof(on)); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(ip);
servaddr.sin_port = htons(port);
if (::bind(m_listenfd, (sockaddr *)&servaddr, sizeof(servaddr)) == -)
return false; if (::listen(m_listenfd, ) == -)
return false; m_epollfd = ::epoll_create();
if (m_epollfd == -)
return false; struct epoll_event e;
memset(&e, , sizeof(e));
e.events = EPOLLIN | EPOLLRDHUP;
e.data.fd = m_listenfd;
if (::epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_listenfd, &e) == -)
return false; return true;
}
main.cpp文件内容:
/**
*@desc: 用reactor模式练习服务器程序
*@author: zhangyl
*@date: 2016.12.03
*/ #include <iostream>
#include <signal.h> //for signal()
#include<unistd.h>
#include <stdlib.h> //for exit()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "myreactor.h" CMyReactor g_reator; void prog_exit(int signo)
{
std::cout << "program recv signal " << signo << " to exit." << std::endl; g_reator.uninit();
} void daemon_run()
{
int pid;
signal(SIGCHLD, SIG_IGN);
//1)在父进程中,fork返回新创建子进程的进程ID;
//2)在子进程中,fork返回0;
//3)如果出现错误,fork返回一个负值;
pid = fork();
if (pid < )
{
std:: cout << "fork error" << std::endl;
exit(-);
}
//父进程退出,子进程独立运行
else if (pid > ) {
exit();
}
//之前parent和child运行在同一个session里,parent是会话(session)的领头进程,
//parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养。
//执行setsid()之后,child将重新获得一个新的会话(session)id。
//这时parent退出之后,将不会影响到child了。
setsid();
int fd;
fd = open("/dev/null", O_RDWR, );
if (fd != -)
{
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
if (fd > )
close(fd);
} int main(int argc, char* argv[])
{
//设置信号处理
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, prog_exit);
signal(SIGKILL, prog_exit);
signal(SIGTERM, prog_exit); short port = ;
int ch;
bool bdaemon = false;
while ((ch = getopt(argc, argv, "p:d")) != -)
{
switch (ch)
{
case 'd':
bdaemon = true;
break;
case 'p':
port = atol(optarg);
break;
}
} if (bdaemon)
daemon_run(); if (port == )
port = ; if (!g_reator.init("0.0.0.0", ))
return -; g_reator.main_loop(&g_reator); return ;
}
完整实例代码下载地址:
普通版本:https://pan.baidu.com/s/1o82Mkno
C++11版本:https://pan.baidu.com/s/1dEJdrih
服务器端编程心得(二)—— Reactor模式的更多相关文章
- Python Twisted系列教程2:异步编程初探与reactor模式
作者:dave@http://krondo.com/slow-poetry-and-the-apocalypse/ 译者:杨晓伟(采用意译) 这个系列是从这里开始的,欢迎你再次来到这里来.现在我们可 ...
- 原生JDK网络编程- NIO之Reactor模式
“反应”器名字中”反应“的由来: “反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示自己对某些事件感兴趣,有时间来了,具体事件处理程序通过事件处理器对某 ...
- 也谈Reactor模式
何谓Reactor模式?它是实现高性能IO的一种设计模式.网上资料有很多,有些写的也很好,但大多不知其所以然.这里博主按自己的思路简单介绍下,有不对的地方敬请指正. BIO Java1.4(2002年 ...
- 高性能服务器开发基础系列 (二)Reactor模式
系列目录 第01篇 主线程与工作线程的分工 第02篇 Reactor模式 第03篇 一个服务器程序的架构介绍 第04篇 如何将socket设置为非阻塞模式 第05篇 如何编写高性能日志 第06篇 关于 ...
- 【Linux 网络编程】生动讲解 Reactor 模式与 Proactor 模式
五种 I/O 模型 先花费点时间了解这几种 I/O 模型,有助于后面的理解. 阻塞 I/O 与非阻塞 I/O 阻塞和非阻塞的概念能应用于所有的文件描述符,而不仅仅是 socket.我们称阻塞的文件描述 ...
- Reactor模式和NIO(转载二)
本文可看成是对Doug Lea Scalable IO in Java一文的翻译. 当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作.他们都有一个共同的 ...
- reactor模式前序(二):NIO WEB服务器设计
前文介绍了传统IO的WEB经典服务器 reactor模式前序:传统IO的WEB服务器设计 下面看看JAVA NIO的WEB服务器设计 NIO是基于事件驱动的,对于NIO来说,重要组件是Selector ...
- 【转】Netty那点事(四)Netty与Reactor模式
[原文]https://github.com/code4craft/netty-learning/blob/master/posts/ch4-reactor.md 一:Netty.NIO.多线程? 时 ...
- ACE反应器(Reactor)模式(2)
转载于:http://www.cnblogs.com/TianFang/archive/2006/12/18/595808.html 在Socket编程中,常见的事件就是"读就绪" ...
随机推荐
- UI:沙盒
IOS平台下,沙盒的本质就是一个文件夹 每一款IOS应用安装在手机上都会自动的生成一个文件夹.之所以叫沙盒,就是因为这个文件夹是每次运行随机产生的文件夹.沙盒文件夹是独立的,每个应用之间不能互相访问. ...
- js函数定义 参数只要写名称就可以了
js函数定义 参数只要写名称就可以了 以下为标准: function add(type) { } 不要写成下面这个样子 function add(var type) { } 哎 妹的 老何ja ...
- 三步升级已安装的 Android SDK 和 ADT 插件(转载)
转载:http://www.tfan.org/update-adt-and-android-sdk-in-five-minutes/ 如何快速地把已安装的 Android SDK 及 Eclipse ...
- mysql 状态查询
select COUNT(case when info.State = '0' then State end ) as daichuliCount, COUNT(case when info.St ...
- 采购发票检验MIRO差异科目设置
采购订单发票检验时,最终的金额可能跟采购订单的价格不一样,对于这部分差异,系统提供了后台配置科目的方式. 配置科目可通过OBYC,在BSX存货差异配置相关评估类型对应科目. 当库存商品少于采购订单数量 ...
- js实现打字效果
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>js typing& ...
- docker学习教程
我们的docker学习教程可以分为以下几个部分,分别是: 第一:docker基础学习 第二:docker日志管理 第三:docker监控管理 第四:docker三剑客之一:docker-machine ...
- String Mark Codeforces - 895D
一看好像会做的样子,就去做了一下,结果 猝不及防地T掉了 赶紧查了一下,没有死循环,复杂度也是对的,无果,于是翻了题解 题解没看懂,但是找到了标程,然后发现我被卡常了... 而且好像当时还过了前10个 ...
- Linux命令(009) -- tar
tar命令可以为Linux的文件和目录创建档案(备份).利用该命令,可以为某一特定文件创建备份,也可以在档案中改变文件或向档案中加入新的文件:可以把一大堆的文件和目录全部打包成一个文件,这对于备份文件 ...
- 转 ORACLE-016:ora-01720 授权选项对于'xxxx'不存在
报错的情形如下, A用户:视图V_A B用户:视图V_B,并且用到了V_A C用户:需要用V_B, 授权过程, A用户下: grant select on V_A to B B用户下: grant s ...