Linux C编程:深入理解select函数 (linux c select函数)
在网络编程中,我们经常需要同时监视多个文件描述符,例如需要监听多个客户端连接请求,或者同时处理多个文件IO操作等等。这时候,就需要用到select函数。
select函数可以同时监视多个文件描述符的状态,包括可读、可写和异常等状态。当某个文件描述符的状态发生变化时,select函数会返回这个文件描述符。使用select函数可以实现高效的事件驱动编程。
本文将深入讲解select函数的原理和使用方法。
一、select函数原理
select函数的原理是通过轮询的方式来查询所有需要监视的文件描述符的状态是否发生变化,一旦有文件描述符的状态发生变化,select函数就会返回这个文件描述符。
select函数有三个参数,分别是:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中,nfds是需要监视的更大文件描述符数加1,readfds、writefds、exceptfds分别是文件描述符,表示需要监视可读、可写和异常状态的文件描述符,如果不需要监视某种状态,则为NULL。timeout表示select函数的超时时间。
select函数返回值是发生变化的文件描述符的个数,如果超时则返回0,如果返回-1则表示出错。
可以通过以下代码来进行select函数操作的流程演示:
#include
#include
int mn()
{
fd_set rfds;
struct timeval tv;
int retval;
/* 每次运行前清空 */
FD_ZERO(&rfds);
/* 将需要监视的文件描述符加入中 */
FD_SET(STDIN_FILENO, &rfds);
/* 超时时间为10秒 */
tv.tv_sec = 10;
tv.tv_usec = 0;
/* 监视文件描述符状态 */
retval = select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
perror(“select”);
}
else if (retval)
{
/* 文件描述符状态发生变化 */
if (FD_ISSET(STDIN_FILENO, &rfds))
{
printf(“Data is avlable now.\n”);
}
}
else
{
printf(“No data within ten seconds.\n”);
}
return 0;
}
以上代码将监视标准输入文件描述符的可读状态,如果在超时范围内有数据可读,则输出“Data is avlable now.”,如果超时则输出“No data within ten seconds.”。
二、select函数使用注意事项
使用select函数需要注意以下几点:
1. nfds参数的值应该是需要监视的更大文件描述符数加1,避免遗漏未监视的文件描述符。
2. 如果不需要监视某种状态,则对应的fd_set参数应该为NULL,否则可能会导致程序阻塞。
3. select函数的timeout参数可能会受到系统调度的影响而不准确,因此建议在程序中设置超时时间,并进行合理处理。
4. 对于返回值为-1的情况,错误原因可以通过perror函数来输出,方便调试程序。
5. 在多进程、多线程程序中使用select函数需要进行同步,避免出现竞争条件。
三、select函数实现模型推荐
在实际使用中,建议使用事件驱动编程模型,即使用select函数来实现事件循环,而不是使用fork或者线程等并发模型。
事件驱动编程模型的主要优点是可以减少系统资源消耗和排除竞争条件等问题,保证程序安全和稳定性。
下面是事件驱动编程模型的示例代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 12345
int mn()
{
int sockfd, client_sockfd;
socklen_t clilen;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
fd_set rfds, allfs;
int maxfd;
int retval;
/* 创建监听套接字 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd
{
perror(“socket”);
exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVER_PORT);
if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))
{
perror(“bind”);
exit(1);
}
if (listen(sockfd, 5)
{
perror(“listen”);
exit(1);
}
FD_ZERO(&allfs);
FD_SET(sockfd, &allfs);
maxfd = sockfd;
printf(“Select server start.\n”);
while (1)
{
rfds = allfs;
retval = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (retval == -1)
{
perror(“select”);
}
else if (retval)
{
/* 处理监听套接字读事件 */
if (FD_ISSET(sockfd, &rfds))
{
clilen = sizeof(cliaddr);
client_sockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen);
if (client_sockfd
{
perror(“accept”);
continue;
}
printf(“Client connected.\n”);
/* 添加客户端套接字描述符 */
FD_SET(client_sockfd, &allfs);
if (client_sockfd > maxfd)
{
maxfd = client_sockfd;
}
}
else
{
/* 处理客户端套接字读事件 */
for (int i=sockfd+1; i
{
if (FD_ISSET(i, &rfds))
{
char buf[1024];
memset(buf, 0, sizeof(buf));
ssize_t n = read(i, buf, sizeof(buf)-1);
if (n
{
perror(“read”);
continue;
}
else if (n == 0)
{
printf(“Client disconnected.\n”);
/* 删除客户端套接字描述符 */
FD_CLR(i, &allfs);
close(i);
}
else
{
buf[n] = ‘\0’;
printf(“Recv: %s\n”, buf);
/* 回复客户端套接字 */
ssize_t nwrite = write(i, buf, strlen(buf));
if (nwrite
{
perror(“write”);
}
}
}
}
}
}
}
close(sockfd);
return 0;
}
以上代码是一个简单的网络回显服务端程序,它使用事件驱动编程模型来实现高效的网络编程。程序使用select函数来监视所有连接的客户端套接字描述符和监听套接字描述符的读事件。