Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Books
  Краткое описание
 Linux
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 A.Rubini-J.Corbet 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 K.A.Robbins 
 С Бекман 
 Р Стивенс 
 Ethereal 
 Cluster 
 Languages
 C
 Perl
 M.Pilgrim 
 А.Фролов 
 Mendel Cooper 
 М Перри 
 Kernel
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 Максвелл 
 G. Kroah-Hartman 
 B. Hansen 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 MINIX...3057 
 Solaris...2933 
 LD...2904 
 Linux Kernel 2.6...2470 
 William Gropp...2180 
 Rodriguez 6...2011 
 C++ Templates 3...1945 
 Trees...1937 
 Kamran Husain...1865 
 Secure Programming for Li...1792 
 Максвелл 5...1710 
 DevFS...1693 
 Part 3...1682 
 Stein-MacEachern-> Час...1632 
 Go Web ...1624 
 Ethreal 4...1618 
 Arrays...1606 
 Стивенс 9...1603 
 Максвелл 1...1592 
 FAQ...1538 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Глава 6 : Мультиплексирование ввода-вывода : функции select , poll

Код данной главы лежит тут

В предыдущей главе описывется клиент-сервер , у которого есть один существенный недостаток : во время чтения сокета канал блокируется до тех пор , пока клиент не получит все данные . Этот недостаток можно преодолеть , сообщив ядру , что мы хотим получить уведомление о том , что появились данные , которые можно считывать из сокета . Эта возможность называется мультиплексированием ввода-вывода и обеспечивается функциями select и poll.

Мультиплексирование используется обычно в следующих случаях :

 	1 Когда клиент обрабатывает множество дескрипторов
 	2 Сервер работает и с udp , и с TCP
 	3 Сервер обрабатывает множество служб
 
В UNIX вообще говоря доступны 5 основных моделей ввода-вывода :
 	1 Блокируемый 
 	2 Неблокируемый
 	3 Мультиплексирование 
 	4 Ввод-вывод , управляемый сигналом (SIGIO)
 	5 Асинхронный ввод-вывод
 
В операции ввода обычно 2 фазы :
 	1 Ожидание готовности данных - по приходу пакета он обычно копируется в буфер ядра 
 	2 Копирование данных от ядра процессу - копирование из буфера ядра в буфер приложения
 
Наиболее распространенной моделью является блокируемый ввод-вывод. Все сокеты по умолчанию блокируемые.
Системная функция recfrom работает в режиме приложения , затем переключается в режим ядра , а затем возвращается в режим приложения. Процесс блокирован до тех пор , пока "ушедшая" в ядро функция не вернет данные .

Режим неблокируемого ввода-вывода отличается тем , что recvfrom вызывается каждый раз в цикле до тех пор , пока ядро не вернет данные для считывания :
Такой процесс называется опросом - polling . Вообще говоря , это пустая трата процессорного времени , но тем не менее .

Режим мультиплексированного ввода-вывода делает блокировку , но она происходит не при вводе-выводе , а при вызове select или poll.
Преимущество мультиплексированного ввода-вывода перед блокируемым в том , что мы можем обрабатывать не один , а несколько дескрипторов .

Модель ввода-вывода , управляемого сигналом , с помощью ядра посылает процессу сигнал SIGIO о готовности дескриптора .
Сигнал обрабатывается с помощью sigaction.

Сравнение моделей ввода-вывода :

Функция select сообщает ядру , что нужно подержать процесс в состоянии ожидания до тех пор , пока не произойдет какое-то событие , или по истечение времени . При этом мы передаем ядру список интересуемых нас дескрипторов либо интервал времени .

 	int select ( int maxfdp1 , 
 			fd_set * readset , 
 			fd_set * writeset , 
 			fd_set * exeptset , 
 			const struct timeval * timeout )
 
 	возвращает : либо положительное число готовых дескрипторов
 				либо ноль в случае тайм-аута
 				либо -1 в случае ошибки
 
