多进程多线程TCP服务器以及进程池
来源:互联网 发布:远程火箭炮知乎 编辑:程序博客网 时间:2024/05/17 10:53
◆多进程tcp_server.c
运行server,在运行client,client向server发消息:
server响应:
◆多线程tcp_server.c
先运行server,然后运行client,client向server发消息:
server响应:
◆进程池
一般我们是通过动态创建子进程(或者子线程)来实现并发服务器的,这样的缺点:
①动态创建进程(或线程)比较耗费时间,这将导致较慢的客户响应;
②动态创建的子进程通常只用来为一个客户服务,这样导致了系统上产生大量的细微进程(或线程)。进程和线程间的切换将消耗大量CPU时间;
③动态创建的子进程是当前进程的完整映像,当前进程必须谨慎的管理其分配的文件描述符和堆内存等系统资源,否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而影响服务器的性能。
进程池:有服务器预先创建的一组子进程,这些子进程的数目在3~10个之间,进程池中的子进程:
①他们都运行着相同的代码,具有相同的属性,比如优先级,PGID(组识别码)等;
②进程池在服务器启动之初就创建好了,所以每个子进程都相对"干净",即它们没有打开不必要的文件描述符(从父进程继承而来);
③也不会错误地使用大块的堆内存(从父进程复制得到)。
实现进程池:
//描述一个子进程的类,//m_pid是目标子进程的PID,m_pipefd是父进程和子进程通信用的管道class process{public: process() : m_pid( -1 ){}public: pid_t m_pid; int m_pipefd[2];};//将它定义为模板类是为了代码复用//其模板参数是处理逻辑任务的类template< typename T >class processpool{private: //将构造函数定义为私有,因此我们只能通过后面的create静态函数来创建 //processpool实例 processpool( int listenfd, int process_number = 8 );public: //单例模式,以保证程序最多创建一个processpool实例,这是程序正确处理信号的必要条件 static processpool< T > *create( int listenfd, int process_number = 8 ) { if( !m_instance ) { m_instance = new processpool< T >( listenfd, process_number ); } return m_instance; } ~processpool() { delete [] m_sub_process; } //启动进程池 void run();private: void setup_sig_pipe(); void run_parent(); void run_child();private: //进程允许的最大子进程数量 static const int MAX_PROCESS_NUMBER = 16; //每个子进程最多能处理的客户数量 static const int USER_PER_PROCESS = 65536; //epoll最多能处理的事件数 static const int MAX_EVENT_NUMBER = 10000; //进程池中的进程总数 int m_process_number; //子进程在池中的序号,从0开始 int m_idx; //每个进程都有一个epoll内核事件表,用m_epoolfd标识 int m_epollfd; //监听socket int m_listenfd; //子进程通过m_stop来决定是否停止运行 int m_stop; //保存所有子进程的描述信息 process *m_sub_process; //进程池静态实例 static processpool< T > *m_instance;};template< typename T >processpool< T > *processpool< T >::m_instance = NULL;//用于处理信号的管道,以实现统一事件源,后面称之为信号管道static int sig_pipefd[2];static int setnonblocking( int fd ){ int old_option = fcntl( fd, F_GETFL ); int new_option = old_option | O_NONBLOCK; fcntl( fd, F_SETFL, new_option ); return old_option;}static void addfd( int epollfd, int fd ){ epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); setnonblocking( fd );}//从epollfd标识的epoll内核事件表中删除fd上的所有注册事件static void removefd( int epollfd, int fd ){ epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 ); close( fd );}static void sig_handler( int sig ){ int save_errno = errno; int msg = sig; send( sig_pipefd[1], ( char * )&msg, 1, 0 ); errno = save_errno;}static void addsig( int sig, void( handler )(int), bool restart = true ){ struct sigaction sa; memset( &sa, '\0', sizeof( sa ) ); sa.sa_handler = handler; if( restart ) { sa.sa_flags |= SA_RESTART; } sigfillset( &sa.sa_mask ); assert( sigaction( sig, &sa, NULL ) != -1 );}//进程池构造函数。//参数listenfd是监听socket,它必须在创建进程池之前被创建,否则//子进程无法直接引用它,参数process_number指定进程池中子进程的数量。template< typename T >processpool< T >::processpool( int listenfd, int process_number ) : m_listenfd( listenfd ), m_process_number( process_number ), m_idx( -1 ), m_stop( false ){ assert( ( process_number > 0 ) && ( process_number <= MAX_PROCESS_NUMBER ) ); m_sub_process = new process[ process_number ]; assert( m_sub_process ); //创建process_number个子进程,并建立他们和父进程之间的管道 for( int i = 0; i < process_number; ++i ) { int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, m_sub_process[i].m_pipefd ); assert( ret == 0 ); m_sub_process[i].m_pid = fork(); assert( m_sub_process[i].m_pid >= 0 ); if( m_sub_process[i].m_pid > 0 ) { close( m_sub_process[i].m_pipefd[1] ); continue; } else { close( m_sub_process[i].m_pipefd[0] ); m_idx = i; break; } }}//统一事件源template< typename T >void processpool< T >::setup_sig_pipe(){ //创建epoll事件监听表和信号管道 m_epollfd = epoll_create( 5 ); assert( m_epollfd != -1 ); int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, sig_pipefd ); assert( ret != -1 ); setnonblocking( sig_pipefd[1] ); addfd( m_epollfd, sig_pipefd[0] ); //设置信号处理函数 addsig( SIGCHLD, sig_handler ); addsig( SIGTERM, sig_handler ); addsig( SIGINT, sig_handler ); addsig( SIGPIPE, SIG_IGN );}//父进程中m_idx值为-1,子进程中m_idx值大于等于0,我们据此判断下来//要运行的是父进程代码还是子进程代码template< typename T >void processpool< T >::run(){ if( m_idx != -1 ) { run_child(); return; } run_parent();}template< typename T >void processpool< T >::run_child(){ setup_sig_pipe(); //每个子进程都通过其在进程池中的序号值m_idx找到与父进程通信的管道 int pipefd = m_sub_process[m_idx].m_pipefd[ 1 ]; //子进程需要监听管道文件描述pipefd,因为父进程将通过它来通知子进程 //accept新连接 addfd( m_epollfd, pipefd ); epoll_event events[ MAX_EVENT_NUMBER ]; T *users = new T [ USER_PER_PROCESS ]; assert( users ); int number = 0; int ret = -1; while( ! m_stop ) { number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 ); if ( ( number < 0 ) && ( errno != EINTR ) ) { printf( "epoll failure\n" ); break; } for ( int i = 0; i < number; i++ ) { int sockfd = events[i].data.fd; if( ( sockfd == pipefd ) && ( events[i].events & EPOLLIN ) ) { int client = 0; //从父/子进程之间的管道读取数据,并将结果保存在变量client中。 //如果读取成功,则表示有新的客户连接到来。 ret = recv( sockfd, ( char * )&client, sizeof( client ), 0 ); if( ( ( ret < 0 ) && ( errno != EAGAIN ) ) || ret == 0 ) { continue; } else { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof( client_address ); int connfd = accept( m_listenfd, ( struct sockaddr * )&client_address, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); continue; } addfd( m_epollfd, connfd ); //模板T必须实现init方法,以初始化一个客户连接 //我们直接使用connfd来索引逻辑处理对象 //T类型的对象,以提高程序效率 users[connfd].init( m_epollfd, connfd, client_address ); } } //下面处理子进程接收到的信号 else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) ) { int sig; char signals[1024]; ret = recv( sig_pipefd[0], signals, sizeof( signals ), 0 ); if( ret <= 0 ) { continue; } else { for( int i = 0; i < ret; ++i ) { switch( signals[i] ) { case SIGCHLD: { pid_t pid; int stat; while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) { continue; } break; } case SIGTERM: case SIGINT: { m_stop = true; break; } default: { break; } } } } } //如果是其他可读数据,那么必然是客户请求到来。 //调用逻辑对象的process方法处理之 else if( events[i].events & EPOLLIN ) { users[sockfd].process(); } else { continue; } } } delete [] users; users = NULL; close( pipefd ); //close( m_listenfd ); //我们将这句话注销掉,以提醒读者,应该有m_listenfd的创建者 //来关闭这个文件描述符,即所谓的“对象(比如一个文件描述符,又或者一 //堆内存)由那个函数创建,就应该由那个函数销毁 close( m_epollfd );}template< typename T >void processpool< T >::run_parent(){ setup_sig_pipe(); //父进程监听m_listenfd addfd( m_epollfd, m_listenfd ); epoll_event events[ MAX_EVENT_NUMBER ]; int sub_process_counter = 0; int new_conn = 1; int number = 0; int ret = -1; while( ! m_stop ) { number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 ); if ( ( number < 0 ) && ( errno != EINTR ) ) { printf( "epoll failure\n" ); break; } for ( int i = 0; i < number; i++ ) { int sockfd = events[i].data.fd; if( sockfd == m_listenfd ) { //如果有新连接到来,就采用RR方式将其分配给一个子进程处理 int i = sub_process_counter; do { if( m_sub_process[i].m_pid != -1 ) { break; } i = (i + 1) % m_process_number; } while( i != sub_process_counter ); if( m_sub_process[i].m_pid == -1 ) { m_stop = true; break; } sub_process_counter = (i + 1) % m_process_number; //send( m_sub_process[sub_process_counter++].m_pipefd[0], ( char* )&new_conn, sizeof( new_conn ), 0 ); send( m_sub_process[i].m_pipefd[0], ( char * )&new_conn, sizeof( new_conn ), 0 ); printf( "send request to child %d\n", i ); //sub_process_counter %= m_process_number; } //下面处理父进程接收到的信号 else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) ) { int sig; char signals[1024]; ret = recv( sig_pipefd[0], signals, sizeof( signals ), 0 ); if( ret <= 0 ) { continue; } else { for( int i = 0; i < ret; ++i ) { //如果进程池中第i个子进程退出了, //则主进程关闭通信管道,并设置相应的m_pid为-1,以标记该子进程已退出 switch( signals[i] ) { case SIGCHLD: { pid_t pid; int stat; while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) { for( int i = 0; i < m_process_number; ++i ) { if( m_sub_process[i].m_pid == pid ) { printf( "child %d join\n", i ); close( m_sub_process[i].m_pipefd[0] ); m_sub_process[i].m_pid = -1; } } } //如果所有子进程都已经退出了,则父进程也退出 m_stop = true; for( int i = 0; i < m_process_number; ++i ) { if( m_sub_process[i].m_pid != -1 ) { m_stop = false; } } break; } case SIGTERM: case SIGINT: { //如果父进程接收到终止信号,那么就杀死所有子进程,并等待它们全部结束,当然, //通知子进程结束更好的方法是向父/子进程之间的通信管道发送特殊数据 printf( "kill all the clild now\n" ); for( int i = 0; i < m_process_number; ++i ) { int pid = m_sub_process[i].m_pid; if( pid != -1 ) { kill( pid, SIGTERM ); } } break; } default: { break; } } } } } else { continue; } } } //由创建者关闭这个文件描述符 //close( m_listenfd ); close( m_epollfd );}
- 多进程多线程TCP服务器以及进程池
- 多进程多线程TCP服务器
- 多线程、多进程TCP服务器、进程池和线程池
- TCP服务器多线程 多进程简单测试
- 多线程、多进程TCP服务器比较
- 多线程与多进程服务器以及池的概念
- TCP->多进程服务器->多进程服务器->线程池
- 多进程、多线程服务器
- 基于tcp的小型服务器(多线程多进程)
- 基于TCP的多进程和多线程服务器
- TCP server的实现,和多线程,多进程服务器
- 基于socket的Tcp多进程多线程服务器
- tcp多进程并发服务器
- TCP多进程并发服务器
- 【网络】实现简单的TCP、UDP服务器、TCP多进程/多线程服务器
- 多线程多进程服务器与进程线程池
- 实现多进程多线程服务器
- 多进程、多线程服务器程序
- linux lsof命令详解
- jsp入門
- 128. Longest Consecutive Sequence(C++)
- linux 开源镜像地址
- React之ref回调函数实现的两种方式
- 多进程多线程TCP服务器以及进程池
- 1-mvn 介绍
- vue路由配置
- matlab 两个序列的相关系数
- CSS Mastery2(9.5常见BUG&fixed/9.6分级浏览器支持/10Roma Italia实例【10.1-10.4】)
- 什么是CRC校验
- Groovy(一): build.gradle为何物?
- 数据库设计原则
- Ubuntu 下切换gcc版本