多进程并发服务器实现--process

目录

1. 多进程并发服务器思路分析

2. 第一个版本的代码如下:

3. 完成代码:


1. 多进程并发服务器思路分析

使用多进程并发服务器时要考虑以下几点:

  1. 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
  2. 系统内创建进程个数(与内存大小相关)
  3. 进程创建过多是否降低整体服务性能(进程调度)

0.注册信号捕捉函数: SIGCHLD   

1. Socket(); 创建 监听套接字 lfd

2. Bind() 绑定地址结构 Strcut scokaddr_in addr;

3. Listen();

4. while (1) {

                cfd = Accpet(); 接收客户端连接请求。

                pid = fork();

                if (pid == 0){  子进程 

                close(lfd)  关闭用于建立连接的套接字sfd

                break;

                } else if (pid > 0) { 父进程:( 子进程用信号来回收)                

                close(cfd);  关闭用于与客户端通信的套接字 cfd

                contiue;

                }

          }

5. 子进程

read()

执行需求

write()

6.在回调函数中, 完成子进程回收  

while (waitpid());

2. 第一个版本的代码如下:

myServer.c

#include "wrap.h"

#define  SER_PORT  8888			//本地端口
#define  SER_ADDR_IP  "127.0.0.1"	//本地IP

int main()
{
	int s_fd = 0 , c_fd = 0 , ret = 0;		   		//文件描述符
	struct sockaddr_in ser_sockaddr = {0};     		//服务器 sockaddr数据结构
	struct sockaddr_in cli_sockaddr = {0};     		//客户端 sockaddr数据结构
	socklen_t cli_addr_len = sizeof(cli_sockaddr);	//客户端地址结构大小
	char cli_addr_ip[1024] = {0};			   		//客户端IP地址
	char buf[1024] = {0};	
	pid_t pid;
	
	//第1步: 创建socket , 得到监听fd
	s_fd = Socket(AF_INET , SOCK_STREAM , 0);
	
	//第2步: bind绑定s_fd和当前电脑的ip地址和端口号
	ser_sockaddr.sin_family = AF_INET;					// 设置地址族为IPv4
	ser_sockaddr.sin_port = htons(SER_PORT);			// 设置地址的端口号信息:本地--》网络 (IP)
	ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP);	// 设置IP地址
	ret = Bind(s_fd , (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
	
	//第三步:listen监听端口
	ret = Listen(s_fd , 100);
	
	while(1)
	{
		// 第四步:accept阻塞等待客户端接入
		c_fd = accept(s_fd , (struct sockaddr *)&cli_sockaddr , &cli_addr_len);
		
		//网络字节序 ---> 本地字节序(string IP)
		printf("client ip:%s port:%d\n", 
				inet_ntop(AF_INET, &cli_sockaddr.sin_addr.s_addr, cli_addr_ip, sizeof(cli_addr_ip)), 
				ntohs(cli_sockaddr.sin_port));			// 根据accept传出参数,获取客户端 ip 和 port
		
		//创建子进程
		pid = fork();
		if(pid < 0)
		{
			exit(1);  
		}
		else if(pid == 0)	//子进程
		{
			close(s_fd);
			break;
		}
		else if(pid > 0) 	//父进程
		{
			close(c_fd);
			continue;
		}
	}
	
	//子进程运行代码
	if(pid == 0)
	{
		//小写 --> 大写 or 大写 --> 小写
		while(1)
		{
			memset(buf , 0 , sizeof(buf));		
			ret = read(c_fd , buf , sizeof(buf));  //阻塞读取指定字节数
			if(ret == 0) 
			{
				Close(c_fd);
				exit(1);  				   //客户端断开连接
			}

			write(STDOUT_FILENO , buf , ret);
			for(int i = 0 ; i < ret ; i++)
			{
				
				if(buf[i] >= 'a' && buf[i] <= 'z')
				{
					buf[i] -= 32 ;
				}
				else if(buf[i] >= 'A' && buf[i] <= 'Z')
				{
					buf[i] += 32 ;
				}
			}
			printf("\n");
			write(c_fd , buf , ret);
		}
	}
	
	return 0;
}

编译运行,结果如下:

这个代码,有问题。我们Ctrl+C终止一个连接进程,会发现,有僵尸进程。

如上图所示,有个僵尸进程。这是因为父进程在阻塞等待,没来得及去回收这个子进程。

所以需要修改代码,增加子进程回收,用信号捕捉来实现。

3. 完成代码:

#include "wrap.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

#define  SER_PORT  8888			//本地端口
#define  SER_ADDR_IP  "127.0.0.1"	//本地IP

void catch_child(int signum)
{
	while ((waitpid(0, NULL, WNOHANG)) > 0);   	//就要这样写
}

int main()
{
	int s_fd = 0 , c_fd = 0 , ret = 0;		   		//文件描述符
	struct sockaddr_in ser_sockaddr = {0};     		//服务器 sockaddr数据结构
	struct sockaddr_in cli_sockaddr = {0};     		//客户端 sockaddr数据结构
	socklen_t cli_addr_len = sizeof(cli_sockaddr);	//客户端地址结构大小
	char cli_addr_ip[1024] = {0};			   		//客户端IP地址
	char buf[1024] = {0};	
	pid_t pid;
	
	//创建信号捕抓函数回收子进程
	struct sigaction act;
	act.sa_handler = catch_child;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	ret = sigaction(SIGCHLD , &act , NULL);
	if(ret != 0)
	{
		perror("sigaction error");
	}
	
	
	//第1步: 创建socket , 得到监听fd
	s_fd = Socket(AF_INET , SOCK_STREAM , 0);
	
	//第2步: bind绑定s_fd和当前电脑的ip地址和端口号
	ser_sockaddr.sin_family = AF_INET;					// 设置地址族为IPv4
	ser_sockaddr.sin_port = htons(SER_PORT);			// 设置地址的端口号信息:本地--》网络 (IP)
	ser_sockaddr.sin_addr.s_addr = inet_addr(SER_ADDR_IP);	// 设置IP地址
	ret = Bind(s_fd , (const struct sockaddr *)&ser_sockaddr, sizeof(ser_sockaddr));
	
	//第三步:listen监听端口
	ret = Listen(s_fd , 100);
	
	while(1)
	{
		// 第四步:accept阻塞等待客户端接入
		c_fd = Accept(s_fd , (struct sockaddr *)&cli_sockaddr , &cli_addr_len);
		
		//网络字节序 ---> 本地字节序(string IP)
		printf("client ip:%s port:%d\n", 
				inet_ntop(AF_INET, &cli_sockaddr.sin_addr.s_addr, cli_addr_ip, sizeof(cli_addr_ip)), 
				ntohs(cli_sockaddr.sin_port));			// 根据accept传出参数,获取客户端 ip 和 port
		
		//创建子进程
		pid = fork();
		if(pid < 0)
		{
			perror("fork error");
			exit(1);  
		}
		else if(pid == 0)	//子进程
		{
			close(s_fd);
			break;
		}
		else if(pid > 0) 	//父进程
		{
			close(c_fd);
			continue;
		}
	}
	
	//子进程运行代码
	if(pid == 0)
	{
		//小写 --> 大写 or 大写 --> 小写
		while(1)
		{
			memset(buf , 0 , sizeof(buf));		
			ret = read(c_fd , buf , sizeof(buf));  //阻塞读取指定字节数
			if(ret == 0) 						   //客户端断开连接
			{
				Close(c_fd);
				exit(1);  				  
			}

			write(STDOUT_FILENO , buf , ret);
			for(int i = 0 ; i < ret ; i++)
			{
				
				if(buf[i] >= 'a' && buf[i] <= 'z')
				{
					buf[i] -= 32 ;
				}
				else if(buf[i] >= 'A' && buf[i] <= 'Z')
				{
					buf[i] += 32 ;
				}
			}
			printf("\n");
			write(c_fd , buf , ret);
		}
	}
	
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值