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...5170 
 Trees...938 
 Максвелл 3...870 
 Go Web ...823 
 William Gropp...802 
 Ethreal 3...787 
 Gary V.Vaughan-> Libtool...772 
 Ethreal 4...770 
 Rodriguez 6...763 
 Ext4 FS...755 
 Steve Pate 1...754 
 Clickhouse...753 
 Ethreal 1...742 
 Secure Programming for Li...731 
 C++ Patterns 3...716 
 Ulrich Drepper...696 
 Assembler...694 
 DevFS...660 
 Стивенс 9...649 
 MySQL & PosgreSQL...631 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

 W. R. Stevens  : Глава 4

Элементарные сокеты TCP

В этой главе будут описаны функции , необходимые для работоспособного клиента и сервера TCP. Сам клиент и сервер будут созданы в следующей , 5-й главе.

Будут описаны параллельные серверы - когда множество клиентов соединяются с одним и тем же сервером. При этом сервер каждый раз будет выполнять функцию fork , порождающую новый серверный процесс.

Типичный сценарий взаимодействия между клиентом и сервером :


 								 TCP-сервер
 								------------
 								| socket() |
 								------------
 								     |
 								------------
 								|  bind()  |
 								------------
 								     |
 								------------
 								| listen() |
 								------------
 								     |
 								------------
 								| accept() |
    TCP-клиент							------------
   ------------							     |
   | socket() |						Блокировка для соединения
   ------------							с клиентом
        |     						 	     |   
   ------------	         Установка соединения            	     |
   | connect()|		<---------------------> 		     |
   ------------	       (3-этапное рукопожатие TCP)		     |
        |                                      			     |
   ------------         Данные(запрос)  			-------------
   | write()  | ---------------------------------------> 	| read()    |
   ------------                          			-------------
        |							     |
        |							     |	
   ------------         Данные (ответ)            		-------------
   |  read()  |    <------------------------------------------  | write()   |
   ------------                                			-------------
        |							     !	
        |							     !	
   ------------      Уведомление о конце файла       		-------------
   | close()  |   ------------------------------->  		| read()    |
   ------------                                      		-------------
 								     |
 								-------------
 								| close()   |
 								-------------
 

Функция socket


 	int socket(int family , int type , int protocol);
Константа family может принимать значения :

 	AF_INET
 	AF_INET6
 	AF_LOCAL
 	AF_ROUTE
 	AF_KEY
Константа type может принимать значения:

 	SOCK_STREAM
 	SOCK_DGRAM
 	SOCK_RAW
protocol обычно равен нулю , за исключением символьных сокетов

Не все сочетания family и type допустимы . Допустимые сочетания :


 	===========================================================================
 	 AF_INET          AF_INET6    AF_LOCAL     AF_ROUTE     AF_key
 	===========================================================================
 	SOCK_STREAM 	  TCP          TCP         Да           
 	SOCK_DGRAM        UDP          UDP         Да
 	SOCK_RAW          IPv6                     Да            Да
 	===========================================================================
При успешном выполнении функции socket() возвращается целое неотрицательное число - sockfd.

Функция connect

Функция используется клиентом.

 	int connect(int sockfd , const struct sockaddr *servaddr,socklen_t addrlen);
Возвращает либо 0 , либо -1.
В случае протокола TCP функция инициирует 3-этапное рукопожатие. Возможные ошибки :

 	ETIMEOUT
 	ECONNREFUSED
 	EHOSTUNREACH
 	ENETUNREACH 
 

Функция bind

Функция связывает сокет с локальным адресом протокола. Это комбинация 32-разрядного (IPv4) либо 128-разрядного (IPv6) адреса протокола с 16-разрядным номером порта TCP либо UDP.

 	int bind(int sockfd , const struct sockaddr *myaddr,socklen_t addrlen);
Функция позволяет нам либо задать ip и порт , либо ничего не задавать. Если мы зададим нулевой порт , то при вызове bind ядро само динамически выберет порт.

Функция listen

Функция вызывается на сервере и выполняет 2 действия :
1. Переводит сокет из состояния CLOSED в состояние LISTEN
2. 2-й аргумент этой функции задает максимальное число соединений , которое ядро может поместить в очередь этого сокета.

  int listen(int sockfd , int backlog);

Для сокета ядро может поддерживать 2 очереди :
1. Очередь не полностью установленных соединений.Эти сокеты находятся в состоянии SYN_RCVD
2. Очередь полностью установленных соединений. Эти сокеты находятся в состоянии ESTABLISHED.
Аргумент backlog задает максимальное значение для обоих очередей.Не нужно этому аргументу присваивать ноль, лучше просто закрыть сокет. Этот параметр является одним из основных в серверах http , и он может быть явно больше 5 - например , 64. Если очередь заполнена , то протокол TCP будет просто игнорировать вновь приходящий SYN.

