高性能服务器编程----多进程&多线程

        TCP编程虽然可以和多个客户交互,但是实现的是串行交互模式,就是一个客户端连接完,一个客户端再连接。但是这种模式并不能满足中国现如今的人口对于服务器的访问,即使服务器速度快,但是这样的效率还是不高的。大致如下:

        那如何实现同一时刻能与多个客户端同时交互呢?使服务器和客户端交互以并发处理呢?这就是今天我们要解决的问题。也就是要使上图中红色框内的与客户端交互的流程实现并发交互。


一、多进程,多线程概念     

       每个进程都是一个执行流,当我们要执行多个请求时,我们就可以引入多进程,多线程了。

       多进程:  启动多个进程,每个进程执行和一个客户端交互的程序;

                       父进程完成与客户端的连接服务,完成后,创建子进程,子进程与客户端具体交互

       多线程:  启动多个线程,每个线程执行和一个客户端交互的程序;

       其实概念并不难理解,我们直接看看代码如何实现吧~

二、多进程的代码实现

 2.1客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in ser,cli;//服务器的IP地址 端口号
	memset(&ser,0,sizeof(ser));

	ser.sin_family = AF_INET;
	ser.sin_port = htons(6666);//服务器上对应服务进程的端口号
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

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

	while(1)
	{
		printf("please input:");
		char buff[128] = {0};
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) == 0)
		{
			close(sockfd);
			break;
		}
		send(sockfd,buff,strlen(buff)-1,0);

		char recvbuff[128] = {0};
		int n = recv(sockfd,recvbuff,127,0);
		if(n <= 0)
		{
			close(sockfd);
			break;
		}
		printf("client recv data: %s\n",recvbuff);
	}
}

 2.2服务器代码:     

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

void CommClient(int c)
{
	while(1)
	{
		char buff[128] = {0};
		int n = recv(c,buff,127,0);
		if(n <= 0)
		{
			close(c);
			printf("%d over\n",c);
			break;
		}
		printf("%d: %s \n ",c,buff);
	}
}

void Zombie(int sign) //僵死进程
{
	wait(NULL);
}

int main()
{
	signal(SIGCHLD,Zombie);
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in cli,ser;
    memset(&ser,0,sizeof(ser));

	ser.sin_family = AF_INET;
	ser.sin_port = htons(6666);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

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

	listen(sockfd,5);

	while(1)
	{
		int cli_len = sizeof(cli);
		int c = accept(sockfd,(struct sockaddr*)&cli,&cli_len);
		if(c < 0)
		{
			printf("link is error!\n");
			continue;
		}


		pid_t n = fork();//创建子进程 c 作为文件描述符传递给子进程
		assert(n != -1);
		if(n == 0)
		{
			CommClient(c);
			exit(0);  //结束子进程的进程实体,PCB依旧保存
		}
		else //父进程
		{
			close(c); //仅仅是父进程关闭文件描述符,并未断开链接
		}
	}

    close(sockfd);
}

        在编写代码过程中,我们得注意:

        我们创建父子进程时,如果只关闭了子进程的文件描述符,断开了子进程自己的连接,不关闭父进程的文件描述符的话会导致c描述符的位置一直被占用,c描述符是一个指针,也就会变成一个野指针因此我们也得关闭父进程的文件描述符。并且在以后处理服务器问题时,一定要注意资源的释放的问题,不用的资源一定要及时关闭,否则会造成资源的的大量虚空浪费。

          当在父进程中未关闭c文件描述符前                                             当在父进程中关闭c文件描述符后。

            

count保存了有几个进程的c指向此结构体,关系着是否为0时释放结构体资源。

接着我们来看一下结果:

          

      也许大家会对为什么文件描述符都是产生疑问4??

      那是因为系统一启动就会默认打开的0,1,2文件描述符,上图有说明,分别代表的含义。而3接着被我们的socket给占领了。所以c就会占据4号位置。而连接第二个时,也是一个全新的开始,还是会从0开始检索,到最后也是4号被占据。因此两个文件符所占的位置都是4。但是他们的端口号是唯一的,我们来看看。

      linux下使用命令:ps -ef | grep cour

     

    关闭两个客户端链接后:

    

    可以看出两个子进程的端口号是不同的,只是拥有一个共同的父进程,这就实现了我们简单的并发操作。

三、多线程代码实现

    编写代码之前我们需要明白:

    1)主线程负责接收客户连接,函数线程负责处理客户连接

    2)   函数线程如何拿到与客户连接的文件描述符  创建线程时,以值传递的形式传递给函数线程 (不能使用地址传递,不然有可能误改地址里的数据)

    3)   主线程需不需要关闭文件描述符  不需要 用一个进程中所有线程共享进程资源,仅有独立的栈区不共享

    4)    僵死进程需不需要处理??     不需要  本身不会有子进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

void* CommClient(void* data)
{
	int c = (int)data; 
	while(1)
	{
		char buff[128] = {0};
		int n = recv(c,buff,127,0);
		if(n <= 0)
		{
			close(c);
			printf("%d over\n",c);
			break;
		}
		printf("%d: %s \n ",c,buff);
		send(c,"ok",2,0);
	}
}


int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);

	struct sockaddr_in cli,ser;
     memset(&ser,0,sizeof(ser));

	ser.sin_family = AF_INET;
	ser.sin_port = htons(6666);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

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

	listen(sockfd,5);

	while(1)
	{
		int cli_len = sizeof(cli);
		int c = accept(sockfd,(struct sockaddr*)&cli,&cli_len);
		printf("%x\n",&c);
		if(c < 0)
		{
	    	printf("link is error!\n");
			continue;
		}

		pthread_t id;
		int res = pthread_create(&id,NULL,CommClient,(void*)c); //传值就行
		assert(res == 0);
		//close(c);  不能关闭 因为在共享
	}
     close(sockfd);
}

我们来验证下结果:

建立连接后                                                                        关闭连接后

我们可以使用命令(netstat -natp)来看一下:

 上图可看出客户端和服务器的情况两两对应,都是已建立连接的情况。

 我们接着使用命令看一下进程和线程:

 查看进程命令:ps -ef | grep thread

 查看线程命令:ps -efL | grep thread   L是显示线程的

       可以看出就是只有一个进程


       可以看出有三个线程,他们的进程号都是6407,父进程都是3385,只有各自的线程号是不同的。主线程负责接收,函数线程负责和客户端通讯。

四、多线程和多进程的对比

      根据不同的业务需求,场景进行选择

      两者的比较: 

  1. 从编程角度:  多线程代码实现简单一些,控制简单一些
  2. 从占据资源:  多线程比多进程小
  3. 从切换角度:  线程切换比进程快
  4. 从资源共享:  线程比进程间共享资源多比如全局,堆,文件,但同时要注意线程安全性问题。
  5. 从创建的数量:多进程比多线程要多很多。

       

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值