С помощью последнего аргумента - времени - можно реализовать 3 сценария :
 	1 ждать вечно - timeval = NULL
 	2 ждать в течение определенного времени - в миллисекундах
 	3 не ждать вообще timeval = 0
 
Три средних аргумента определяют тип дескрипторов . Это изменяемый тип аргументов , и их значение можно проверить с помощью макроса FD_ISSET . Первый аргумент задает число проверяемых дескрипторов . Максимально возможное число дескрипторов обычно 1024 .

А теперь начнем переписывать эхо-сервер , который был описан в предыдущей пятой главе . Перепишем его с помощью функции select и без использования fork. Сервер будет обслуживать набор дескрипторов для чтения . Дескрипторы 0,1 и 2 будут соответственно потоками ввода , вывода и ошибок. Поэтому первым доступным для прослушивания сокетом станет номер 3 , и первым аргументом функции select будет 4. Все дескрипторы будут содержаться в массиве client и проинициализированы как -1.
После того , как первый клиент установит коннект с сервером , картина станет такой :
Затем еще один слиент :
Затем первый клиент завершает соединение :

Код сервера :

 //tcpcliserv/tcpservselect01.c
 
 int main(int argc, char **argv)
 {
 	int					i, maxi, maxfd, listenfd, connfd, sockfd;
 	int					nready, client[FD_SETSIZE];
 	ssize_t				n;
 	fd_set				rset, allset;
 	char				line[MAXLINE];
 	socklen_t			clilen;
 	struct sockaddr_in	cliaddr, servaddr;
 
 	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
 
 	bzero(&servaddr, sizeof(servaddr));
 	servaddr.sin_family      = AF_INET;
 	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 	servaddr.sin_port        = htons(SERV_PORT);
 
 	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
 
 	Listen(listenfd, LISTENQ);
 
 	maxfd = listenfd;			/* initialize */
 	maxi = -1;					/* index into client[] array */
 	for (i = 0; i < FD_SETSIZE; i++)
 		client[i] = -1;			/* -1 indicates available entry */
 	FD_ZERO(&allset);
 	FD_SET(listenfd, &allset);
 
 	for ( ; ; ) {
 		rset = allset;		/* structure assignment */
 		nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
 
 		if (FD_ISSET(listenfd, &rset)) {	/* new client connection */
 			clilen = sizeof(cliaddr);
 			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
 #ifdef	NOTDEF
 			printf("new client: %s, port %d\n",
 					Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
 					ntohs(cliaddr.sin_port));
 #endif
 
 			for (i = 0; i < FD_SETSIZE; i++)
 				if (client[i] < 0) {
 					client[i] = connfd;	/* save descriptor */
 					break;
 				}
 			if (i == FD_SETSIZE)
 				err_quit("too many clients");
 
 			FD_SET(connfd, &allset);	/* add new descriptor to set */
 			if (connfd > maxfd)
 				maxfd = connfd;			/* for select */
 			if (i > maxi)
 				maxi = i;				/* max index in client[] array */
 
 			if (--nready <= 0)
 				continue;				/* no more readable descriptors */
 		}
 
 		for (i = 0; i <= maxi; i++) {	/* check all clients for data */
 			if ( (sockfd = client[i]) < 0)
 				continue;
 			if (FD_ISSET(sockfd, &rset)) {
 				if ( (n = Readline(sockfd, line, MAXLINE)) == 0) {
 						/*4connection closed by client */
 					Close(sockfd);
 					FD_CLR(sockfd, &allset);
 					client[i] = -1;
 				} else
 					Writen(sockfd, line, n);
 
 				if (--nready <= 0)
 					break;				/* no more readable descriptors */
 			}
 		}
 	}
 }
 
 
Функция select ждет , пока не будет установлено новое клиентское соединение , либо на существующем соединении не прибудут данные , сегмент FIN или сегмент RST .

Затем вызываем accept и меняем структуры данных. В каждом клиентском соединении проверяем , содержится ли его дескриптор в наборе дескрипторов client , и если да , то с клиента считывается строка и отдается ему назад .

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье