多线程多进程处理服务器并发(多进程处理如何解决僵死进程)

目录

1.可循环发送数据的代码

2.改成循环之后每次发现只能处理一个客户端

3.服务器端处理并发问题

[3.1 思路](#3.1 思路)

[3.2 利用多线程实现并发](#3.2 利用多线程实现并发)

​编辑

[3.3 利用多进程实现并发](#3.3 利用多进程实现并发)

[3.3.1 多进程并发产生的僵死进程问题](#3.3.1 多进程并发产生的僵死进程问题)

[​3.3.2 解决僵死进程问题](#3.3.2 解决僵死进程问题)


1.可循环发送数据的代码

服务器代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字
	assert(sockfd!=-1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);//主机,网络大小端转换
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//IP地址转换
	
	int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	res=listen(sockfd,5);
	assert(res!=-1);
	
	while(1)
	{
		int len=sizeof(saddr);
		printf("accept wait...\n");
		int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//链接套接字
		if(c<0)
		{
			continue;
		}
		printf("accept c=%d\n",c);
	    printf("accept client ip:%s ,port=%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
		while(1)
		{	
			char buff[128]={0};
			int n=recv(c,buff,127,0);//返回值为0说明断开连接
			if(n<=0)
			{
				break;
			}
			printf("buff=%s\n",buff);

			send(c,"ok",2,0);
		}
		close(c);
	}
	close(sockfd);
	exit(0);
}

客户端代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字
	assert(sockfd!=-1);

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

	int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);
	
	while(1)
	{
		printf("input:\n");
		char buff[128]={0};
		fgets(buff,127,stdin);

		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
		send(sockfd,buff,strlen(buff),0);
		memset(buff,0,128);
		recv(sockfd,buff,127,0);
		printf("read:%s\n",buff);
	}
	close(sockfd);
	exit(0);
}

运行结果:

2.改成循环之后每次发现只能处理一个客户端

将代码从单词发送数据改为while(1)循环发送数据后,我们发现每次只能处理一个客户端,其它客户端消息无法发送给服务器。

原因:

3.服务器端处理并发问题

3.1 思路

这个问题可以通过引入多线程和多进程来解决。

服务端接收一个客户端的连接后(accept之后),创建一个线程或者进程,然后在新创建的线程或进程中循环处理数据。

**主线程(父进程)只负责监听客户端的连接,并使用 accept()接受连接,不进行数据的处理。**如下图所示:

3.2 利用多线程实现并发

客户端代码不变,服务器端代码做如下更改:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
void* work_pthread(void*arg)
{	
	int c=*(int*)arg;
	while(1)
	{
		char buff[128]={0};
		int n=recv(c,buff,127,0);//返回值为0说明断开连接
		if(n<=0)
		{
			break;
		}
		printf("recv(%d)=%s\n",c,buff);

		send(c,"ok",2,0);
	}
	printf("one clinet over!\n");
	close(c);
}

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字
	assert(sockfd!=-1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);//主机,网络大小端转换
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//IP地址转换
	
	int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	res=listen(sockfd,5);
	assert(res!=-1);
    
	while(1)
	{
		int len=sizeof(saddr);
		printf("accept wait...\n");
		int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//链接套接字
		if(c<0)
		{
			continue;
		}
		printf("accept c=%d\n",c);
	    printf("accept client ip:%s ,port=%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
		
		pthread_t id;
		pthread_create(&id,NULL,work_pthread,(void*)&c);

	}
	close(sockfd);
	exit(0);
}

netstat -natp连接成功之后发现有两个./ser

3.3 利用多进程实现并发

客户端代码不变,服务器代码如下:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void DealClientLink(int c,struct sockaddr_in caddr)
{
	while(1)
	{
		char buff[128]={0};
		int n=recv(c,buff,127,0);//返回值为0说明断开连接
		if(n<=0)
		{
			break;
		}
		printf("%s:%d:buff=%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buff);

		send(c,"ok",2,0);
	}
	printf("one clinet unlike!\n");
	close(c);
}

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字
	assert(sockfd!=-1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);//主机,网络大小端转换
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//IP地址转换

	int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	res=listen(sockfd,5);
	assert(res!=-1);

	printf("%s:%d link success!\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
	while(1)
	{
		int len=sizeof(saddr);
		int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//链接套接字
		if(c<0)
		{
			continue;
		}
		pid_t pid=fork();
		assert(pid!=-1);

		if(pid==0)
		{
			DealClientLink(c,caddr);
			exit(0);
		}
	}
	close(sockfd);
	exit(0);
}

运行结果:

3.3.1 多进程并发产生的僵死进程问题

子进程为客户端,父进程为服务器端,子进程先于父进程结束,父进程没有获取到子进程的退出码,子进程就会变成僵死进程,占用内存,影响执行速度。

客户端代码运行前:

关闭客户端(子进程结束)后:

如下图,产生了两个僵死进程。

3.3.2 解决僵死进程问题

修改一下代码,让父进程调用wait()方法获取子进程的退出码,并结合信号使用,让它不再阻塞。

cpp 复制代码
#include <wait.h>

void fun(int sig)
{
    wait(&sig);
}

signal(SIGCHLD,fun);//在主进程中添加

完整代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <wait.h>

void DealClientLink(int c,struct sockaddr_in caddr)
{
	while(1)
	{
		char buff[128]={0};
		int n=recv(c,buff,127,0);//返回值为0说明断开连接
		if(n<=0)
		{
			break;
		}
		printf("%s:%d:buff=%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buff);

		send(c,"ok",2,0);
	}
	printf("one clinet unlike!\n");
	close(c);
}

void fun(int sign)
{
	wait(&sign);
}

int main()
{
	signal(SIGCHLD,fun);
	int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字
	assert(sockfd!=-1);

	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);//主机,网络大小端转换
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//IP地址转换

	int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res!=-1);

	res=listen(sockfd,5);
	assert(res!=-1);

	printf("%s:%d link success!\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
	while(1)
	{
		int len=sizeof(saddr);
		int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//链接套接字
		if(c<0)
		{
			continue;
		}
		pid_t pid=fork();
		assert(pid!=-1);

		if(pid==0)
		{
			DealClientLink(c,caddr);
			exit(0);
		}
	}
	close(sockfd);
	exit(0);
}

使用ps -f命令查看进程信息,可以看到子进程退出后,没有僵死进程。

相关推荐
三思而后行,慎承诺17 分钟前
tcp 和http 网络知识
网络·tcp/ip·http
JavaEdge.19 分钟前
LangChain4j HTTP 客户端定制:解锁 LLM API 交互的更多可能性
网络·网络协议·http
Hy行者勇哥24 分钟前
形象解释 HTTP 的四种常见请求方式及其中的区别联系
网络·网络协议·http
꧁坚持很酷꧂25 分钟前
Linux Ubuntu18.04下安装Qt Craeator 5.12.9(图文详解)
linux·运维·qt
凉、介42 分钟前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci
Cuit小唐43 分钟前
TCP 协议:原理、机制与应用
网络·网络协议·tcp/ip
电鱼智能的电小鱼1 小时前
EFISH-SBC-RK3588无人机地面基准站项目
linux·网络·嵌入式硬件·机器人·无人机·边缘计算
电鱼智能的电小鱼1 小时前
基于 EFISH-SBC-RK3588 的无人机环境感知与数据采集方案
linux·网络·嵌入式硬件·数码相机·无人机·边缘计算
小诸葛的博客2 小时前
详解Linux中的定时任务管理工具crond
linux·运维·chrome
一默19912 小时前
CentOS 7.9升级OpenSSH到9.9p2
linux·运维·centos