这里讲的仅仅是一个简单的server的模型!为了处理同时来到很多小的链接请求( 解释:就是请求很简单,持续时间很短,那么if server在请求到来时在fork来处理它,有可能fork的时间比应答请求的还要少,那么就是不合理的服务设计 ),所以我们采用的是“prefork”和“prethread”模型!
Unix 网络编程 上的4个模型是:prefork:主进程accept
子进程accept
prethread:
主线程accept
子线程accept ( 姑且使用主线程和子线程来描述 )
第一部分是:使用“预先生成进程”处理
CODE_1 : server是:主进程accept,那么这是4种方法中最复杂的,因为要涉及到进程间传递socket描述符的问题!( 进程间传递描述符在上一篇bolg中有过 !),server采用轮询的方式将socket传递给子进程!
话不多说,贴上代码:
Server:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> #include <sys/types.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/epoll.h> #include <fcntl.h> #define PORT 6000 #define MAXBACK 100 #define MAXLINE 1024 #define CHILD_NUM 10 typedef struct child_process { pid_t s_pid; //!> 子进程的pid int s_pipe_fd; //!> 与子进程通信的pipe口 int s_status; //!> 子进程的状态!0:闲 1:忙 }child_process; child_process child[CHILD_NUM]; //!> 定义10个子进程( 此处以10个为例 ) static int n_child_use = 0; //!> 几个child在工作( if 全忙就不给他们 ) //!> 发送socket描述符( 这个代码在上一篇博文上有 ) //!> int send_fd( int fd_send_to, void * data, size_t len, int sock_fd ) { struct msghdr msghdr_send; //!> the info struct struct iovec iov[1]; //!> io vector size_t n; //!> union { struct cmsghdr cm; //!> control msg char ctl[CMSG_SPACE(sizeof( int ))]; //!> the pointer of char }ctl_un; struct cmsghdr * pCmsghdr = NULL; //!> the pointer of control msghdr_send.msg_control = ctl_un.ctl; msghdr_send.msg_controllen = sizeof( ctl_un.ctl ); //!> design : the first info pCmsghdr = CMSG_FIRSTHDR( &msghdr_send ); //!> the info of head pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); //!> the msg len pCmsghdr->cmsg_level = SOL_SOCKET; //!> -> stream mode pCmsghdr->cmsg_type = SCM_RIGHTS; //!> -> file descriptor *((int *)CMSG_DATA( pCmsghdr )) = sock_fd; //!> data: the file fd //!> these infos are nosignification msghdr_send.msg_name = NULL; //!> the name msghdr_send.msg_namelen = 0; //!> len of name iov[0].iov_base = data; //!> no data here iov[0].iov_len = len; //!> the len of data msghdr_send.msg_iov = iov; //!> the io/vector info msghdr_send.msg_iovlen = 1; //!> the num of iov return ( sendmsg( fd_send_to, &msghdr_send, 0 ) ); //!> send msg now } //!> 接收socket描述符 //!> int recv_sock_fd( int fd, void * data, size_t len, int * recv_fd ) { struct msghdr msghdr_recv; //!> the info struct struct iovec iov[1]; //!> io vector size_t n; //!> union { struct cmsghdr cm; //!> control msg char ctl[CMSG_SPACE(sizeof( int ))]; //!> the pointer of char }ctl_un; struct cmsghdr * pCmsghdr = NULL; //!> the pointer of control msghdr_recv.msg_control = ctl_un.ctl; msghdr_recv.msg_controllen = sizeof( ctl_un.ctl ); //!> these infos are nosignification msghdr_recv.msg_name = NULL; //!> the name msghdr_recv.msg_namelen = 0; //!> len of name iov[0].iov_base = data; //!> no data here iov[0].iov_len = len; //!> the len of data msghdr_recv.msg_iov = iov; //!> the io/vector info msghdr_recv.msg_iovlen = 1; //!> the num of iov if( ( n = recvmsg( fd, &msghdr_recv, 0 ) ) < 0 ) //!> recv msg { //!> the msg is recv by msghdr_recv printf("recv error : %d\n", errno); exit(EXIT_FAILURE); } //!> now, we not use 'for' just because only one test_data_ if( ( pCmsghdr = CMSG_FIRSTHDR( &msghdr_recv ) ) != NULL //!> now we need only one, && pCmsghdr->cmsg_len == CMSG_LEN( sizeof( int ) ) //!> we should use 'for' when ) //!> there are many fds { if( pCmsghdr->cmsg_level != SOL_SOCKET ) { printf("Ctl level should be SOL_SOCKET :%d \n", errno); exit(EXIT_FAILURE); } if( pCmsghdr->cmsg_type != SCM_RIGHTS ) { printf("Ctl type should be SCM_RIGHTS : %d\n", errno); exit(EXIT_FAILURE); } *recv_fd =*((int*)CMSG_DATA(pCmsghdr)); //!> get the data : the file des* } else { *recv_fd = -1; } return n; } //!> 子进程具体的执行过程 //!> void web_child( int con_fd ) { char buf[MAXLINE]; int n_read; int i = 0; while( strcmp( buf, "Q" ) != 0 && strcmp( buf, "q" ) != 0 ) { memset( buf, 0, sizeof( buf ) ); if( ( n_read = read( conn_fd, buf, MAXLINE ) ) < 0 ) { printf( "Read errnr! :%d \n", errno ); exit( EXIT_FAILURE ); } else if( n_read == 0 ) { continue; } else { while( buf[i] ) { buf[i] = toupper( buf[i] ); i++; } buf[i] = '\0'; printf("Child %d done! \n", ( unsigned int )pthread_self()); printf("Child %d send %s\n", ( unsigned int )pthread_self(), buf); write( conn_fd, buf, strlen( buf ) ); //!> 写回给client } } printf("Child %d : Dating end!\n", ( unsigned int )pthread_self()); } //!> child process 的主函数 //!> void child_main( int i ) { char data; //!> 由于此处我们主要是传递socket,那么data一般就给一个” “做一个标志就好 int con_fd; //!> 接受con_fd int n_read; //!> 读取长度 printf( "Child %d starting ... \n", i ); while( 1 ) { if( ( n_read = recv_sock_fd( STDERR_FILENO, &data, 1, &con_fd ) ) == 0 ) { continue; //!> 此处理论上应该是阻塞,但是简化为轮询 //printf( " Child process %d read errnr! : %d\n", i, errno ); //exit( EXIT_FAILURE ); } if( con_fd < 0 ) { printf("Child %d read connfd errnr! : %d\n", i, errno); exit( EXIT_FAILURE ); } web_child( con_fd ); //!> child具体的执行过程 write( STDERR_FILENO, " ", 1 ); //!> 随便写点什么让server知道我处理完成了,那么就可以将状态位置为0了 } } //!> 产生子进程及相关处理 //!> void child_make( int i, int listen_fd ) { int sock_fd[2]; //!> 为了和主进程通信创建socket pair pid_t pid; //!> 创建 socketpair if( socketpair( AF_LOCAL, SOCK_STREAM, 0, sock_fd ) == -1 ) { printf( "create socketpair error : %d\n", errno ); exit( EXIT_FAILURE ); } if( ( pid = fork() ) > 0 ) //!> 父进程 { close( sock_fd[1] ); child[i].s_pid = pid; child[i].s_pipe_fd = sock_fd[0]; child[i].s_status = 0; return; } if( dup2( sock_fd[0], STDERR_FILENO ) == -1 ) //!> 现在可以使用STDERR_FILENO替换刚刚创建的sock描述符 { //!> 往后的child的操作就可以STDERR_FILENO中进行! printf("socket pair errnr! : %d\n", errno); exit( EXIT_FAILURE ); } close( sock_fd[0] ); //!> 这些描述符都bu需要了! close( sock_fd[1] ); close( listen_fd ); child_main( i ); //!> child 主循环 } //!> MAIN PROCESS //!> int main( int argc, char ** argv ) { int i; int listen_fd; int conn_fd; int max_fd; int n_select; int n_read; char buf[5]; fd_set all_set, now_set; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; int len = sizeof( struct sockaddr_in ); //!> server 套接口 //!> bzero( &servaddr, sizeof( servaddr ) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons( PORT ); //!> 建立套接字 if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf("Socket Error...\n" , errno ); exit( EXIT_FAILURE ); } //!> 绑定 //!> if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeof( servaddr ) ) == -1 ) { printf("Bind Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> 监听 //!> if( listen( listen_fd, MAXBACK ) == -1 ) { printf("Listen Error : %d\n", errno); exit( EXIT_FAILURE ); } FD_ZERO( &all_set ); FD_SET( listen_fd, &all_set ); //!> 将listenfd加入select max_fd = listen_fd; for( i = 0; i < CHILD_NUM; i++ ) { child_make( i, listen_fd ); FD_SET( child[i].s_pipe_fd, &all_set ); //!> 将子进程socket加入 max_fd = max_fd > child[i].s_pipe_fd ? max_fd : child[i].s_pipe_fd; } while( 1 ) //!> 主进程循环 { now_set = all_set; if( n_child_use >= CHILD_NUM ) //!> 没有可以使用的child 了 { //!> 那么就将listenfd从中清空,也就是不在响应listen了,直到有child空闲 FD_CLR( listen_fd, &now_set ); } if( (n_select = select( max_fd + 1, &now_set, NULL, NULL, NULL )) == -1) { printf(" Main process select errnr~ :%d\n", errno); exit( EXIT_FAILURE ); } if( FD_ISSET( listen_fd, &now_set ) ) //!> if来了请求 { if( ( conn_fd = accept( listen_fd, ( struct sockaddr *)&cliaddr , &len ) ) == -1 ) { printf("Server accept errnr! : %d\n", errno); exit( EXIT_FAILURE ); } for( i = 0; i < CHILD_NUM; i++ ) { if( child[i].s_status == 0 ) //!> 此child闲置 { break; } } if( i == CHILD_NUM ) //!> 说明child已经全部处于忙态 { printf("All childs are busy! \n"); exit( EXIT_FAILURE ); //!> 此处可以等待哦,或者丢弃数据 } child[i].s_status = 1; //!> busy n_child_use++; //!> busy child ++ send_fd( child[i].s_pipe_fd, " ", 1, conn_fd ); //!> 发送socket描述符 close( conn_fd ); //!> server不需要处理了 if( --n_select == 0 ) //!> 没有其他的请求了 { continue; } } for( i = 0; i < CHILD_NUM; i++ ) //!> 看看那些child发来了msg,其实server知道肯定是child完成处理的提示标志 { if( FD_ISSET( child[i].s_pipe_fd, &now_set ) ) { if( ( n_read = read( child[i].s_pipe_fd, buf, 5 ) ) == 0 ) //!> 这里的buf中data没有用,仅仅是child告诉server我完成了 { printf("Child %d exit error! : %d\n", i, errno); exit( EXIT_FAILURE ); } child[i].s_status = 0; //!> 状态位置闲 if( --n_select == 0 ) //!> if没有其他child回送消息就不要浪费时间for了 { break; } } } } return 0; } Client: [cpp] view plaincopyprint? #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> #define MAXLINE 1024 #define SERV_PORT 6000 //!> 注意输入是由stdin,接受是由server发送过来 //!> 所以在client端也是需要select进行处理的 void send_and_recv( int connfd ) { FILE * fp = stdin; int lens; char send[MAXLINE]; char recv[MAXLINE]; fd_set rset; FD_ZERO( &rset ); int maxfd = ( fileno( fp ) > connfd ? fileno( fp ) : connfd + 1 ); //!> 输入和输出的最大值 int n; while( 1 ) { FD_SET( fileno( fp ), &rset ); FD_SET( connfd, &rset ); //!> 注意不要把rset看作是简单的一个变量 //!> 注意它其实是可以包含一组套接字的哦, //!> 相当于是封装的数组!每次都要是新的哦! if( select( maxfd, &rset, NULL, NULL, NULL ) == -1 ) { printf("Client Select Error..\n"); exit(EXIT_FAILURE ); } //!> if 连接口有信息 if( FD_ISSET( connfd, &rset ) ) //!> if 连接端口有信息 { printf( "client get from server ...\n" ); memset( recv, 0, sizeof( recv ) ); n = read( connfd, recv, MAXLINE ); if( n == 0 ) { printf("Recv ok...\n"); break; } else if( n == -1 ) { printf("Recv error...\n"); break; } else { lens = strlen( recv ); recv[lens] = '\0'; //!> 写到stdout write( STDOUT_FILENO, recv, MAXLINE ); printf("\n"); } } //!> if 有stdin输入 if( FD_ISSET( fileno( fp ), &rset ) ) //!> if 有输入 { //!> printf("client stdin ...\n"); memset( send, 0, sizeof( send ) ); if( fgets( send, MAXLINE, fp ) == NULL ) { printf("End...\n"); exit( EXIT_FAILURE ); } else { //!>if( str ) lens = strlen( send ); send[lens-1] = '\0'; //!> 减一的原因是不要回车字符 //!> 经验值:这一步非常重要的哦!!!!!!!! if( strcmp( send, "q" ) == 0 ) { printf( "Bye..\n" ); return; } printf("Client send : %s\n", send); write( connfd, send, strlen( send ) ); } } } } int main( int argc, char ** argv ) { //!> char * SERV_IP = "10.30.97.188"; char buf[MAXLINE]; int connfd; struct sockaddr_in servaddr; if( argc != 2 ) { printf("Input server ip !\n"); exit( EXIT_FAILURE ); } //!> 建立套接字 if( ( connfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf("Socket Error...\n" , errno ); exit( EXIT_FAILURE ); } //!> 套接字信息 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); //!> 链接server if( connect( connfd, ( struct sockaddr * )&servaddr, sizeof( servaddr ) ) < 0 ) { printf("Connect error..\n"); exit(EXIT_FAILURE); } /*else { printf("Connet ok..\n"); }*/ //!> //!> send and recv send_and_recv( connfd ); //!> close( connfd ); printf("Exit\n"); return 0; } CODE_2 : server 是“子进程accept”,那么就是要比第一个简单一点,但是有一个新的问题就是,子进程要“抢”accept,那么必然存在一个“互斥”accept的问题,当然我么可以使用文件锁,但是效率比较低,而且在此处只是一个实验,所以姑且就使用“线程互斥量”,看起来有点不和谐,呵呵呵!~ Server: [cpp] view plaincopyprint? /* 基本思路: server预先创建几个子进程,由子进程进行互斥accept, 由于此处使用文件锁的效率会低一点,那么就使用互斥量 代替! 当然这是不协调的,这里只是简单处理! */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <sys/select.h> #include <sys/types.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/epoll.h> #include <fcntl.h> #define PORT 6000 #define MAXBACK 100 #define MAXLINE 1024 #define CHILD_NUM 10 pthread_mutex_t g_mutex; //!> 互斥量 pthread_mutexattr_t g_mattr ; //!> 属性 //!> 产生子进程及相关处理 //!> void child_make( int i_num, int listen_fd ) { int i = 0; pid_t pid; int conn_fd; int n_read; char buf[MAXLINE]; struct sockaddr_in cliaddr; int len = sizeof( struct sockaddr_in ); if( ( pid = fork() ) > 0 ) { return; } while( 1 ) { pthread_mutex_lock( &g_mutex ); //!> 加锁 if( ( conn_fd = accept( listen_fd, ( struct sockaddr *)&cliaddr , &len ) ) == -1 ) { printf("Accept errnr! :%d\n", errno); exit( EXIT_FAILURE ); } pthread_mutex_unlock( &g_mutex ); //!> 解锁 if( ( n_read = read( conn_fd, buf, MAXLINE ) ) < 0 ) { printf( "Read errnr! :%d \n", errno ); exit( EXIT_FAILURE ); } else if( n_read == 0 ) { continue; } else { while( buf[i] ) { buf[i] = toupper( buf[i] ); i++; } printf("Child %d done! \n", i_num); printf("Child %d send %s\n", i_num, buf); write( conn_fd, buf, strlen( buf ) ); //!> 写回给client } } } //!> MAIN PROCESS //!> int main( int argc, char ** argv ) { int i; int listen_fd; int conn_fd; int n_read; char buf[5]; struct sockaddr_in servaddr; //!>下面需要设置进程间共享此锁 //!> pthread_mutexattr_init( &g_mattr ); pthread_mutexattr_setpshared( &g_mattr, PTHREAD_PROCESS_SHARED ); pthread_mutex_init( &g_mutex, &g_mattr ); //!> 初始化 //!> server 套接口 //!> bzero( &servaddr, sizeof( servaddr ) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons( PORT ); //!> 建立套接字 if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf("Socket Error...\n" , errno ); exit( EXIT_FAILURE ); } //!> 绑定 //!> if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeof( servaddr ) ) == -1 ) { printf("Bind Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> 监听 //!> if( listen( listen_fd, MAXBACK ) == -1 ) { printf("Listen Error : %d\n", errno); exit( EXIT_FAILURE ); } for( i = 0; i < CHILD_NUM; i++ ) { child_make( i, listen_fd ); } waitpid( 0 ); //!> 等待所有子进程而已 pthread_mutex_destroy( &g_mutex ); //!> 删除互斥灯 return 0; } Client:和上面的一样!!!