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);
آرگومان توضیح
nfds بزرگ‌ترین توصیف‌گر پرونده در همه مجموعه‌ها، به علاوه یک.
readfds این پارامتر که از نوع fd_set است، در حین فراخوانایی تابع، باید توسط برنامه‌نویس پر شود و نشان‌دهنده توصیف‌گرهایی است که باید آماده بودن آنها جهت انجام عمل خواندن بررسی شود. در خروجی و پس از اجرای select()‎، این پارامتر توسط تابع تغییر می‌کند و نشان‌دهنده توصیف‌گرهایی است که اطلاعات در آنها آماده خواندن است. اگر نمی‌خواهید هیچ توصیف‌گری را جهت آماده بودن برای خواندن بررسی کنید، می‌توانید این پارامتر را با NULL مقدار دهی کنید.
writefds این پارامتر که از نوع fd_set است، در حین فراخوانایی تابع، باید توسط برنامه‌نویس پر شود و نشان‌دهنده توصیف‌گرهایی است که باید آماده بودن آنها جهت انجام عمل نوشتن بررسی شود. در خروجی و پس از اجرای select()‎، نشان‌دهنده توصیف‌گرهایی است که اطلاعات در آنها آماده نوشتن است. اگر نمی‌خواهید هیچ توصیف‌گری را جهت آماده بودن برای نوشتن بررسی کنید، می‌توانید این پارامتر را با NULL مقدار دهی کنید.
errorfds این پارامتر که از نوع fd_set است، در حین فراخوانایی تابع، باید توسط برنامه‌نویس پر شود و نشان‌دهنده توصیف‌گرهایی است که باید بررسی شوند تا معلوم شود که آیا دارای استثنا هستند یا نه. این پارامتر در خروجی و پس از اجرای select()‎، نشان‌دهنده توصیف‌گرهایی است که دارای شرایط استثنا هستند. اگر نمی‌خواهید هیچ توصیف‌گری را جهت قرار داشتن در شرایط استثنا بررسی کنید، می‌توانید این پارامتر را با NULL مقدار دهی کنید.
timeout این پارامتر ساختاری از نوع timeval است. این پارامتر مشخص می‌کند که select()‎ حداکثر باید تا چه مدت زمانی توصیفگرها را بررسی کند. اگر اعضای این ساختار با 0 مقداردهی شده باشند، select()‎ بلوکه نخواهد شد. اگر این پارامتر NULL باشد، select()‎ برای یک مدت زمان تعریف نشده بلوکه می‌شود.

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

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);
}

منابع ویرایش