Select (فراخوان سیستمی)

select() نام یک فراخوان سیستمی و رابط برنامه‌نویسی نرم‌افزار در سیستم‌عامل‌های شبه یونیکس و سازگار با استاندارد پازیکس است. از این فراخوان سیستمی، برای بررسی کردن وضعیت مجموعه‌ای از توصیف‌گرهای پرونده استفاده می‌شود. به کمک این فراخوان سیستمی، می‌توان تشخیص داد که در یک مجموعه از توصیف‌گرهای فایل، کدام یک از توصیف‌گرها آماده خواندن یا نوشتن هستند یا یک شرایط استثناء برای آنها پیش آمده است. فراخوان سیستمی select() مشابه فراخوان poll() در SysV و سیستم‌عامل‌های جدیدتر است. در زبان برنامه‌نویسی سی، فراخوان سیستمی select() معمولاً در فایل unistd.h تعریف شده است و تعداد کمی از سیستم‌عامل‌ها هم این فراخوان را در فایل سرایند sys/select.h تعریف کرده‌اند. این فراخوان به شکل زیر اعلان شده است:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

چندین ماکرو هم برای مدیریت کردن مجموعه‌ها تدارک دیده شده است که به صورت زیر هستند:

FD_SET(fd, &fdset);
FD_CLR(fd, &fdset);
FD_ISSET(fd, &fdset);
FD_ZERO(&fdset);

ماکروی FD_SET توصیف‌گر fd را به مجموعه fdset اضافه می‌کند. ماکروی FD_CLR توصیف‌گر fd را از مجموعه fdset حذف می‌کند. ماکرو FD_ISSET بررسی می‌کند که آیا توصیف‌گر fd در مجموعه fdset قرار دارد یا نه. ماکرو FD_ZERO هم کل مجموعه fdset را خالی می‌کند. 

select() تعداد کل توصیف‌گرهایی که در مجموعه‌های readfds و writefds و errorfds در حالت آماده قرار دارند را برمی‌گرداند. اگر خطایی اتفاق بیفتد، مقدار -1 برمی‌گردد و متغیر سراسری errno مقداردهی می‌شود. اگر مهلت زمانی به پایان برسد، مقدار صفر برمی‌گردد. با NULL قرار دادن سه پارامتر readfds، writefds و errorfds، می‌توان تایمری با دقت بالاتر نسبت به sleep() داشت.

select() در اکثر سیستم‌عامل‌ها وجود دارد و از قابلیت حمل بالایی برخوردار است، اما سرعت آن به نسبت پایین است. libevent می‌تواند یک جایگزین سریع‌تر برای select() باشد.

مثال 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define PORT "9421"

/* function prototypes */
void die(const char*);

int main(int argc, char **argv)
{
	int sockfd, new, maxfd, on = 1, nready, i;

	struct addrinfo *res0, *res, hints;

	char buffer[BUFSIZ];

	fd_set master, readfds;

	ssize_t nbytes;

	(void)memset(&hints, '\0', sizeof(struct addrinfo));

	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	if(-1 == (getaddrinfo(NULL, PORT, &hints, &res0)))
		die("getaddrinfo()");

	for(res = res0; res; res = res->ai_next)
	{
		if(-1 == (sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)))
		{
			perror("socket()");
			continue;
		}

		if(-1 == (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(int))))
		{
			perror("setsockopt()");
			continue;
		}

		if(-1 == (bind(sockfd, res->ai_addr, res->ai_addrlen)))
		{
			perror("bind");
			continue;
		}

		break;

	}

	if(-1 == sockfd)
		exit(EXIT_FAILURE);

	freeaddrinfo(res0);

	if(-1 == (listen(sockfd, 32)))
		die("listen()");

	if(-1 == (fcntl(sockfd, F_SETFD, O_NONBLOCK)))
		die("fcntl()");

	FD_ZERO(&master);
	FD_ZERO(&readfds);
	
	FD_SET(sockfd, &master);

	maxfd = sockfd;

	for(;;)
	{
		memcpy(&readfds, &master, sizeof(master));

		(void)printf("running select()\n");

		if(-1 == (nready = select(maxfd+1, &readfds, NULL, NULL, NULL)))
			die("select()");

		(void)printf("Number of ready descriptor: %d\n", nready);

		for(i=0; i<=maxfd && nready>0; i++)
		{
			if(FD_ISSET(i, &readfds))
			{
				nready--;

				if(i == sockfd)
				{
					(void)printf("Trying to accept() new connection(s)\n");

					if(-1 == (new = accept(sockfd, NULL, NULL)))
					{
						if(EWOULDBLOCK != errno)
							die("accept()");

						break;
					}
					
					else
					{

						if(-1 == (fcntl(new, F_SETFD, O_NONBLOCK)))
							die("fcntl()");

						FD_SET(new, &master);

						if(maxfd <new)
							maxfd = new;
					}
				}

				else
				{
					(void)printf("recv() data from one of descriptors(s)\n");

					nbytes = recv(i, buffer, sizeof(buffer), 0);
					if(nbytes <= 0)
					{
						if(EWOULDBLOCK != errno)
							die("recv()");

						break;
					}

					buffer[nbytes] = '\0';
					printf("%s", buffer);
					
					(void)printf("%zi bytes received.\n", nbytes);

					close(i);
					FD_CLR(i, &master);

				}
			}

		}

	}

	return 0;
}

void die(const char *msg)
{
	perror(msg);
	exit(EXIT_FAILURE);
}

منابع 

رویدادها (رایانش)
فراخوان‌های سیستمی
کتابخانه پازیکس سی
مقاله‌های خرد یونیکس