【Linux学习笔记54】多进程处理TCP服务器IO模型之并发阻塞

引言

这一篇笔记主要记录在TCP服务器中,如果连接端点都是阻塞的,我们应该如何处理。
TCP是面向连接的,在服务端中,需要先进行连接,再通信。因此就存在一个监听套接字,专门监听对端的连接请求。那么,当存在多个对端的连接请求的时候,当达到服务器的连接上限后,如果服务器要读其中一个端的数据的时候,就不能做其他事情,只有当一件事完成后才能做下一件事,也就是存在阻塞。为了解决这个问题,我们可以使用多进程来解决监听,读数据等各种操作。

实现思路

在这里插入图片描述

  • 服务器首先会创建一个监听套接字(监听端点)
  • 客户端1会对服务器发起连接,并且产生连接套接字fd
  • 服务器为了能够实现并发阻塞,会产生一个与其一模一样的子进程,并且屏蔽与客户端1的连接套接字fd
  • 子进程1只需要负责与客户端1进行通信,所以可以屏蔽监听端点。
  • 子进程1负责与客户端1进行通信
  • 当有客户端2进行链接时,如此类推。
  • 需要注意的是,当客户端1退出连接后,子进程1也应该退出,但是要防止编程僵尸进程,必须要父进程释放资源,但是父进程一直在监听连接而不会终止,这就很容易导致僵尸进程的出现。此时的处理方法可以参考前面的笔记【Linux学习笔记33】进程的状态与创建退出
  • 另外一种情况,当出现很多子进程的时候,如果同时有进行一起退出,同时对父进程发出死亡信号,父进程分辨不出是一个进程的死亡还是多个进程的死亡,随着时间推移,僵尸进程也越来越多。

代码实现

【Linux学习笔记51】多线程TCP服务端之简易QQ代码为模板修改:
server.c:

#include "head4sock.h"

void cleanup(int sig)
{
	/*因为父进程不能辨别有多少个子进程同时退出,-1 会遍历所有子进程
	waitpid > 0代表回收了一个子进程资源
	然后再进行循环检查是否有子进程退出*/
	while (waitpid(-1,NULL,WNOHANG)>0 );
}
int main(int argc, char const *argv[])
{
	if(argc != 2)
	{
		printf("Usage: %s <PORT>\n", argv[0]);
		exit(0);
	}

	// 创建一个TCP套接字
	int fd = Socket(AF_INET, SOCK_STREAM, 0); //设为IPv4的套接字

	// 绑定地址(IP:PORT)
	struct sockaddr_in srvaddr, cliaddr;
	socklen_t len = sizeof(srvaddr);
	bzero(&srvaddr, len);

	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = htons(atoi(argv[1]));
	// inet_pton(AF_INET, "192.168.1.166", &srvaddr.sin_addr);
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(fd, (struct sockaddr *)&srvaddr, len);

	// 设置监听套接字
	Listen(fd, 3); //如果同时有人发出请求,能够同时处理若干个,在Linux中至少同时允许4+3个连接请求

	signal(SIGCHLD,cleanup); //捕获孩子进程死亡发送的信号
	int connfd;
	while (1)
	{
		 connfd = Accept(fd, (struct sockaddr *)&cliaddr, &len);

		char peeraddr[50];
		bzero(peeraddr, 50);
		printf("new connection: %s:%hu\n",  //打印端口号
					inet_ntop(AF_INET, &cliaddr.sin_addr, peeraddr, 50),
					ntohs(cliaddr.sin_port));

		pid_t a =fork();
		if (a >0)  //parent
		{
			close(connfd); //关闭连接套接字
			continue;
		}
		else if (a == 0) // child
		{
			close(fd);//关闭监听套接字
			break;
		}
	}
	//child
	char buf[SIZE];
	while (1)
	{
		bzero(buf,SIZE);
		if(Read(connfd,buf,SIZE)== 0)
			break;

		printf("%s",buf );
		write(connfd,buf,strlen(buf));
	}

	return 0;
}

client.c:

#include "head4sock.h"

void *routine(void *arg)
{
	int fd = (int) arg;
	char buf[SIZE];
	while (1)
		{
			bzero(buf,SIZE);
			if(Read(fd,buf,SIZE)==0 )
			{
				break;
			}
			printf("form server: %s ",buf);
		}
}

int main(int argc, char const *argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s <IP> <PORT>\n", argv[0]);
		exit(0);
	}

	// 创建一个TCP套接字
	int fd = Socket(AF_INET, SOCK_STREAM, 0);

	// 准备好对端的地址信息
	struct sockaddr_in srvaddr;
	socklen_t len = sizeof(srvaddr);
	bzero(&srvaddr, len);

	srvaddr.sin_family = AF_INET;
	inet_pton(AF_INET, argv[1], &srvaddr.sin_addr);
	srvaddr.sin_port = htons(atoi(argv[2]));

	// 连接服务端
	Connect(fd, (struct sockaddr *)&srvaddr, len);

	pthread_t tid;
	pthread_create(&tid,NULL,routine,(void *)fd);

	char buf[SIZE];
	while(1)
	{
		bzero(buf, SIZE);
		if(fgets(buf, SIZE, stdin) == NULL||
			!strcmp(buf, "quit\n"))
			break;

		write(fd, buf, strlen(buf));
	}

	close(fd);
	return 0;
}

代码运行结果

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值