Linux网络编程基础-04_TCP/IP网络编程-并发服务器

服务器模型

Linux网络编程基础-04_TCP/IP网络编程-并发服务器
在网络程序里面,通常都是一个服务器处理多个客户机。

为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。

目前最常用的服务器模型.

  • 循环服务器:
    循环服务器在同一个时刻只能响应一个客户端的请求
  • 并发服务器:
    并发服务器在同一个时刻可以响应多个客户端的请求

循环服务器模型

TCP服务器
  • TCP服务器端运行后等待客户端的连接请求。
  • TCP服务器接受一个客户端的连接后开始处理,完成了客户的所有请求后断开连接。
  • TCP循环服务器一次只能处理一个客户端的请求。
  • 只有在当前客户的所有请求都完成后,服务器才能处理下一个客户的连接/服务请求。
  • 如果某个客户端一直占用服务器资源,那么其它的客户端都不能被处理。TCP服务器一般很少采用循环服务器模型。

TCP循环服务器流程如下:

socket(...); 
bind(...); 
listen(...); 
while(1) 
{    
   accept(...); 
   while(1) 
   {
    	 recv(...);      
         process(...); 
         send(...); 
   } 
   close(...); 
}
UDP服务器

UDP服务器每次从套接字上读取一个客户端的请求,处理后将结果返回给客户机.
可以用下面的算法来实现(伪代码):

socket(...);
bind(...);
while(1) 
{    
	 recvfrom(...);   
	 process(...); 
	 sendto(...); 
}

并发服务器模型

TCP服务器
  • 为了弥补TCP循环服务器的缺陷,人们又设计了并发服务器的模型。
    并发服务器的设计思想是服务器接受客户端的连接请求后创建子进程来为客户端服务

  • TCP并发服务器可以避免TCP循环服务器中客户端独占服务器的情况。
    为了响应客户机的请求,服务器要创建子进程来处理。 如果有多个客户端的话,服务器端需要创建多个子进程。过多的子进程会影响服务器端的运行效率

     socket(...); 
      bind(...); 
      listen(...);
      while(1) 
      {      
      	accept(...);      
      	if (fork() = = 0) 
      	{           
      		while(1) 
      		{ 
      			recv(...); 
      			process(...); 
      			send(...); 
      		}          
      		close(...);           
      		exit(...);       
      	}      
      	close(...);  
      }
    

UDP服务器

  • 人们把并发的概念用于UDP就得到了UDP并发服务器模型。
  • UDP并发服务器模型TCP服务器模型一样,创建一个子进程来处理客户端的请求
  • 除非UDP服务器在处理某个客户端的请求时所用的时间比较长,人们实际上较少用这种模型。

示例TCP多线程:
Server.c

#include "Net.h"

void cli_data_handle (void *arg);
int fd_w = -1;	

int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	

	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}
	//Step1:创建socket 文件描述符 
	if ((socket_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) 
	{
		perror ("socket");
		exit (1);
	}
	
	/*优化: 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt (socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
	
	//连接服务器	
	struct sockaddr_in sin;
	bzero (&sin, sizeof (sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号
	sin.sin_addr.s_addr = htonl (INADDR_ANY);

	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}
	
	//Step2-绑定
	if( bind(socket_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("bind");
		exit(1);
	}
	
	//Step3-调用listen() 
	if( listen(socket_fd, BACKLOG) < 0)
	{
		perror("listen");
		exit(1);
	}
	
	printf(" Server Starting... OK!\n ");
	
	

#if 1
/* 优化: 用多进程/多线程处理已经建立号连接的客户端数据 */
	int new_sockfd = -1;
	pthread_t tid;
	void *thrd_ret;	
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);