Функция accept

Функция вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений. Если очередь пуста , процесс переходит в состояние ожидания

 	int accept(int sockfd , struct sockaddr *cliaddr,socklen_t addrlen);
Если функция возвращает положительное число , это означает , что ядро создало новый дескриптор. Этот дескриптор будет использоваться конкретным клиентом. Первый аргумент функции - прослушивающий сокет , существующий как правило в единственном экземпляре . Затем ядро создает по одному присоединенному сокету для каждого клиентского соединения , принятого с помощью accept. cliaddr - адрес протокола клиентского процесса. addrlen - размер адреса. Можно cliaddr и addrlen сделать пустыми указателями , если они нам не нужны.
В следующем листинге показано , как присоединенный сокет закрывается при каждом прохождении цикла , но прослушиваемый сокет остается открытым. Здесь 2-й и 3-й аргументы accept - пустые указатели , поскольку нам не нужна идентификация клиента.

  // intro/daytimetcpsrv1.c
 #include	"unp.h"
 #include	< time.h>
 
 int main(int argc, char **argv)
 {
 	int					listenfd, connfd;
 	socklen_t			len;
 	struct sockaddr_in	servaddr, cliaddr;
 	char				buff[MAXLINE];
 	time_t				ticks;
 
 	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(13);	/* daytime server */
 
 	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
 
 	Listen(listenfd, LISTENQ);
 
 	for ( ; ; ) {
 		len = sizeof(cliaddr);
 		connfd = Accept(listenfd, (SA *) &cliaddr, &len);
 		printf("connection from %s, port %d\n",
 			   Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
 			   ntohs(cliaddr.sin_port));
 
         ticks = time(NULL);
         snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
         Write(connfd, buff, strlen(buff));
 
 		Close(connfd);
 	}
 }
Мы вызываем здесь функцию inet_ntop для преобразования 32-битного IP-адреса в строку ASCII (точечно-десятичную запись) , затем вызываем ntohs для преобразования сетевого порядка байтов в порядок байтов узла. Адрес IP-сервера - 127.0.0.1 - это т.н. loopback address , здесь и клиент , и сервер запускаются на одной машине.Сервер должен обладать админскими правами.

Функции fork и exec


 	pid_t fork(void);
 
Функция fork возвращает : 0 в дочернем процессе , идентификатор дочернего процесса в родительском процессе. В случае ошибки возвращает -1. Функция , вызываемая 1 раз , возвращает 2 значения :-) Дочернему процессу возвращается ноль , а не id-шник родителя . потому что у дочернего процесса всегда есть возможность узнать идентификатор родителя - функция getppid. Все дескрипторы . открытые в родительском процессе , становятся доступны дочернему после fork. Существует 2 типичных случая использования fork :
1. Процесс создает копию , чтобы та могла обработать одно задание - применяется серверами.
2. Запуск другой программы - после fork идет вызов функции exec . Exec - пожалуй единственный способ запустить файл на выполнение.

Параллельные серверы

Сервер , представленный в предыдущем листинге , является последовательным. Он связан с одним клиентом . В следующем примере показан параллельный сервер , обслуживающий несколько клиентов одновременно.

 	pid_t pid;
 	int listenfd , connfd;
 	listenfd = Socket( ...);
 	Bind(listenfd , ...);
 	Listen(listenfd,LISTENQ);
 
 	for(;;) {
 		connfd = Accept(listenfd , ...);
 		if ((pid == Fork()) ==0) {
 			Close(listenfd);
 			doit(connfd);
 			Close(connfd);
 			exit(0);
 		}
 		Close(connfd);
 	}
Здесь после установки соединения форкается дочерний процесс , который обслуживает клиента , а родительский процесс ждет другое соединение. doit -условная функция для обслуживания клиента. Вызов функции Close сразу после doit , кстати , необязателен , поскольку последующий exit все равно всё прибьет . Стивенс тут выражается так - это , мол , уже дело вкуса программера. А вот вызов Close после цикла обязателен , иначе TCP -соединение не закроется.

Функции getsockname и getpeername

Эти функции возвращают либо локальный , либо удаленный адрес сокета.

 	int getsockname(int sockfd, struct sockaddr *localadd, socklen_t *addrlen); 
     int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
 
Функция getsockname нужна клиенту для получения IP-адреса и номера локального порта. Если функция bind вызвана с номером порта 0 , то функция getpeername вернет нужный номер порта. Сервер с помощью функции getsockname может получить локальный ip-адрес соединения.

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

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

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