Linux网络编程——并发服务器模型


分类:

    服务器设计技术有很多,按使用的协议来分有 TCP 服务器和 UDP 服务器,按处理方式来分有循环服务器并发服务器


    循环服务器与并发服务器模型

    在网络程序里面,一般来说都是许多客户对应一个服务器(多对一),为了处理客户的请求,对服务端的程序就提出了特殊的要求。


    目前最常用的服务器模型有:

    ·循环服务器:服务器在同一时刻只能响应一个客户端的请求

    ·并发服务器:服务器在同一时刻可以响应多个客户端的请求


    UDP 循环服务器的实现方法

    UDP 循环服务器每次从套接字上读取一个客户端的请求 -> 处理 -> 然后将结果返回给客户机。


    因为 UDP 是非面向连接的,没有一个客户端可以老是占住服务端。只要处理过程不是死循环,或者耗时不是很长,服务器对于每一个客户机的请求在某种程度上来说是能够满足。


    UDP 循环服务器模型为

    1. socket(...); // 创建套接字  
    2. bind(...);   // 绑定  
    3.   
    4. while(1)  
    5. {  
    6.     recvfrom(...); // 接收客户端的请求  
    7.     process(...);  // 处理请求  
    8.     sendto(...);   // 反馈处理结果  
    9. }  
    socket(...); // 创建套接字
    bind(...);   // 绑定
    
    while(1)
    {
    	recvfrom(...); // 接收客户端的请求
    	process(...);  // 处理请求
    	sendto(...);   // 反馈处理结果
    }


    示例代码如下:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>  
    4. #include <unistd.h>  
    5. #include <sys/socket.h>  
    6. #include <netinet/in.h>  
    7. #include <arpa/inet.h>  
    8.   
    9. int main(int argc, char *argv[])  
    10. {  
    11.     unsigned short port = 8080; // 本地端口  
    12.   
    13.     int sockfd;  
    14.     sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建udp套接字  
    15.     if(sockfd < 0)  
    16.     {  
    17.         perror("socket");  
    18.         exit(-1);  
    19.     }  
    20.       
    21.     // 初始化本地网络信息  
    22.     struct sockaddr_in my_addr;  
    23.     bzero(&my_addr, sizeof(my_addr));   // 清空  
    24.     my_addr.sin_family = AF_INET;       // IPv4  
    25.     my_addr.sin_port   = htons(port);   // 端口  
    26.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  
    27.       
    28.     printf("Binding server to port %d\n", port);  
    29.       
    30.     // 绑定  
    31.     int err_log;  
    32.     err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    33.     if(err_log != 0)  
    34.     {  
    35.         perror("bind");  
    36.         close(sockfd);        
    37.         exit(-1);  
    38.     }  
    39.       
    40.     printf("receive data...\n");  
    41.     while(1)  
    42.     {  
    43.         int recv_len;  
    44.         char recv_buf[512] = {0};  
    45.         struct sockaddr_in client_addr;  
    46.         char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16  
    47.         socklen_t cliaddr_len = sizeof(client_addr);  
    48.           
    49.         // 接收客户端数据  
    50.         recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);  
    51.           
    52.         // 处理数据,这里只是把接收过来的数据打印  
    53.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    54.         printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port)); // 客户端的ip  
    55.         printf("data(%d):%s\n",recv_len,recv_buf);  // 客户端的数据  
    56.           
    57.         // 反馈结果,这里把接收直接到客户端的数据回复过去  
    58.         sendto(sockfd, recv_buf, recv_len, 0, (struct sockaddr*)&client_addr, cliaddr_len);  
    59.     }  
    60.       
    61.     close(sockfd);  
    62.       
    63.     return 0;  
    64. }  
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char *argv[])
    {
    	unsigned short port = 8080;	// 本地端口
    
    	int sockfd;
    	sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建udp套接字
    	if(sockfd < 0)
    	{
    		perror("socket");
    		exit(-1);
    	}
    	
    	// 初始化本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));	// 清空
    	my_addr.sin_family = AF_INET;		// IPv4
    	my_addr.sin_port   = htons(port);	// 端口
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
    	
    	printf("Binding server to port %d\n", port);
    	
    	// 绑定
    	int err_log;
    	err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	printf("receive data...\n");
    	while(1)
    	{
    		int recv_len;
    		char recv_buf[512] = {0};
    		struct sockaddr_in client_addr;
    		char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
    		socklen_t cliaddr_len = sizeof(client_addr);
    		
    		// 接收客户端数据
    		recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
    		
    		// 处理数据,这里只是把接收过来的数据打印
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port)); // 客户端的ip
    		printf("data(%d):%s\n",recv_len,recv_buf);	// 客户端的数据
    		
    		// 反馈结果,这里把接收直接到客户端的数据回复过去
    		sendto(sockfd, recv_buf, recv_len, 0, (struct sockaddr*)&client_addr, cliaddr_len);
    	}
    	
    	close(sockfd);
    	
    	return 0;
    }

    运行结果如下:


    TCP 循环服务器的实现方法

    TCP 循环服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接。TCP 循环服务器一次只能处理一个客户端的请求,只有在这个客户的所有请求满足后,服务器才可以继续后面的请求如果有一个客户端占住服务器不放时,其它的客户机都不能工作了,因此,TCP 服务器一般很少用循环服务器模型的。


    TCP循环服务器模型为:

    1. socket(...);// 创建套接字  
    2. bind(...);// 绑定  
    3. listen(...);// 监听  
    4.   
    5. while(1)  
    6. {  
    7.     accept(...);// 取出客户端的请求连接  
    8.     process(...);// 处理请求,反馈结果  
    9.     close(...);// 关闭连接套接字:accept()返回的套接字  
    10. }  
    socket(...);// 创建套接字
    bind(...);// 绑定
    listen(...);// 监听
    
    while(1)
    {
    	accept(...);// 取出客户端的请求连接
    	process(...);// 处理请求,反馈结果
    	close(...);// 关闭连接套接字:accept()返回的套接字
    }


    示例代码如下:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>                         
    4. #include <unistd.h>  
    5. #include <sys/socket.h>  
    6. #include <netinet/in.h>  
    7. #include <arpa/inet.h>  
    8.               
    9. int main(int argc, char *argv[])  
    10. {  
    11.     unsigned short port = 8080;     // 本地端口   
    12.   
    13.     // 创建tcp套接字  
    14.     int sockfd = socket(AF_INET, SOCK_STREAM, 0);     
    15.     if(sockfd < 0)  
    16.     {  
    17.         perror("socket");  
    18.         exit(-1);  
    19.     }  
    20.       
    21.     // 配置本地网络信息  
    22.     struct sockaddr_in my_addr;  
    23.     bzero(&my_addr, sizeof(my_addr));     // 清空     
    24.     my_addr.sin_family = AF_INET;         // IPv4  
    25.     my_addr.sin_port   = htons(port);     // 端口  
    26.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  
    27.       
    28.     // 绑定  
    29.     int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    30.     if( err_log != 0)  
    31.     {  
    32.         perror("binding");  
    33.         close(sockfd);        
    34.         exit(-1);  
    35.     }  
    36.       
    37.     // 监听,套接字变被动  
    38.     err_log = listen(sockfd, 10);   
    39.     if(err_log != 0)  
    40.     {  
    41.         perror("listen");  
    42.         close(sockfd);        
    43.         exit(-1);  
    44.     }     
    45.       
    46.     printf("listen client @port=%d...\n",port);  
    47.   
    48.     while(1)  
    49.     {     
    50.       
    51.         struct sockaddr_in client_addr;          
    52.         char cli_ip[INET_ADDRSTRLEN] = "";       
    53.         socklen_t cliaddr_len = sizeof(client_addr);      
    54.           
    55.         // 取出客户端已完成的连接  
    56.         int connfd;  
    57.         connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);         
    58.         if(connfd < 0)  
    59.         {  
    60.             perror("accept");  
    61.             continue;  
    62.         }  
    63.   
    64.         // 打印客户端的ip和端口  
    65.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    66.         printf("----------------------------------------------\n");  
    67.         printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));  
    68.           
    69.         // 接收数据  
    70.         char recv_buf[512] = {0};  
    71.         int len =  recv(connfd, recv_buf, sizeof(recv_buf), 0);  
    72.           
    73.         // 处理数据,这里只是打印接收到的内容  
    74.         printf("\nrecv data:\n");  
    75.         printf("%s\n",recv_buf);  
    76.           
    77.         // 反馈结果  
    78.         send(connfd, recv_buf, len, 0);  
    79.           
    80.         close(connfd);     //关闭已连接套接字  
    81.         printf("client closed!\n");  
    82.     }  
    83.       
    84.     close(sockfd);         //关闭监听套接字  
    85.       
    86.     return 0;  
    87. }  
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    			
    int main(int argc, char *argv[])
    {
    	unsigned short port = 8080;		// 本地端口	
    
    	// 创建tcp套接字
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0);   
    	if(sockfd < 0)
    	{
    		perror("socket");
    		exit(-1);
    	}
    	
    	// 配置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));	  // 清空   
    	my_addr.sin_family = AF_INET;		  // IPv4
    	my_addr.sin_port   = htons(port);	  // 端口
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
    	
    	// 绑定
    	int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if( err_log != 0)
    	{
    		perror("binding");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10); 
    	if(err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}	
    	
    	printf("listen client @port=%d...\n",port);
    
    	while(1)
    	{	
    	
    		struct sockaddr_in client_addr;		   
    		char cli_ip[INET_ADDRSTRLEN] = "";	   
    		socklen_t cliaddr_len = sizeof(client_addr);    
    		
    		// 取出客户端已完成的连接
    		int connfd;
    		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);       
    		if(connfd < 0)
    		{
    			perror("accept");
    			continue;
    		}
    
    		// 打印客户端的ip和端口
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("----------------------------------------------\n");
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		// 接收数据
    		char recv_buf[512] = {0};
    		int len =  recv(connfd, recv_buf, sizeof(recv_buf), 0);
    		
    		// 处理数据,这里只是打印接收到的内容
    		printf("\nrecv data:\n");
    		printf("%s\n",recv_buf);
    		
    		// 反馈结果
    		send(connfd, recv_buf, len, 0);
    		
    		close(connfd);     //关闭已连接套接字
    		printf("client closed!\n");
    	}
    	
    	close(sockfd);         //关闭监听套接字
    	
    	return 0;
    }
    

    运行结果如下:



    三种并发服务器实现方法

    一个好的服务器,一般都是并发服务器(同一时刻可以响应多个客户端的请求)。并发服务器设计技术一般有:多进程服务器、多线程服务器、I/O复用服务器等。


    多进程并发服务器

    在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求。父进程继续等待其它客户的请求。这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中。对于一个 TCP 服务器,客户与服务器的连接可能并不马上关闭,可能会等到客户提交某些数据后再关闭,这段时间服务器端的进程会阻塞,所以这时操作系统可能调度其它客户服务进程,这比起循环服务器大大提高了服务性能


    TCP多进程并发服务器
    TCP 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。


    示例代码如下:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>                         
    4. #include <unistd.h>  
    5. #include <sys/socket.h>  
    6. #include <netinet/in.h>  
    7. #include <arpa/inet.h>      
    8.   
    9. int main(int argc, char *argv[])  
    10. {  
    11.     unsigned short port = 8080;     // 本地端口   
    12.   
    13.     // 创建tcp套接字  
    14.     int sockfd = socket(AF_INET, SOCK_STREAM, 0);     
    15.     if(sockfd < 0)  
    16.     {  
    17.         perror("socket");  
    18.         exit(-1);  
    19.     }  
    20.       
    21.     // 配置本地网络信息  
    22.     struct sockaddr_in my_addr;  
    23.     bzero(&my_addr, sizeof(my_addr));     // 清空     
    24.     my_addr.sin_family = AF_INET;         // IPv4  
    25.     my_addr.sin_port   = htons(port);     // 端口  
    26.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  
    27.       
    28.     // 绑定  
    29.     int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    30.     if( err_log != 0)  
    31.     {  
    32.         perror("binding");  
    33.         close(sockfd);        
    34.         exit(-1);  
    35.     }  
    36.       
    37.     // 监听,套接字变被动  
    38.     err_log = listen(sockfd, 10);   
    39.     if(err_log != 0)  
    40.     {  
    41.         perror("listen");  
    42.         close(sockfd);        
    43.         exit(-1);  
    44.     }  
    45.       
    46.     while(1) //主进程 循环等待客户端的连接  
    47.     {  
    48.           
    49.         char cli_ip[INET_ADDRSTRLEN] = {0};  
    50.         struct sockaddr_in client_addr;  
    51.         socklen_t cliaddr_len = sizeof(client_addr);  
    52.           
    53.         // 取出客户端已完成的连接  
    54.         int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);  
    55.         if(connfd < 0)  
    56.         {  
    57.             perror("accept");  
    58.             close(sockfd);  
    59.             exit(-1);  
    60.         }  
    61.           
    62.         pid_t pid = fork();  
    63.         if(pid < 0){  
    64.             perror("fork");  
    65.             _exit(-1);  
    66.         }else if(0 == pid){ //子进程 接收客户端的信息,并发还给客户端  
    67.             /*关闭不需要的套接字可节省系统资源, 
    68.               同时可避免父子进程共享这些套接字 
    69.               可能带来的不可预计的后果 
    70.             */  
    71.             close(sockfd);   // 关闭监听套接字,这个套接字是从父进程继承过来  
    72.           
    73.             char recv_buf[1024] = {0};  
    74.             int recv_len = 0;  
    75.               
    76.             // 打印客户端的 ip 和端口  
    77.             memset(cli_ip, 0, sizeof(cli_ip)); // 清空  
    78.             inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    79.             printf("----------------------------------------------\n");  
    80.             printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));  
    81.               
    82.             // 接收数据  
    83.             while( (recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0 )  
    84.             {  
    85.                 printf("recv_buf: %s\n", recv_buf); // 打印数据  
    86.                 send(connfd, recv_buf, recv_len, 0); // 给客户端回数据  
    87.             }  
    88.               
    89.             printf("client closed!\n");  
    90.               
    91.             close(connfd);    //关闭已连接套接字  
    92.               
    93.             exit(0);  
    94.         }else if(pid > 0){   // 父进程  
    95.           
    96.             close(connfd);    //关闭已连接套接字  
    97.               
    98.         }  
    99.     }  
    100.       
    101.     close(sockfd);  
    102.       
    103.     return 0;  
    104. }  
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>	
    
    int main(int argc, char *argv[])
    {
    	unsigned short port = 8080;		// 本地端口	
    
    	// 创建tcp套接字
    	int sockfd = socket(AF_INET, SOCK_STREAM, 0);   
    	if(sockfd < 0)
    	{
    		perror("socket");
    		exit(-1);
    	}
    	
    	// 配置本地网络信息
    	struct sockaddr_in my_addr;
    	bzero(&my_addr, sizeof(my_addr));	  // 清空   
    	my_addr.sin_family = AF_INET;		  // IPv4
    	my_addr.sin_port   = htons(port);	  // 端口
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
    	
    	// 绑定
    	int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if( err_log != 0)
    	{
    		perror("binding");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10); 
    	if(err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	while(1) //主进程 循环等待客户端的连接
    	{
    		
    		char cli_ip[INET_ADDRSTRLEN] = {0};
    		struct sockaddr_in client_addr;
    		socklen_t cliaddr_len = sizeof(client_addr);
    		
    		// 取出客户端已完成的连接
    		int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    		if(connfd < 0)
    		{
    			perror("accept");
    			close(sockfd);
    			exit(-1);
    		}
    		
    		pid_t pid = fork();
    		if(pid < 0){
    			perror("fork");
    			_exit(-1);
    		}else if(0 == pid){ //子进程 接收客户端的信息,并发还给客户端
    			/*关闭不需要的套接字可节省系统资源,
    			  同时可避免父子进程共享这些套接字
    			  可能带来的不可预计的后果
    			*/
    			close(sockfd);   // 关闭监听套接字,这个套接字是从父进程继承过来
    		
    			char recv_buf[1024] = {0};
    			int recv_len = 0;
    			
    			// 打印客户端的 ip 和端口
    			memset(cli_ip, 0, sizeof(cli_ip)); // 清空
    			inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    			printf("----------------------------------------------\n");
    			printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    			
    			// 接收数据
    			while( (recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0 )
    			{
    				printf("recv_buf: %s\n", recv_buf); // 打印数据
    				send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    			}
    			
    			printf("client closed!\n");
    			
    			close(connfd);    //关闭已连接套接字
    			
    			exit(0);
    		}else if(pid > 0){	// 父进程
    		
    			close(connfd);    //关闭已连接套接字
    			
    		}
    	}
    	
    	close(sockfd);
    	
        return 0;
    }


    运行结果如下:



    多线程服务器

    多线程服务器是对多进程的服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题


    以下是多线程服务器模板:


    示例代码如下:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>                         
    4. #include <unistd.h>  
    5. #include <sys/socket.h>  
    6. #include <netinet/in.h>  
    7. #include <arpa/inet.h>                      
    8. #include <pthread.h>  
    9.   
    10. /************************************************************************ 
    11. 函数名称:   void *client_process(void *arg) 
    12. 函数功能:   线程函数,处理客户信息 
    13. 函数参数:   已连接套接字 
    14. 函数返回:   无 
    15. ************************************************************************/  
    16. void *client_process(void *arg)  
    17. {  
    18.     int recv_len = 0;  
    19.     char recv_buf[1024] = "";   // 接收缓冲区  
    20.     int connfd = (int)arg; // 传过来的已连接套接字  
    21.   
    22.     // 接收数据  
    23.     while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)  
    24.     {  
    25.         printf("recv_buf: %s\n", recv_buf); // 打印数据  
    26.         send(connfd, recv_buf, recv_len, 0); // 给客户端回数据  
    27.     }  
    28.       
    29.     printf("client closed!\n");  
    30.     close(connfd);  //关闭已连接套接字  
    31.       
    32.     return  NULL;  
    33. }  
    34.   
    35. //===============================================================  
    36. // 语法格式:    void main(void)  
    37. // 实现功能:    主函数,建立一个TCP并发服务器  
    38. // 入口参数:    无  
    39. // 出口参数:    无  
    40. //===============================================================  
    41. int main(int argc, char *argv[])  
    42. {  
    43.     int sockfd = 0;             // 套接字  
    44.     int connfd = 0;  
    45.     int err_log = 0;  
    46.     struct sockaddr_in my_addr; // 服务器地址结构体  
    47.     unsigned short port = 8080; // 监听端口  
    48.     pthread_t thread_id;  
    49.       
    50.     printf("TCP Server Started at port %d!\n", port);  
    51.       
    52.     sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字  
    53.     if(sockfd < 0)  
    54.     {  
    55.         perror("socket error");  
    56.         exit(-1);  
    57.     }  
    58.       
    59.     bzero(&my_addr, sizeof(my_addr));      // 初始化服务器地址  
    60.     my_addr.sin_family = AF_INET;  
    61.     my_addr.sin_port   = htons(port);  
    62.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    63.       
    64.       
    65.     printf("Binding server to port %d\n", port);  
    66.       
    67.     // 绑定  
    68.     err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    69.     if(err_log != 0)  
    70.     {  
    71.         perror("bind");  
    72.         close(sockfd);        
    73.         exit(-1);  
    74.     }  
    75.       
    76.     // 监听,套接字变被动  
    77.     err_log = listen(sockfd, 10);  
    78.     if( err_log != 0)  
    79.     {  
    80.         perror("listen");  
    81.         close(sockfd);        
    82.         exit(-1);  
    83.     }  
    84.       
    85.     printf("Waiting client...\n");  
    86.       
    87.     while(1)  
    88.     {  
    89.         char cli_ip[INET_ADDRSTRLEN] = "";     // 用于保存客户端IP地址  
    90.         struct sockaddr_in client_addr;        // 用于保存客户端地址  
    91.         socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!  
    92.           
    93.         //获得一个已经建立的连接     
    94.         connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);                                
    95.         if(connfd < 0)  
    96.         {  
    97.             perror("accept this time");  
    98.             continue;  
    99.         }  
    100.           
    101.         // 打印客户端的 ip 和端口  
    102.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    103.         printf("----------------------------------------------\n");  
    104.         printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));  
    105.           
    106.         if(connfd > 0)  
    107.         {  
    108.             //由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,值传递。  
    109.             pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd);  //创建线程  
    110.             pthread_detach(thread_id); // 线程分离,结束时自动回收资源  
    111.         }  
    112.     }  
    113.       
    114.     close(sockfd);  
    115.       
    116.     return 0;  
    117. }  
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>					
    #include <pthread.h>
    
    /************************************************************************
    函数名称:	void *client_process(void *arg)
    函数功能:	线程函数,处理客户信息
    函数参数:	已连接套接字
    函数返回:	无
    ************************************************************************/
    void *client_process(void *arg)
    {
    	int recv_len = 0;
    	char recv_buf[1024] = "";	// 接收缓冲区
    	int connfd = (int)arg; // 传过来的已连接套接字
    
    	// 接收数据
    	while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
    	{
    		printf("recv_buf: %s\n", recv_buf); // 打印数据
    		send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    	}
    	
    	printf("client closed!\n");
    	close(connfd);	//关闭已连接套接字
    	
    	return 	NULL;
    }
    
    //===============================================================
    // 语法格式:	void main(void)
    // 实现功能:	主函数,建立一个TCP并发服务器
    // 入口参数:	无
    // 出口参数:	无
    //===============================================================
    int main(int argc, char *argv[])
    {
    	int sockfd = 0;				// 套接字
    	int connfd = 0;
    	int err_log = 0;
    	struct sockaddr_in my_addr;	// 服务器地址结构体
    	unsigned short port = 8080; // 监听端口
    	pthread_t thread_id;
    	
    	printf("TCP Server Started at port %d!\n", port);
    	
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字
    	if(sockfd < 0)
    	{
    		perror("socket error");
    		exit(-1);
    	}
    	
    	bzero(&my_addr, sizeof(my_addr));	   // 初始化服务器地址
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	
    	printf("Binding server to port %d\n", port);
    	
    	// 绑定
    	err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10);
    	if( err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	printf("Waiting client...\n");
    	
    	while(1)
    	{
    		char cli_ip[INET_ADDRSTRLEN] = "";	   // 用于保存客户端IP地址
    		struct sockaddr_in client_addr;		   // 用于保存客户端地址
    		socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!
    		
    		//获得一个已经建立的连接	
    		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);   							
    		if(connfd < 0)
    		{
    			perror("accept this time");
    			continue;
    		}
    		
    		// 打印客户端的 ip 和端口
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("----------------------------------------------\n");
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		if(connfd > 0)
    		{
    			//由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,值传递。
    			pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd);  //创建线程
    			pthread_detach(thread_id); // 线程分离,结束时自动回收资源
    		}
    	}
    	
    	close(sockfd);
    	
    	return 0;
    }
    

    运行结果如下:



    注意,上面例子给线程传参有很大的局限性,最简单的一种情况,如果我们需要给线程传多个参数,这时候我们需要结构体传参,这种值传递编译都通不过,这里之所以能够这么值传递,是因为, int 长度时 4 个字节, void * 长度也是 4 个字节。

    1. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);  
    2. pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd);   
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd); 


    如果考虑类型匹配的话,应该是这么传参,pthread_create()最后一个参数应该传地址( &connfd ),而不是值:

    1. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);  
    2. pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);   
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); 

    但是,如果按地址传递的话,又会有这么一个问题,假如有多个客户端要连接这个服务器,正常的情况下,一个客户端连接对应一个 connfd,相互之间独立不受影响,但是,假如多个客户端同时连接这个服务器,A 客户端的连接套接字为 connfd,服务器正在用这个 connfd 处理数据,还没有处理完,突然来了一个 B 客户端,accept()之后又生成一个 connfd, 因为是地址传递, A 客户端的连接套接字也变成 B 这个了,这样的话,服务器肯定不能再为 A 客户端服务器了,这时候,我们就需要考虑多任务的互斥或同步问题了,这里通过互斥锁来解决这个问题,确保这个connfd值被一个临时变量保存过后,才允许修改

    1. #include <pthread.h>  
    2.   
    3. pthread_mutex_t mutex;  // 定义互斥锁,全局变量  
    4.   
    5. pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的  
    6.   
    7. // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞  
    8. pthread_mutex_lock(&mutex);   
    9. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);  
    10.   
    11. //给回调函数传的参数,&connfd,地址传递  
    12. pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程  
    13.   
    14. // 线程回调函数  
    15. void *client_process(void *arg)  
    16. {  
    17.     int connfd = *(int *)arg; // 传过来的已连接套接字  
    18.       
    19.     // 解锁,pthread_mutex_lock()唤醒,不阻塞  
    20.     pthread_mutex_unlock(&mutex);   
    21.       
    22.     return  NULL;  
    23. }  
    #include <pthread.h>
    
    pthread_mutex_t mutex;	// 定义互斥锁,全局变量
    
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
    
    // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
    pthread_mutex_lock(&mutex);	
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    
    //给回调函数传的参数,&connfd,地址传递
    pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程
    
    // 线程回调函数
    void *client_process(void *arg)
    {
    	int connfd = *(int *)arg; // 传过来的已连接套接字
    	
    	// 解锁,pthread_mutex_lock()唤醒,不阻塞
    	pthread_mutex_unlock(&mutex); 
    	
    	return 	NULL;
    }


    修改的完整代码如下:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <string.h>                         
    4. #include <unistd.h>  
    5. #include <sys/socket.h>  
    6. #include <netinet/in.h>  
    7. #include <arpa/inet.h>                      
    8. #include <pthread.h>  
    9.   
    10. pthread_mutex_t mutex;  // 定义互斥锁,全局变量  
    11.   
    12. /************************************************************************ 
    13. 函数名称:   void *client_process(void *arg) 
    14. 函数功能:   线程函数,处理客户信息 
    15. 函数参数:   已连接套接字 
    16. 函数返回:   无 
    17. ************************************************************************/  
    18. void *client_process(void *arg)  
    19. {  
    20.     int recv_len = 0;  
    21.     char recv_buf[1024] = "";   // 接收缓冲区  
    22.     int connfd = *(int *)arg; // 传过来的已连接套接字  
    23.       
    24.     // 解锁,pthread_mutex_lock()唤醒,不阻塞  
    25.     pthread_mutex_unlock(&mutex);   
    26.       
    27.     // 接收数据  
    28.     while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)  
    29.     {  
    30.         printf("recv_buf: %s\n", recv_buf); // 打印数据  
    31.         send(connfd, recv_buf, recv_len, 0); // 给客户端回数据  
    32.     }  
    33.       
    34.     printf("client closed!\n");  
    35.     close(connfd);  //关闭已连接套接字  
    36.       
    37.     return  NULL;  
    38. }  
    39.   
    40. //===============================================================  
    41. // 语法格式:    void main(void)  
    42. // 实现功能:    主函数,建立一个TCP并发服务器  
    43. // 入口参数:    无  
    44. // 出口参数:    无  
    45. //===============================================================  
    46. int main(int argc, char *argv[])  
    47. {  
    48.     int sockfd = 0;             // 套接字  
    49.     int connfd = 0;  
    50.     int err_log = 0;  
    51.     struct sockaddr_in my_addr; // 服务器地址结构体  
    52.     unsigned short port = 8080; // 监听端口  
    53.     pthread_t thread_id;  
    54.       
    55.     pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的  
    56.       
    57.     printf("TCP Server Started at port %d!\n", port);  
    58.       
    59.     sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字  
    60.     if(sockfd < 0)  
    61.     {  
    62.         perror("socket error");  
    63.         exit(-1);  
    64.     }  
    65.       
    66.     bzero(&my_addr, sizeof(my_addr));      // 初始化服务器地址  
    67.     my_addr.sin_family = AF_INET;  
    68.     my_addr.sin_port   = htons(port);  
    69.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    70.       
    71.       
    72.     printf("Binding server to port %d\n", port);  
    73.       
    74.     // 绑定  
    75.     err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    76.     if(err_log != 0)  
    77.     {  
    78.         perror("bind");  
    79.         close(sockfd);        
    80.         exit(-1);  
    81.     }  
    82.       
    83.     // 监听,套接字变被动  
    84.     err_log = listen(sockfd, 10);  
    85.     if( err_log != 0)  
    86.     {  
    87.         perror("listen");  
    88.         close(sockfd);        
    89.         exit(-1);  
    90.     }  
    91.       
    92.     printf("Waiting client...\n");  
    93.       
    94.     while(1)  
    95.     {  
    96.         char cli_ip[INET_ADDRSTRLEN] = "";     // 用于保存客户端IP地址  
    97.         struct sockaddr_in client_addr;        // 用于保存客户端地址  
    98.         socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!  
    99.           
    100.         // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞  
    101.         pthread_mutex_lock(&mutex);   
    102.           
    103.         //获得一个已经建立的连接     
    104.         connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);                                
    105.         if(connfd < 0)  
    106.         {  
    107.             perror("accept this time");  
    108.             continue;  
    109.         }  
    110.           
    111.         // 打印客户端的 ip 和端口  
    112.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    113.         printf("----------------------------------------------\n");  
    114.         printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));  
    115.           
    116.         if(connfd > 0)  
    117.         {  
    118.             //给回调函数传的参数,&connfd,地址传递  
    119.             pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程  
    120.             pthread_detach(thread_id); // 线程分离,结束时自动回收资源  
    121.         }  
    122.     }  
    123.       
    124.     close(sockfd);  
    125.       
    126.     return 0;  
    127. }  
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>					
    #include <pthread.h>
    
    pthread_mutex_t mutex;	// 定义互斥锁,全局变量
    
    /************************************************************************
    函数名称:	void *client_process(void *arg)
    函数功能:	线程函数,处理客户信息
    函数参数:	已连接套接字
    函数返回:	无
    ************************************************************************/
    void *client_process(void *arg)
    {
    	int recv_len = 0;
    	char recv_buf[1024] = "";	// 接收缓冲区
    	int connfd = *(int *)arg; // 传过来的已连接套接字
    	
    	// 解锁,pthread_mutex_lock()唤醒,不阻塞
    	pthread_mutex_unlock(&mutex); 
    	
    	// 接收数据
    	while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
    	{
    		printf("recv_buf: %s\n", recv_buf); // 打印数据
    		send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    	}
    	
    	printf("client closed!\n");
    	close(connfd);	//关闭已连接套接字
    	
    	return 	NULL;
    }
    
    //===============================================================
    // 语法格式:	void main(void)
    // 实现功能:	主函数,建立一个TCP并发服务器
    // 入口参数:	无
    // 出口参数:	无
    //===============================================================
    int main(int argc, char *argv[])
    {
    	int sockfd = 0;				// 套接字
    	int connfd = 0;
    	int err_log = 0;
    	struct sockaddr_in my_addr;	// 服务器地址结构体
    	unsigned short port = 8080; // 监听端口
    	pthread_t thread_id;
    	
    	pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
    	
    	printf("TCP Server Started at port %d!\n", port);
    	
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字
    	if(sockfd < 0)
    	{
    		perror("socket error");
    		exit(-1);
    	}
    	
    	bzero(&my_addr, sizeof(my_addr));	   // 初始化服务器地址
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	
    	printf("Binding server to port %d\n", port);
    	
    	// 绑定
    	err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10);
    	if( err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	printf("Waiting client...\n");
    	
    	while(1)
    	{
    		char cli_ip[INET_ADDRSTRLEN] = "";	   // 用于保存客户端IP地址
    		struct sockaddr_in client_addr;		   // 用于保存客户端地址
    		socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!
    		
    		// 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
    		pthread_mutex_lock(&mutex);	
    		
    		//获得一个已经建立的连接	
    		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);   							
    		if(connfd < 0)
    		{
    			perror("accept this time");
    			continue;
    		}
    		
    		// 打印客户端的 ip 和端口
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("----------------------------------------------\n");
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		if(connfd > 0)
    		{
    			//给回调函数传的参数,&connfd,地址传递
    			pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程
    			pthread_detach(thread_id); // 线程分离,结束时自动回收资源
    		}
    	}
    	
    	close(sockfd);
    	
    	return 0;
    }


    I/O复用服务器

    I/O 复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。它也可用于并发服务器的设计,常用函数 select() 或 epoll() 来实现。详情,请看《select、poll、epoll的区别使用》

    1. socket(...); // 创建套接字  
    2. bind(...);   // 绑定  
    3. listen(...); // 监听  
    4.   
    5. while(1)  
    6. {  
    7.     if(select(...) > 0// 检测监听套接字是否可读  
    8.     {  
    9.         if(FD_ISSET(...)>0// 套接字可读,证明有新客户端连接服务器    
    10.         {  
    11.             accpet(...);// 取出已经完成的连接  
    12.             process(...);// 处理请求,反馈结果  
    13.         }  
    14.     }  
    15.     close(...); // 关闭连接套接字:accept()返回的套接字  
    16. }  
    socket(...); // 创建套接字
    bind(...);   // 绑定
    listen(...); // 监听
    
    while(1)
    {
    	if(select(...) > 0) // 检测监听套接字是否可读
    	{
    		if(FD_ISSET(...)>0) // 套接字可读,证明有新客户端连接服务器  
    		{
    			accpet(...);// 取出已经完成的连接
    			process(...);// 处理请求,反馈结果
    		}
    	}
    	close(...); // 关闭连接套接字:accept()返回的套接字
    }


    示例代码如下:

    1. #include <stdio.h>   
    2. #include <unistd.h>  
    3. #include <stdlib.h>  
    4. #include <errno.h>  
    5. #include <string.h>  
    6. #include <sys/socket.h>  
    7. #include <sys/types.h>  
    8. #include <netinet/in.h>  
    9. #include <arpa/inet.h>  
    10. #include <sys/select.h>  
    11.   
    12. #define SERV_PORT 8080  
    13. #define LIST 20                //服务器最大接受连接  
    14. #define MAX_FD 10              //FD_SET支持描述符数量  
    15.   
    16.   
    17. int main(int argc, char *argv[])  
    18. {  
    19.     int sockfd;  
    20.     int err;  
    21.     int i;  
    22.     int connfd;  
    23.     int fd_all[MAX_FD]; //保存所有描述符,用于select调用后,判断哪个可读  
    24.       
    25.     //下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值  
    26.     fd_set fd_read;    //FD_SET数据备份  
    27.     fd_set fd_select;  //用于select  
    28.   
    29.     struct timeval timeout;         //超时时间备份  
    30.     struct timeval timeout_select;  //用于select  
    31.       
    32.     struct sockaddr_in serv_addr;   //服务器地址  
    33.     struct sockaddr_in cli_addr;    //客户端地址  
    34.     socklen_t serv_len;  
    35.     socklen_t cli_len;  
    36.       
    37.     //超时时间设置  
    38.     timeout.tv_sec = 10;  
    39.     timeout.tv_usec = 0;  
    40.       
    41.     //创建TCP套接字  
    42.     sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    43.     if(sockfd < 0)  
    44.     {  
    45.         perror("fail to socket");  
    46.         exit(1);  
    47.     }  
    48.       
    49.     // 配置本地地址  
    50.     memset(&serv_addr, 0, sizeof(serv_addr));  
    51.     serv_addr.sin_family = AF_INET;         // ipv4  
    52.     serv_addr.sin_port = htons(SERV_PORT);  // 端口, 8080  
    53.     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  
    54.   
    55.   
    56.     serv_len = sizeof(serv_addr);  
    57.       
    58.     // 绑定  
    59.     err = bind(sockfd, (struct sockaddr *)&serv_addr, serv_len);  
    60.     if(err < 0)  
    61.     {  
    62.         perror("fail to bind");  
    63.         exit(1);  
    64.     }  
    65.   
    66.     // 监听  
    67.     err = listen(sockfd, LIST);  
    68.     if(err < 0)  
    69.     {  
    70.         perror("fail to listen");  
    71.         exit(1);  
    72.     }  
    73.       
    74.     //初始化fd_all数组  
    75.     memset(&fd_all, -1, sizeof(fd_all));  
    76.   
    77.     fd_all[0] = sockfd;   //第一个为监听套接字  
    78.       
    79.     FD_ZERO(&fd_read);  // 清空  
    80.     FD_SET(sockfd, &fd_read);  //将监听套接字加入fd_read  
    81.   
    82.     int maxfd;  
    83.     maxfd = fd_all[0];  //监听的最大套接字  
    84.       
    85.     while(1){  
    86.       
    87.         // 每次都需要重新赋值,fd_select,timeout_select每次都会变  
    88.         fd_select = fd_read;  
    89.         timeout_select = timeout;  
    90.           
    91.         // 检测监听套接字是否可读,没有可读,此函数会阻塞  
    92.         // 只要有客户连接,或断开连接,select()都会往下执行  
    93.         err = select(maxfd+1, &fd_select, NULL, NULL, NULL);  
    94.         //err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select);  
    95.         if(err < 0)  
    96.         {  
    97.                 perror("fail to select");  
    98.                 exit(1);  
    99.         }  
    100.   
    101.         if(err == 0){  
    102.             printf("timeout\n");  
    103.         }  
    104.           
    105.         // 检测监听套接字是否可读  
    106.         if( FD_ISSET(sockfd, &fd_select) ){//可读,证明有新客户端连接服务器  
    107.               
    108.             cli_len = sizeof(cli_addr);  
    109.               
    110.             // 取出已经完成的连接  
    111.             connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);  
    112.             if(connfd < 0)  
    113.             {  
    114.                 perror("fail to accept");  
    115.                 exit(1);  
    116.             }  
    117.               
    118.             // 打印客户端的 ip 和端口  
    119.             char cli_ip[INET_ADDRSTRLEN] = {0};  
    120.             inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
    121.             printf("----------------------------------------------\n");  
    122.             printf("client ip=%s,port=%d\n", cli_ip,ntohs(cli_addr.sin_port));  
    123.               
    124.             // 将新连接套接字加入 fd_all 及 fd_read  
    125.             for(i=0; i < MAX_FD; i++){  
    126.                 if(fd_all[i] != -1){  
    127.                     continue;  
    128.                 }else{  
    129.                     fd_all[i] = connfd;  
    130.                     printf("client fd_all[%d] join\n", i);  
    131.                     break;  
    132.                 }  
    133.             }  
    134.               
    135.             FD_SET(connfd, &fd_read);  
    136.               
    137.             if(maxfd < connfd)  
    138.             {  
    139.                 maxfd = connfd;  //更新maxfd  
    140.             }  
    141.           
    142.         }  
    143.           
    144.         //从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd)  
    145.         for(i=1; i < maxfd; i++){  
    146.             if(FD_ISSET(fd_all[i], &fd_select)){  
    147.                 printf("fd_all[%d] is ok\n", i);  
    148.                   
    149.                 char buf[1024]={0};  //读写缓冲区  
    150.                 int num = read(fd_all[i], buf, 1024);  
    151.                 if(num > 0){  
    152.   
    153.                     //收到 客户端数据并打印  
    154.                     printf("receive buf from client fd_all[%d] is: %s\n", i, buf);  
    155.                       
    156.                     //回复客户端  
    157.                     num = write(fd_all[i], buf, num);  
    158.                     if(num < 0){  
    159.                         perror("fail to write ");  
    160.                         exit(1);  
    161.                     }else{  
    162.                         //printf("send reply\n");  
    163.                     }  
    164.                       
    165.                       
    166.                 }else if(0 == num){ // 客户端断开时  
    167.                       
    168.                     //客户端退出,关闭套接字,并从监听集合清除  
    169.                     printf("client:fd_all[%d] exit\n", i);  
    170.                     FD_CLR(fd_all[i], &fd_read);  
    171.                     close(fd_all[i]);  
    172.                     fd_all[i] = -1;  
    173.                       
    174.                     continue;  
    175.                 }  
    176.                   
    177.             }else {  
    178.                 //printf("no data\n");                    
    179.             }  
    180.         }  
    181.       
    182.     }  
    183.       
    184.     return 0;  
    185. }  
    #include <stdio.h> 
    #include <unistd.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/select.h>
    
    #define SERV_PORT 8080
    #define LIST 20                //服务器最大接受连接
    #define MAX_FD 10              //FD_SET支持描述符数量
    
    
    int main(int argc, char *argv[])
    {
    	int sockfd;
    	int err;
    	int i;
    	int connfd;
    	int fd_all[MAX_FD]; //保存所有描述符,用于select调用后,判断哪个可读
    	
    	//下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值
    	fd_set fd_read;    //FD_SET数据备份
        fd_set fd_select;  //用于select
    
    	struct timeval timeout;         //超时时间备份
    	struct timeval timeout_select;  //用于select
    	
    	struct sockaddr_in serv_addr;   //服务器地址
    	struct sockaddr_in cli_addr;    //客户端地址
    	socklen_t serv_len;
    	socklen_t cli_len;
    	
    	//超时时间设置
    	timeout.tv_sec = 10;
    	timeout.tv_usec = 0;
    	
    	//创建TCP套接字
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    	if(sockfd < 0)
    	{
    		perror("fail to socket");
    		exit(1);
    	}
    	
    	// 配置本地地址
    	memset(&serv_addr, 0, sizeof(serv_addr));
    	serv_addr.sin_family = AF_INET;			// ipv4
    	serv_addr.sin_port = htons(SERV_PORT);	// 端口, 8080
    	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
    
    
        serv_len = sizeof(serv_addr);
    	
    	// 绑定
    	err = bind(sockfd, (struct sockaddr *)&serv_addr, serv_len);
    	if(err < 0)
    	{
    		perror("fail to bind");
    		exit(1);
    	}
    
    	// 监听
    	err = listen(sockfd, LIST);
    	if(err < 0)
    	{
    		perror("fail to listen");
    		exit(1);
    	}
    	
    	//初始化fd_all数组
    	memset(&fd_all, -1, sizeof(fd_all));
    
    	fd_all[0] = sockfd;   //第一个为监听套接字
    	
    	FD_ZERO(&fd_read);	// 清空
    	FD_SET(sockfd, &fd_read);  //将监听套接字加入fd_read
    
    	int maxfd;
    	maxfd = fd_all[0];  //监听的最大套接字
    	
    	while(1){
    	
    		// 每次都需要重新赋值,fd_select,timeout_select每次都会变
            fd_select = fd_read;
            timeout_select = timeout;
    		
    		// 检测监听套接字是否可读,没有可读,此函数会阻塞
    		// 只要有客户连接,或断开连接,select()都会往下执行
    		err = select(maxfd+1, &fd_select, NULL, NULL, NULL);
    		//err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select);
    		if(err < 0)
    		{
    				perror("fail to select");
    				exit(1);
    		}
    
    		if(err == 0){
                printf("timeout\n");
    		}
    		
    		// 检测监听套接字是否可读
    		if( FD_ISSET(sockfd, &fd_select) ){//可读,证明有新客户端连接服务器
    			
                cli_len = sizeof(cli_addr);
    			
    			// 取出已经完成的连接
    			connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
    			if(connfd < 0)
    			{
    				perror("fail to accept");
    				exit(1);
    			}
    			
    			// 打印客户端的 ip 和端口
    			char cli_ip[INET_ADDRSTRLEN] = {0};
    			inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    			printf("----------------------------------------------\n");
    			printf("client ip=%s,port=%d\n", cli_ip,ntohs(cli_addr.sin_port));
    			
    			// 将新连接套接字加入 fd_all 及 fd_read
    			for(i=0; i < MAX_FD; i++){
    				if(fd_all[i] != -1){
    					continue;
    				}else{
    					fd_all[i] = connfd;
    					printf("client fd_all[%d] join\n", i);
    					break;
    				}
    			}
    			
    			FD_SET(connfd, &fd_read);
    			
    			if(maxfd < connfd)
    			{
    				maxfd = connfd;  //更新maxfd
    			}
    		
    		}
    		
    		//从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd)
    		for(i=1; i < maxfd; i++){
    			if(FD_ISSET(fd_all[i], &fd_select)){
    				printf("fd_all[%d] is ok\n", i);
    				
    				char buf[1024]={0};  //读写缓冲区
    				int num = read(fd_all[i], buf, 1024);
    				if(num > 0){
    
    					//收到 客户端数据并打印
    					printf("receive buf from client fd_all[%d] is: %s\n", i, buf);
    					
    					//回复客户端
    					num = write(fd_all[i], buf, num);
    					if(num < 0){
    						perror("fail to write ");
    						exit(1);
    					}else{
    						//printf("send reply\n");
    					}
    					
    					
    				}else if(0 == num){ // 客户端断开时
    					
    					//客户端退出,关闭套接字,并从监听集合清除
    					printf("client:fd_all[%d] exit\n", i);
    					FD_CLR(fd_all[i], &fd_read);
    					close(fd_all[i]);
    					fd_all[i] = -1;
    					
    					continue;
    				}
    				
    			}else {
                    //printf("no data\n");                  
                }
    		}
    	
    	}
    	
        return 0;
    }
    

    运行结果如下:

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    整理的高性能高并发服务器架构文章,内容预览:  初创网站与开源软件 6  谈谈大型高负载网站服务器的优化心得! 8  Lighttpd+Squid+Apache搭建高效率Web服务器 9  浏览量比较大的网站应该从哪几个方面入手? 17  用负载均衡技术建设高负载站点 20  大型网站的架构设计问题 25  开源平台的高并发集群思考 26  大型、高负载网站架构和应用初探 时间:30-45分钟 27  说说大型高并发高负载网站的系统架构 28  mixi技术架构 51 mixi.jp:使用开源软件搭建的可扩展SNS网站 51 总概关键点: 51 1,Mysql 切分,采用Innodb运行 52 2,动态Cache 服务器 -- 52 美国Facebok.com,中国Yeejee.com,日本mixi.jp均采用开源分布式缓存服务器Memcache 52 3,图片缓存和加 52  memcached+squid+apache deflate解决网站大访问量问题 52  FeedBurner:基于MySQL和JAVA的可扩展Web应用 53  YouTube 的架构扩展 55  了解一下 Technorati 的后台数据库架构 57  Myspace架构历程 58  eBay 的数据量 64  eBay 的应用服务器规模 67  eBay 的数据库分布扩展架构 68  从LiveJournal后台发展看大规模网站性能优化方法 70 一、LiveJournal发展历程 70 二、LiveJournal架构现状概况 70 三、从LiveJournal发展中学习 71 1、一台服务器 71 2、两台服务器 72 3、四台服务器 73 4、五台服务器 73 5、更多服务器 74 6、现在我们在哪里: 75 7、现在我们在哪里 78 8、现在我们在哪里 79 9、缓存 80 10、Web访问负载均衡 80 11、MogileFS 81  Craigslist 的数据库架构 81  Second Life 的数据拾零 82  eBay架构的思想金矿 84  一天十亿次的访问-eBay架构(一) 85  七种缓存使用武器 为网站应用和访问加速发布时间: 92  可缓存的CMS系统设计 93  开发大型高负载类网站应用的几个要点 105  Memcached和Lucene笔记 110  使用开源软件,设计高性能可扩展网站 110  面向高负载的架构Lighttpd+PHP(FastCGI)+Memcached+Squid 113  思考高并发高负载网站的系统架构 113  "我在SOHU这几年做的一些门户级别的程序系统(C/C++开发)" 115  中国顶级门户网站架构分析1 116  中国顶级门户网站架构分析 2 118  服务器的大用户量的承载方案 120  YouTube Scalability Talk 121  High Performance Web Sites by Nate Koechley 123 One dozen rules for faster pages 123 Why talk about performance? 123 Case Studies 124 Conclusion 124  Rules for High Performance Web Sites 124  对于应用高并发,DB千万级数量该如何设计系统哪? 125  高性能服务器设计 130  优势与应用:再谈CDN镜像加速技术 131  除了程序设计优化,zend+ eacc(memcached)外,有什么办法能提高服务器的负载能力呢? 135  如何规划您的大型JAVA多并发服务器程序 139  如何架构一个“Just so so”的网站? 148  最便宜的高负载网站架构 152  负载均衡技术全攻略 154  海量数据处理分析 164  一个很有意义的SQL的优化过程(一个电子化支局中的大数据量的统计SQL) 166  如何优化大数据量模糊查询(架构,数据库设置,SQL..) 168  求助:海量数据处理方法 169 # re: 求助:海量数据处理方法 回复 更多评论 169  海量数据库查询方略 169  SQL Server 2005对海量数据处理 170  分表处理设计思想和实现 174  Linux系统高负载 MySQL数据库彻底优化(1) 179  大型数据库的设计与编程技巧 本人最近开发一个访问统计系统,日志非常的大,都保存在数据库里面。 我现在按照常规的设计方法对表进行设计,已经出现了查询非常缓慢地情形。 大家对于这种情况如何来设计数据库呢?把一个表分成多个表么?那么查询和插入数据库又有什么技巧呢? 谢谢,村里面的兄弟们! 183  方案探讨,关于工程中数据库的问题. 184  web软件设计时考虑你的性能解决方案 190  大型Java Web系统服务器选型问题探讨 193  高并发高流量网站架构 210 1.1 互联网的发展 210 1.2 互联网网站建设的新趋势 210 1.3 新浪播客的简介 211 2.1 镜像网站技术 211 2.2 CDN内容分发网络 213 2.3 应用层分布式设计 214 2.4 网络层架构小结 214 3.1 第四层交换简介 214 3.2 硬件实现 215 3.3 软件实现 215  网站架构的高性能和可扩展性 233  资料收集:高并发 高性能 高扩展性 Web 2.0 站点架构设计及优化策略 243  CommunityServer性能问题浅析 250 鸡肋式的多站点支持 250 内容数据的集中式存储 250 过于依赖缓存 250 CCS的雪上加霜 250 如何解决? 251  Digg PHP's Scalability and Performance 251  YouTube Architecture 253 Information Sources 254 Platform 254 What's Inside? 254 The Stats 254 Recipe for handling rapid growth 255 Web Servers 255 Video Serving 256 Serving Video Key Points 257 Serving Thumbnails 257 Databases 258 Data Center Strategy 259 Lessons Learned 260 1. Jesse • Comments (78) • April 10th 261 Library 266 Friendster Architecture 273 Information Sources 274 Platform 274 What's Inside? 274 Lessons Learned 274  Feedblendr Architecture - Using EC2 to Scale 275 The Platform 276 The Stats 276 The Architecture 276 Lesson Learned 277 Related Articles 278 Comments 279 Re: Feedblendr Architecture - Using EC2 to Scale 279 Re: Feedblendr Architecture - Using EC2 to Scale 279 Re: Feedblendr Architecture - Using EC2 to Scale 280  PlentyOfFish Architecture 281 Information Sources 282 The Platform 282 The Stats 282 What's Inside 283 Lessons Learned 286  Wikimedia architecture 288 Information Sources 288 Platform 288 The Stats 289 The Architecture 289 Lessons Learned 291  Scaling Early Stage Startups 292 Information Sources 293 The Platform 293 The Architecture 293 Lessons Learned 294  Database parallelism choices greatly impact scalability 295  Introduction to Distributed System Design 297 Table of Contents 297 Audience and Pre-Requisites 298 The Basics 298 So How Is It Done? 301 Remote Procedure Calls 305 Some Distributed Design Principles 307 Exercises 308 References 309  Flickr Architecture 309 Information Sources 309 Platform 310 The Stats 310 The Architecture 311 Lessons Learned 316 Comments 318 How to store images? 318 RE: How to store images? 318  Amazon Architecture 319 Information Sources 319 Platform 320 The Stats 320 The Architecture 320 Lessons Learned 324 Comments 329 Jeff.. Bazos? 329 Werner Vogels, the CTO of 329 Re: Amazon Architecture 330 Re: Amazon Architecture 330 Re: Amazon Architecture 330 It's WSDL 330 Re: It's WSDL 331 Re: Amazon Architecture 331  Scaling Twitter: Making Twitter 10000 Percent Faster 331 Information Sources 332 The Platform 332 The Stats 333 The Architecture 333 L
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值