#endif
	while(1)
	{

#if 1	
	if ((new_sockfd = accept (socket_fd, (struct sockaddr *) &cin, &addrlen)) < 0) 
	{
			perror ("accept");
			exit (1);
	}

	char ipv4_addr[16];
	if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) 
	{
			perror ("inet_ntop");
			exit (1);
	}
	
	printf ("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs (cin.sin_port));

	pthread_create (&tid, NULL, (void *) cli_data_handle, (void *) &new_sockfd);
	
#endif
	}
	
	
	if( pthread_join(tid, &thrd_ret) != 0 )
	{
		perror("pthread_join");
		exit(1);
	}
	//Step6:关闭套接字
	close(new_sockfd);
	close(socket_fd);

	
	exit(0);

}

void cli_data_handle (void *arg)
{
	int new_sockfd = *(int *) arg;

	printf ("handler thread: newfd =%d\n", new_sockfd);

	//..和newfd进行数据读写
	
	int ret_sock = -1;
	int ret_write = -1;
	int ret_read = -1;
	
	char buf[BUFSIZ];
	char send_buf[BUFSIZ];
	char display_buf[BUFSIZ];

	while (1) 
	{
		bzero(buf, BUFSIZ);
		bzero(send_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);

		do{
			ret_read = read(new_sockfd, buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}
		printf("Receive data: %s\n", buf);
		if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Client is exiting!\n ");
			break;
		}
	
		printf("Server Input data: ");
		if(fgets (send_buf, BUFSIZ - 1, stdin) == NULL)
		{
			continue;
		}
		if( !strncasecmp(send_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
		do {
			ret_sock = write (new_sockfd, send_buf, strlen (send_buf));
			
			//sprintf(display_buf,"Client: %s",buf);
			sprintf(display_buf,"Server: %s",send_buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno  && ret_write  < 0); 				
	}
	close (new_sockfd);
	
	pthread_exit(NULL);
}

Client.c

#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	int fd_w = -1;	
	
	if( argc < 3)
	{
		printf("Input Error\n");
		exit(1);
	}
	
	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}

	//Step1:创建socket 文件描述符 
	if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(1);
	}

	//连接服务器	
	struct sockaddr_in sin;
	bzero(&sin, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//将主机字节序转化成网络字节序

	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}

	//Step2:建立连接
	if (connect (socket_fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) 		{
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");
	

	//Step3:进行数据读写
	int ret_sock = -1; 
	int ret_write = -1;
	int ret_read = -1;
	char buf[BUFSIZ];
	char rece_buf[BUFSIZ];
	char display_buf[BUFSIZ];

	while(1)
	{
		
#if 1
		bzero (buf, BUFSIZ);
		bzero (rece_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);
		
		printf("Client Input data: ");
		if (fgets (buf, BUFSIZ - 1, stdin) == NULL) 
		{
			continue;
		}
		do {
			ret_sock = write (socket_fd, buf, strlen (buf));
			
			sprintf(display_buf,"Client: %s",buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno && ret_write < 0); 

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client is exiting!\n");
			break;
		}
#endif

		//------------------------------------------------------------------
		do{
			ret_read =read(socket_fd, rece_buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}

		printf("Client Receive data: %s\n", rece_buf);
		if( !strncasecmp(rece_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
	}
	
	//Step3:关闭套接字
	close(fd_w);
	close(socket_fd);

	exit(0);

}

示例TCP多经程:

Server.c

#include "Net.h"
#include <signal.h>

void cli_data_handle (void *arg);
int fd_w = -1;	

void sig_child_handle(int signo)
{
	if(SIGCHLD == signo) 
	{
		waitpid(-1, NULL,  WNOHANG);
	}
}

int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	

	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}

	signal(SIGCHLD, sig_child_handle);	

	//Step1:创建socket 文件描述符 
	if ((socket_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) 
	{
		perror ("socket");
		exit (1);
	}
	
	/*优化: 允许绑定地址快速重用 */
	int b_reuse = 1;
	setsockopt (socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
	
	//连接服务器	
	struct sockaddr_in sin;
	bzero (&sin, sizeof (sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号
	sin.sin_addr.s_addr = htonl (INADDR_ANY);

	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}
	
	//Step2-绑定
	if( bind(socket_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("bind");
		exit(1);
	}
	
	//Step3-调用listen() 
	if( listen(socket_fd, BACKLOG) < 0)
	{
		perror("listen");
		exit(1);
	}
	
	printf(" Server Starting... OK!\n ");
	
	

#if 1
/* 优化: 用多进程/多线程处理已经建立号连接的客户端数据 */
	int new_sockfd = -1;
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	
#endif
	while(1)
	{
		
		pid_t pid = -1;
		if ((new_sockfd = accept (socket_fd, (struct sockaddr *) &cin, &addrlen)) < 0) 
		{
				perror ("accept");
				exit (1);
		}
		
		/*创建一个子进程用于处理已建立连接的客户的交互数据*/
		if ((pid = fork()) < 0) 
		{
			perror("fork");
			break;
		}

		if(pid == 0)
		{
			close(socket_fd);
			char ipv4_addr[16];

			if (!inet_ntop (AF_INET, (void *) &cin.sin_addr, ipv4_addr, sizeof (cin))) 
			{
					perror ("inet_ntop");
					exit (1);
			}
	
			printf ("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs (cin.sin_port));
			cli_data_handle(&new_sockfd );
			return 0;
		}
		else
		{
			close(new_sockfd);
		}

	}
	
	
	
	//Step6:关闭套接字
	close(new_sockfd);
	close(socket_fd);
	
	exit(0);

}

void cli_data_handle (void *arg)
{
	int new_sockfd = *(int *) arg;

	printf ("Child handling process: newfd =%d\n", new_sockfd);

	//..和newfd进行数据读写
	
	int ret_sock = -1;
	int ret_write = -1;
	int ret_read = -1;
	
	char buf[BUFSIZ];
	char send_buf[BUFSIZ];
	char display_buf[BUFSIZ];

	while (1) 
	{
		bzero(buf, BUFSIZ);
		bzero(send_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);

		do{
			ret_read = read(new_sockfd, buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}
		printf("Receive data: %s\n", buf);
		if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Client is exiting!\n ");
			break;
		}
	
		printf("Server Input data: ");
		if(fgets (send_buf, BUFSIZ - 1, stdin) == NULL)
		{
			continue;
		}
		if( !strncasecmp(send_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
		do {
			ret_sock = write (new_sockfd, send_buf, strlen (send_buf));
			
			//sprintf(display_buf,"Client: %s",buf);
			sprintf(display_buf,"Server: %s",send_buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno  && ret_write  < 0); 				
	}
	close (new_sockfd);
	
}

Client.c

#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	int fd_w = -1;	
	int port = -1;

	if( argc < 3)
	{
		printf("Input Error\n");
		exit(1);
	}
	port = atoi (argv[2]);
	if (port < 5000) 
	{
		printf("Port Input Error\n");
		exit (1);
	}
	
	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}

	//Step1:创建socket 文件描述符 
	if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(1);
	}

	//连接服务器	
	struct sockaddr_in sin;
	bzero(&sin, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//将主机字节序转化成网络字节序

	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}

	//Step2:建立连接
	if (connect (socket_fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) 		{
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");
	

	//Step3:进行数据读写
	int ret_sock = -1; 
	int ret_write = -1;
	int ret_read = -1;
	char buf[BUFSIZ];
	char rece_buf[BUFSIZ];
	char display_buf[BUFSIZ];

	while(1)
	{
		
#if 1
		bzero (buf, BUFSIZ);
		bzero (rece_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);
		
		printf("Client Input data: ");
		if (fgets (buf, BUFSIZ - 1, stdin) == NULL) 
		{
			continue;
		}
		do {
			ret_sock = write (socket_fd, buf, strlen (buf));
			
			sprintf(display_buf,"Client: %s",buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno && ret_write < 0); 

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client is exiting!\n");
			break;
		}
#endif

		//------------------------------------------------------------------
		do{
			ret_read =read(socket_fd, rece_buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}

		printf("Client Receive data: %s\n", rece_buf);
		if( !strncasecmp(rece_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
	}
	
	//Step3:关闭套接字
	close(fd_w);
	close(socket_fd);

	exit(0);

}