Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
      Languages 
      Kernels 
      Packages 
      Books 
      Tests 
      OS 
      Forum 
      Математика 
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
 Secure Programming for Li...6613 
 Linux Kernel 2.6...5375 
 Trees...1248 
 Максвелл 3...1173 
 Go Web ...1158 
 William Gropp...1147 
 Clickhouse...1035 
 Ethreal 4...1034 
 Ethreal 1...1033 
 Ethreal 3...1032 
 Rodriguez 6...1025 
 Ext4 FS...1023 
 Gary V.Vaughan-> Libtool...1010 
 Steve Pate 1...1000 
 Assembler...978 
 C++ Patterns 3...966 
 Ulrich Drepper...942 
 DevFS...890 
 MySQL & PosgreSQL...874 
 Стивенс 9...849 
 
  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 , и если да , то с клиента считывается строка и отдается ему назад .

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

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

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