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
Последние статьи :
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
  SQL 30.07   
  Python 10.06   
 
TOP 20
 Part 3...299 
 Part 4...296 
 Kamran Husain...191 
 Commands...181 
 Secure Programming for Li...170 
 Stein-MacEachern-> Час...143 
 2.0-> Linux IP Networking...140 
 Ethreal 1...123 
 Сетунь...119 
 William Gropp...118 
 Trees...117 
 Python...117 
 Steve Pate 2...114 
 Steve Pate 3...109 
 Plusquellic 1...108 
 C++ Patterns 2...108 
 Httpd-> История Ap...108 
 Redox...108 
 Part 2...108 
 Ethreal 4...107 
 
  01.10.2020 : 2983014+ посещений 

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 , и если да , то с клиента считывается строка и отдается ему назад .

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

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

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