最大连接数与每ip连接数的限制

一、最大连接数的限制

概念

这是指服务器可以同时接收的最大连接数,防止服务器压力过大而在应用层进行的限制。

实现

总体思路:将当前连接数保存于变量num_clients变量中,然后与配置项numble_max_clients进行比较,如果超过了就不让登录,当一个客户登录的时候num_clents加1,当一个客户退出的时候,num_clients减1。

1.一旦服务器端与客户端经过三次握手建立连接之后,服务端就需要创建进程为其服务,此时,将连接数加1。

//来了一个新的客户端,需要创建子进程出来
++s_children;
//当前连接数等于子进程数
sess.num_clients = s_children;

2.然后在负责服务的子进程中进行最大连接数的判断

如果当前连接数超过了最大连接数,那么就要退出当前子进程。

//连接数的判定
void check_limits(session_t *sess)
{
	//最大连接数配置项是否开启并且当前连接数超过了最大连接数
	if (tunable_max_clients > 0 && sess->num_clients > tunable_max_clients) {
		ftp_reply(sess, FTP_TOO_MANY_USERS, 
			"There are too many connected users, please try later.");

		//退出当前子进程
		exit(EXIT_FAILURE);
	}

    //最大连接数没有超过上限的情况下,再检查ip的连接数是否超过上限
	if (tunable_max_per_ip > 0 && sess->num_this_ip > tunable_max_per_ip) {
		ftp_reply(sess, FTP_IP_LIMIT, 
			"There are too many connections from your internet address.");

		exit(EXIT_FAILURE);
	}
}

3.负责服务客户端的子进程退出连接后,父进程进行后续处理。

父进程是专门用于处理客户端连接的,父进程会注册SIGCHLD信号,在信号函数中将连接数进行减一操作。

//子进程退出时候的信号处理函数
signal(SIGCHLD, handle_sigchld);
void handle_sigchld(int sig)
{
	// 当一个客户端退出的时候,那么该客户端对应ip的连接数要减1,
	// 处理过程是这样的,首先是客户端退出的时候,
	// 父进程需要知道这个客户端的ip,这可以通过在s_pid_ip_hash查找得到,
	

	pid_t pid;
	while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
		--s_children;
		//通过pid找到ip
		unsigned int *ip = hash_lookup_entry(s_pid_ip_hash, &pid, sizeof(pid));
		if (ip == NULL) {
			continue;
		}

		drop_ip_count(ip);
		//进程退出,进程和ip的表项就没有意义了
		hash_free_entry(s_pid_ip_hash, &pid, sizeof(pid));
	}

}

二、每IP连接数的限制

概念

是在说某个IP能够连接的最大个数。如果达到了每IP的最大连接数的数量,即使没有达到总的最大连接数的限制,那么也会中断当前连接。

实现

维护两个哈希表

static hash_t *s_ip_count_hash   ip与对应连接数的哈希表

static hash_t *s_pid_ip_hash        进程与ip对应的哈希函数表

将当前ip的连接数保存在变量num_this_ip中,然后与配置项tumable_max_per_ip进行比较,如果超过了就不让登录,当一个客户登录的时候,要在s_ip_count_hash这个哈希表中更新对应的表项,即该ip对应的连接数要加1,如果这个表项还不存在,要在表中添加一条记录,并将ip对应的连接数置1,当一个客户端退出的时候,那么该客户端对应ip的连接数减1,处理过程是这样的,首先是客户端退出的时候,父进程需要知道这个客户的ip,这可以通过在s_pid_ip_hash这个哈希表中查找得到,得到了ip进而我们就可以在s_ip_count_hash表中找到对应的连接数,进而进行减1操作。

因为子进程的退出的时候,父进程要减少该进程对应ip的连接数,但是父进程只能知道是哪个进程退出了,不能知道是哪个ip的连接数需要减一,所以进程与ip对应的哈希函数表是必要的。

1.建立两个哈希表

//创建hash表,256是桶的个数,hash_fun是哈希函数
s_ip_count_hash = hash_alloc(256, hash_func);
s_pid_ip_hash = hash_alloc(256, hash_func);
//哈希函数
unsigned int hash_func(unsigned int buckets, void *key)
{
	unsigned int *number = (unsigned int*)key;//void*转换为unsigned int*

	//返回桶号
	return (*number) % buckets;
}

