服务端使用多线程处理客户端请求

文章介绍了如何使用C语言和多线程技术,通过设置最大连接数,创建子线程来处理客户端的连接请求。通过accept()函数监听并管理连接,确保资源的有效利用。
摘要由CSDN通过智能技术生成
1.照惯例,直接放上源码:
​
// 目的:使用多线程处理客户端发来的请求, 宏定义#define NUM_CLIENT 3
//      为了方便实验,设置可连接的客户端最多为3台
// 步骤:
// 		1.socket()创建监听文件描述符 lfd
//		2.bind() 将lfd与ip和port绑定
//		3.listen() 设置为被动监听模式
//		4.while(1)
//		{
// 			cfd = accept()
// 			pthread_create()创建子线程
//			设置 子线程为分离属性
//			子线程,循环读写
// 		}
// 	

#include "wrap.h"
#include <pthread.h>
#include <ctype.h>

#define NUM_CLIENT 3


void * mythread(void * arg)
{
	int * p = (int *)arg;
	int cfd = *p;
	// 处理业务逻辑 读写
	char buf[128];
	int n = 0;
	int i = 0;

	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = Read(cfd, buf, sizeof(buf));
		if(n <= 0)
		{
			printf("n == [%d],read error or client closed\n",n);
			if(n == 0)
			{
				printf("---client closed cfd == [%d]\n",cfd);
				*p = -1;    // 关闭连接后,又将 传递的参数相应位arr[i] 置为-1,为连接新的客户端作准备。
			}
			break;
		}
		printf("cfd == [%d] %s\n",cfd, buf);

		for(i = 0; i < n; i++)
		{
			buf[i] = toupper(buf[i]);
		}

		Write(cfd, buf, sizeof(buf));

	}
	// 读错误退出业务逻辑
	close(cfd);
	pthread_exit(NULL);

}

int main()
{
	// --1--生成监听文件描述符 int socket(int domain, int type, int protocol);
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);

	//设置端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

	// --2--绑定端口 int bind(int sockfd, const struct sockaddr *addr,
	// socklen_t addrlen);
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr*)&serv, sizeof(serv));

	// --3-- 设置为监听模式 int listen(int sockfd, int backlog);
	Listen(lfd, 128);

	// --4-- 进入while循环,监听连接请求,建立相应子线程
	pthread_t thread[100];
	int ret = 0;
	int cfd;
	int arr[NUM_CLIENT];
	int i = 0;
	int len;

	for(i = 0; i < NUM_CLIENT; i++)
	{
		arr[i] = -1;
	}

	while(1)
	{
		// --接受连接--int accept(int sockfd, struct sockaddr *addr, 
		// socklen_t *addrlen);
		struct sockaddr_in client;
		len = sizeof(client);
		cfd = Accept(lfd, (struct sockaddr *)&client, &len);

		// 创建子线程
		for(i = 0; i < NUM_CLIENT; i++)
		{
			if(arr[i] == -1)
			{
				arr[i] = cfd;
				ret = pthread_create((thread+i), NULL, mythread, (arr+i));
				if(ret != 0)
				{
					printf("pthread_create error, [%s]\n",strerror(ret));
				}

				// 打印客户端的信息
				char IP[16];
				inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
				printf("client IP = [%s], port = [%d]\n", IP, ntohs(client.sin_port));

				break;
			}
			
		}
		if(i == NUM_CLIENT)
		{
			close(cfd);
		}

		

		// 设置子线程为分离属性
		pthread_detach(thread[i]);

	}

	// 关闭监听文件描述符
	close(lfd);

	return 0;
}

​

2.说明:

        ①:头文件wrap.h下载于网络,可以直接搜索,上一篇博客也有说明。

        ②:设置子线程为分离属性,使得线程的退出将自动释放其资源。

3.一些细节:

关注一下这里的创建子线程的for循环,首先判断了一下 arr[i] 是否为-1,如果是才创建线程,否则跳出 for() 循环,重新阻塞等待accept()连接。

arr[i] 为-1有两种情况:

        ①:客户端连接还没有达到上限,这里设置的上限为3

        ②:客户端连接达到上限后,有客户端退出,让出了位置。 

// --4-- 进入while循环,监听连接请求,建立相应子线程
	pthread_t thread[NUM_CLIENT];
	int ret = 0;
	int cfd;
	int arr[NUM_CLIENT];
	int i = 0;
	int len;

	for(i = 0; i < NUM_CLIENT; i++)
	{
		arr[i] = -1;
	}

	while(1)
	{
		// --接受连接--int accept(int sockfd, struct sockaddr *addr, 
		// socklen_t *addrlen);
		struct sockaddr_in client;
		len = sizeof(client);
		cfd = Accept(lfd, (struct sockaddr *)&client, &len);

		// 创建子线程
		for(i = 0; i < NUM_CLIENT; i++)
		{
			if(arr[i] == -1)
			{
				arr[i] = cfd;
				ret = pthread_create((thread+i), NULL, mythread, (arr+i));
				if(ret != 0)
				{
					printf("pthread_create error, [%s]\n",strerror(ret));
				}

				// 打印客户端的信息
				char IP[16];
				inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
				printf("client IP = [%s], port = [%d]\n", IP, ntohs(client.sin_port));

				// 设置子线程为分离属性
				pthread_detach(thread[i]);

				break;
			}
			
		}
		if(i == NUM_CLIENT)  // 客户端连接达到上限,忽略之后的连接
		{
			close(cfd);
		}

	}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

子线程处理函数中,使用指针 p 接收 arg 参数的目的是获得指向数组 arr 的指针,以便在关闭客户端后重新将相应位置,重新置为 -1.

对第三点总结一下:其实就是为了实现使用一个有限长数组 arr 接受多个客户端的连接啦 ~

4.实验:

想象一下,启动服务端后,客户端去连接它,会有什么结果发生:

  1. 首先  客户端上限为3,第4个客户端连接应该是失败的;
  2. 然后  在3个已经连接的客户端中,关闭1个,再去尝试连接,应该是成功的;
  3. 而且  由于关闭客户端时同时回收了通信文件描述符 cfd ,所以关闭客户端后再连接应该得到以前分配过的通信文件描述符,当然回收由内核选择合适的时机去做,可能存在分配新的文件描述符。

< 1 >.首先3个客户端去连接服务端,都是成功的


再第4个去连


服务端的显示没有变化,说明没有连接成功(我没有对连接不成功的做打印,只对成功的做了打印)


还可以直接看一下相关线程信息如下。主线程4208和它的3个子线程4210、4212、4214,也说明第4个客户端没有连接成功


< 2 和 3>再,关一个再连

我下面关了两个,连了连个,关闭之后连接成功,对应上面我们的设想2

仔细观察一下,新连接的客户端,分配到的 通信文件描述符是 7和5 符合我们的设想3


再观察一下对应的线程信息:

的确是两个新建立的子线程 4295、4297

好了,over~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值