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
 Linux Kernel 2.6...5164 
 Trees...935 
 Максвелл 3...861 
 Go Web ...814 
 William Gropp...796 
 Ethreal 3...779 
 Ethreal 4...766 
 Gary V.Vaughan-> Libtool...765 
 Rodriguez 6...756 
 Steve Pate 1...749 
 Ext4 FS...748 
 Clickhouse...748 
 Ethreal 1...736 
 Secure Programming for Li...721 
 C++ Patterns 3...711 
 Ulrich Drepper...693 
 Assembler...687 
 DevFS...655 
 Стивенс 9...644 
 MySQL & PosgreSQL...622 
 
  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 , и если да , то с клиента считывается строка и отдается ему назад .

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

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

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