select服务器

多进程与多线程服务器局限性

                   多进程和多线程服务器有什么缺点呢?

多进程服务器缺点:

    1.内存消耗比较大,每个进程都独立加载完整的应用环境

      2.cpu消耗偏高,高并发下,进程之间频繁进行上下文切换,需要大量的内存换页操作

      3.很低的io并发处理能力,只适合处理短请求,不适合处理长请求

多线程服务器缺点:

    1.不方便操作系统的管理

    2.VM对内存的管理要求非常高,GC的策略会影响多线程并发能力和系统吞吐量

    3.由于存在对共享资源操作,一旦出现线程"死锁"和线程阻塞,很容易使整个应用失去可用性


用他们编写的服务器,当用户增多时,服务器性能也会下降


为了解决这个问题,select就出现了。

select服务器的优点:

1.高性能(与多进程和多线程比较)
(1)select一次等待多个文件描述符;
(2)select的cpu压力低;
(3)等待时间变短,提升了性能;

  select服务器的缺点:

(1)每次调⽤用select,都需要把fd集合从⽤用户态拷贝到内核态,这个开销在fd很多时会很⼤大
(2)同时每次调⽤用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很⼤大
(3)select⽀支持的⽂文件描述符数量太⼩小了,默认是1024



select服务器代码如下

#include 
#include 
#include 
#include 
#include 
#include 
#include 



int array_fds[1024];

int startup(char * _ip, int _port)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("create socket is fail");
		return 2;
	}

	struct sockaddr_in peer;
	peer.sin_family = AF_INET;
	peer.sin_port = htons(_port);
	peer.sin_addr.s_addr = inet_addr(_ip);

	int opt = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	
	if(bind(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
	{
		perror("bind");
		return 3;
	}

	if(listen(sock, 10) < 0)
	{
		perror("listen");
		return 4;
	}

	return sock;
}

static void Usage(const char* proc)
{
	printf("%s[ip][port]\n", proc);
}

int main(int argc, char *argv[])
{
	if(argc != 3)
	{
		Usage(argv[0]);
		return 1;
	}

	int listensock = startup(argv[1], atoi(argv[2]));

	int maxfd = 0;
	int array_size = sizeof(array_fds)/sizeof(array_fds[0]);
	fd_set rfds;
	array_fds[0] = listensock;

	int i = 1;
	for(; i < array_size; i++)
	{
		array_fds[i] = -1;
	}
	while(1)
	{
		struct timeval _timeout = {5, 0};
		FD_ZERO(&rfds);
		maxfd = -1;
		for(i = 0; i < array_size; i++)
		{
			if(array_fds[i] > 0)
			{
				FD_SET(array_fds[i], &rfds);
				if(array_fds[i] > maxfd)
				{
					maxfd = array_fds[i];
				}
			}
		}

		switch(select(maxfd + 1, &rfds, NULL, NULL, &_timeout))
		{
			case 0:
				printf("timeout....");
				break;
			case -1:
				perror("select");
				break;
			default:
				{
					int j = 0;
					for(; j < array_size; j++)
					{
						if(array_fds[j] < 0)
							continue;

					    if(j == 0 && FD_ISSET(array_fds[j], &rfds))
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_fd = accept(array_fds[j], (struct sockaddr*)&client, &len);
							if(new_fd < 0)
							{
								perror("new_fd:accept");
								continue;
							}else
							{
								//printf("get a new client:(%s:%d)");
								int k = 1;
								for(; k < array_size; k++)
								{
									if(array_fds[k] < 0)
									{
										array_fds[k] = new_fd;
										break;
									}
								}

								if(k == array_size)
								{
									close(new_fd);
								}
							}
						}
						else if(j != 0 && FD_ISSET(array_fds[j], &rfds))
						{
							char buf[1024];
							ssize_t s = read(array_fds[j], buf, sizeof(buf)-1);
							if(s > 0)
							{
								buf[s] = 0;
								printf("client say# %s\n", buf);

							}else if(s == 0)
							{
								printf("client is quit!");
								close(array_fds[j]);
								array_fds[j] = -1;
							}else
							{
								perror("read");
								close(array_fds);
								array_fds[j] = -1;
							}
						}
					}
				}
				break;
		}
	}

	return 0;
}

