Linux 下服务器设计( 一 )

      这里讲的仅仅是一个简单的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:


/*
	基本思路:
	server预先创建几个子进程,他们都在各自与server独立链接的pipe socket等待数据,
	server调用accept,然后遍历子进程,将connfd通过pipe给字进程处理!
	一旦子进程结束就返回任意内容告知已经处理结束!那么server将其状态位置为闲置!!!
	注意:这里应该使用select或者epoll,因为server接受的不仅仅是listen的msg,还有child
	处理完成返回的标志信息!  
*/

#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:

#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


/*
	基本思路:
	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:和上面的一样!!!


到这里,使用进程的简单处理结束,下一篇会是线程处理~~~


Bye~


你可能感兴趣的:(Linux 下服务器设计( 一 ))