2.连接建立之后,s_ip_count_hash的更新

当客户端的一个连接过来之后,可以由accept得到对等方的地址,然后对ip和连接数的哈希表进行更新操作。如果之前有这个ip,连接数加1;如果之前没有这个ip,插入一个ip和连接数为1的表项。

//返回当前ip的连接数,进行加1操作
unsigned int handle_ip_count(void *ip)
{
	// 当一个客户登录的时候,要在s_ip_count_hash更新这个表中的对应表项,
	// 即该ip对应的连接数要加1,如果这个表项还不存在,要在表中添加一条记录,
	// 并且将ip对应的连接数置1。

	unsigned int count;
	unsigned int *p_count = (unsigned int *)hash_lookup_entry(s_ip_count_hash,
		ip, sizeof(unsigned int));
	//表明该ip是第一次连接
	if (p_count == NULL) {
		count = 1;
		//插入一个表项
		hash_add_entry(s_ip_count_hash, ip, sizeof(unsigned int),
			&count, sizeof(unsigned int));
	}
	//表项已经存在,更新对应的连接数
	else {
		count = *p_count;
		++count;
		*p_count = count;
	}

	return count;
}

3.连接建立之后,s_pid_ip_hash的更新

插入进程和ip的表项,这是在父进程中进行的

//添加进程和ip的对应关系,这里的进程是子进程
hash_add_entry(s_pid_ip_hash, &pid, sizeof(pid),
		&ip, sizeof(unsigned int));

4.在子进程中进行连接数的判断

先判断总的最大连接数,如果超过最大连接数就要退出当前子进程;再判断是否超过每ip的最大连接数,如果超过最大连接数就要退出当前子进程。

//连接数的判定
void check_limits(session_t *sess)
{
	//最大连接数配置项是否开启并且当前连接数超过了最大连接数
	if (tunable_max_clients > 0 && sess->num_clients > tunable_max_clients) {
		ftp_reply(sess, FTP_TOO_MANY_USERS, 
			"There are too many connected users, please try later.");

		//退出当前子进程
		exit(EXIT_FAILURE);
	}

    //最大连接数没有超过上限的情况下,再检查ip的连接数是否超过上限
	if (tunable_max_per_ip > 0 && sess->num_this_ip > tunable_max_per_ip) {
		ftp_reply(sess, FTP_IP_LIMIT, 
			"There are too many connections from your internet address.");

		exit(EXIT_FAILURE);
	}
}

5.子进程退出之后,父进程进入sigchld信号处理函数

首先在哈希表s_pid_ip_hash中根据进程号获得ip

void handle_sigchld(int sig)
{
	// 当一个客户端退出的时候,那么该客户端对应ip的连接数要减1,
	// 处理过程是这样的,首先是客户端退出的时候,
	// 父进程需要知道这个客户端的ip,这可以通过在s_pid_ip_hash查找得到,
	

	pid_t pid;
	while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
		--s_children;
		//通过pid找到ip
		unsigned int *ip = hash_lookup_entry(s_pid_ip_hash, &pid, sizeof(pid));
		if (ip == NULL) {
			continue;
		}

		drop_ip_count(ip);
		//进程退出,进程和ip的表项就没有意义了
		hash_free_entry(s_pid_ip_hash, &pid, sizeof(pid));
	}

}

然后将该ip的连接数减1,如果ip数减为0,那么将该ip和连接数的对应表项删除。

//减1操作
void drop_ip_count(void *ip)
{
	// 得到了ip进而我们就可以在s_ip_count_hash表中找到对应的连接数,进而进行减1操作。

	unsigned int count;
	unsigned int *p_count = (unsigned int *)hash_lookup_entry(s_ip_count_hash,
		ip, sizeof(unsigned int));
	if (p_count == NULL) {
		return;
	}

	count = *p_count;
	if (count <= 0) {
		return;
	}
	--count;
	*p_count = count;

	if (count == 0) {
		//可以删除表项了
		hash_free_entry(s_ip_count_hash, ip, sizeof(unsigned int));
	}
}

进程退出之后,进程和ip的表项也就没有意义了,删除。

 

你可能感兴趣的:(FTP)