<think>嗯,用户让我介绍一下TCP中的select服务器。首先,我需要确认自己对select模型的理解是否正确。select是I/O多路复用的一种机制,用于处理多个套接字的异步事件。在TCP服务器中,使用select可以同时监控多个连接,提高效率。 用户可能已经了解基本的TCP服务器模型,比如单线程阻塞式的,但遇到并发连接数多时性能问题。这时候需要多路复用技术,而select是其中一种经典方法。我需要解释清楚select的工作原理,以及它在TCP服务器中的应用步骤。 接下来,我应该先介绍select的基本概念,包括它如何同时监视多个文件描述符,比如读、写、异常集合。然后说明select的工作流程,包括设置fd_set、调用select函数、检查就绪的描述符等步骤。需要注意select的优缺点,比如有最大文件描述符数量限制,以及效率问题。 可能用户需要知道如何实际编写一个select服务器,所以需要给出一个简单的代码示例框架,用中文注释说明每个部分的作用。同时,要指出select的局限性,比如FD_SETSIZE的限制,以及在高并发场景下可能不如epoll或kqueue高效。 还需要比较select和其他多路复用机制的区别,这样用户能明白在什么情况下选择select是合适的。例如,在连接数不高且跨平台需求强的情况下,select可能更合适;而在高并发Linux服务器中,epoll会更优。 最后,总结一下select的适用场景和注意事项,确保用户理解其实际应用中的权衡。检查是否有遗漏的关键点,比如超时机制、如何正确处理多个就绪的fd,以及错误处理等。</think>TCP中select服务器的实现原理与工作流程如下: 1. 基本概念 select是一种I/O多路复用技术,通过单线程同时监控多个socket文件描述符的状态变化。它能检测三种事件: - 可读事件(数据到达) - 可写事件(可发送数据) - 异常事件(连接错误) 2. 核心函数 $$select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)$$ 3. 实现步骤: (1) 创建监听socket ```c int listen_fd = socket(AF_INET, SOCK_STREAM, 0); bind(listen_fd, ...); listen(listen_fd, BACKLOG); ``` (2) 初始化fd_set ```c fd_set read_fds; FD_ZERO(&read_fds); FD_SET(listen_fd, &read_fds); int max_fd = listen_fd; ``` (3) 事件循环 ```c while(1) { fd_set tmp_fds = read_fds; int ready = select(max_fd+1, &tmp_fds, NULL, NULL, NULL); if(FD_ISSET(listen_fd, &tmp_fds)) { // 处理新连接 int conn_fd = accept(...); FD_SET(conn_fd, &read_fds); max_fd = (conn_fd > max_fd) ? conn_fd : max_fd; } for(int fd = listen_fd+1; fd <= max_fd; ++fd) { if(FD_ISSET(fd, &tmp_fds)) { // 处理客户端数据 recv(fd, ...); // 业务逻辑处理 } } } ``` 4. 特点分析 - 优点: - 跨平台支持(Windows/Linux/BSD) - 单线程处理多连接 - 避免多线程上下文切换开销 - 缺点: - 文件描述符数量限制(FD_SETSIZE通常为1024) - 线性扫描所有fd效率低 - 需要每次重置fd_set - 高延迟(时间复杂度O(n)) 5. 适用场景 - 连接数 < 1000的轻量级服务 - 需要跨平台兼容的客户端程序 - 短连接为主的网络服务 6. 优化建议 - 使用非阻塞socket避免单连接阻塞 - 采用时间轮记录连接活跃时间 - 配合内存池减少内存分配开销 - 设置合理的select超时时间 对比epoll: $$吞吐量 = \frac{有效数据量}{单位时间} \quad select\ll epoll\ (当连接数>1000时)$$ 实际开发中,Linux平台推荐使用epoll,Windows平台建议使用IOCP,select适合作为学习多路复用的入门模型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值