организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2. |
Семейство протоколов TCP/IP. Сокеты (sockets) в UNIX и основы работы с ними
Системные вызовы sendto() и recvfrom()
Для отправки датаграмм применяется системный вызов sendto() . В число параметров этого вызова входят:
- дескриптор сокета, через который отсылается датаграмма ;
- адрес области памяти, где лежат данные, которые должны составить содержательную часть датаграммы, и их длина;
- флаги, определяющие поведение системного вызова (в нашем случае они всегда будут иметь значение 0 );
- указатель на структуру, содержащую адрес сокета получателя, и ее фактическая длина.
Системный вызов возвращает отрицательное значение при возникновении ошибки и количество реально отосланных байт при нормальной работе. Нормальное завершение системного вызова не означает, что датаграмма уже покинула ваш компьютер! Датаграмма сначала помещается в системный сетевой буфер, а ее реальная отправка может произойти после возврата из системного вызова. Вызов sendto() может блокироваться, если в сетевом буфере не хватает места для датаграммы.
Для чтения принятых датаграмм и определения адреса получателя (при необходимости) служит системный вызов recvfrom() . В число параметров этого вызова входят:
- Дескриптор сокета, через который принимается датаграмма.
- Адрес области памяти, куда следует положить данные, составляющие содержательную часть датаграммы.
- Максимальная длина, допустимая для датаграммы. Если количество данных датаграммы превышает заданную максимальную длину, то вызов по умолчанию рассматривает это как ошибочную ситуацию.
- Флаги, определяющие поведение системного вызова (в нашем случае они будут полагаться равными 0 ).
- Указатель на структуру, в которую при необходимости может быть занесен адрес сокета отправителя. Если этот адрес не требуется, то можно указать значение NULL.
- Указатель на переменную, содержащую максимально возможную длину адреса отправителя. После возвращения из системного вызова в нее будет занесена фактическая длина структуры, содержащей адрес отправителя. Если предыдущий параметр имеет значение NULL, то и этот параметр может иметь значение NULL.
Системный вызов recvfrom() по умолчанию блокируется, если отсутствуют принятые датаграммы, до тех пор, пока датаграмма не появится. При возникновении ошибки он возвращает отрицательное значение, при нормальной работе – длину принятой датаграммы.
Определение IP-адресов для вычислительного комплекса
Для определения IP-адресов на компьютере можно воспользоваться утилитой /sbin/ifconfig. Эта утилита выдает всю информацию о сетевых интерфейсах, сконфигурированных в вычислительной системе. Пример выдачи утилиты показан ниже:
eth0 Link encap:Ethernet HWaddr 00:90:27:A7:1B:FE inet addr:192.168.253.12 Bcast:192.168.253.255 Mask:255.255.255.0 UP BROADCAST NOTRAILERS RUNNING MULTICAST MTU:1500 Metric:1 RX packets:122556059 errors:0 dropped:0 overruns:0 frame:0 TX packets:116085111 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:100 RX bytes:2240402748 (2136.6 Mb) TX bytes:3057496950 (2915.8 Mb) Interrupt:10 Base address:0x1000 lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:403 errors:0 dropped:0 overruns:0 frame:0 TX packets:403 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:39932 (38.9 Kb) TX bytes:39932 (38.9 Kb)Пример 15-16.0. Информация о сетевых интерфейсах.
Сетевой интерфейс eth0 использует протокол Ethernet. Физический 48-битовый адрес, зашитый в сетевой карте, – 00:90:27:A7:1B:FE. Его IP-адрес – 192.168.253.12.
Сетевой интерфейс lo не относится ни к какой сетевой карте. Это так называемый локальный интерфейс, который через общую память эмулирует работу сетевой карты для взаимодействия процессов, находящихся на одной машине по полным сетевым адресам. Наличие этого интерфейса позволяет отлаживать сетевые программы на машинах, не имеющих сетевых карт. Его IP-адрес обычно одинаков на всех компьютерах – 127.0.0.1.
Пример программы UDP-клиента
Рассмотрим, наконец, простой пример программы 15–16-1.с. Эта программа является UDP-клиентом для стандартного системного сервиса echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого нужно обратиться, в качестве аргумента командной строки, например:
a.out 192.168.253.12
Ниже следует текст программы
/* Простой пример UDP клиента для сервиса echo */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> int main(int argc, char **argv) { int sockfd; /* Дескриптор сокета */ int n, len; /* Переменные для различных длин и количества символов */ char sendline[1000], recvline[1000]; /* Массивы для отсылаемой и принятой строки */ struct sockaddr_in servaddr, cliaddr; /* Структуры для адресов сервера и клиента */ /* Сначала проверяем наличие второго аргумента в командной строке. При его отсутствии ругаемся и прекращаем работу */ if(argc != 2){ printf("Usage: a.out <IP address>\n"); exit(1); } /* Создаем UDP сокет */ if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */ exit(1); } /* Заполняем структуру для адреса клиента: семейство протоколов TCP/IP, сетевой интерфейс – любой, номер порта по усмотрению операционной системы. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&cliaddr, sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(0); cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Настраиваем адрес сокета */ if(bind(sockfd, (struct sockaddr *) &cliaddr, sizeof(cliaddr)) < 0){ perror(NULL); close(sockfd); /* По окончании работы закрываем дескриптор сокета */ exit(1); } /* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс – из аргумента командной строки, номер порта 7. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(7); if(inet_aton(argv[1], &servaddr.sin_addr) == 0){ printf("Invalid IP address\n"); close(sockfd); /* По окончании работы закрываем дескриптор сокета */ exit(1); } /* Вводим строку, которую отошлем серверу */ printf("String => "); fgets(sendline, 1000, stdin); /* Отсылаем датаграмму */ if(sendto(sockfd, sendline, strlen(sendline)+1, 0, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Ожидаем ответа и читаем его. Максимальная допустимая длина датаграммы – 1000 символов, адрес отправителя нам не нужен */ if((n = recvfrom(sockfd, recvline, 1000, 0, (struct sockaddr *) NULL, NULL)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Печатаем пришедший ответ и закрываем сокет */ printf("%s\n", recvline); close(sockfd); return 0; }Листинг 15-16.1. Программа 15–16-1.c . Простой пример UDP клиента для сервиса echo.
Наберите и откомпилируйте программу. Перед запуском "узнайте у своего системного администратора", запущен ли в системе стандартный UDP-сервис echo и если нет, попросите стартовать его. Запустите программу с запросом к сервису своего компьютера, к сервисам других компьютеров. Если в качестве IP-адреса указать несуществующий адрес, адрес выключенной машины или машины, на которой не работает сервис echo, то программа бесконечно блокируется в вызове recvfrom() , ожидая ответа. Протокол UDP не является надежным протоколом. Если датаграмму доставить по назначению не удалось, то отправитель никогда об этом не узнает!
Пример программы UDP-сервера
Поскольку UDP-сервер использует те же самые системные вызовы, что и UDP-клиент, мы можем сразу приступить к рассмотрению примера UDP-сервера (программа 15–16-2.с ) для сервиса echo.
/* Простой пример UDP-сервера для сервиса echo */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> int main() { int sockfd; /* Дескриптор сокета */ int clilen, n; /* Переменные для различных длин и количества символов */ char line[1000]; /* Массив для принятой и отсылаемой строки */ struct sockaddr_in servaddr, cliaddr; /* Структуры для адресов сервера и клиента */ /* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс – любой, номер порта 51000. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(51000); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Создаем UDP сокет */ if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */ exit(1); } /* Настраиваем адрес сокета */ if(bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror(NULL); close(sockfd); exit(1); } while(1) { /* Основной цикл обслуживания*/ /* В переменную clilen заносим максимальную длину для ожидаемого адреса клиента */ clilen = sizeof(cliaddr); /* Ожидаем прихода запроса от клиента и читаем его. Максимальная допустимая длина датаграммы – 999 символов, адрес отправителя помещаем в структуру cliaddr, его реальная длина будет занесена в переменную clilen */ if((n = recvfrom(sockfd, line, 999, 0, (struct sockaddr *) &cliaddr, &clilen)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Печатаем принятый текст на экране */ printf("%s\n", line); /* Принятый текст отправляем обратно по адресу отправителя */ if(sendto(sockfd, line, strlen(line), 0, (struct sockaddr *) &cliaddr, clilen) < 0){ perror(NULL); close(sockfd); exit(1); } /* Уходим ожидать новую датаграмму*/ } return 0; }Листинг 15-16.2. Программа 15–16-2.c . Простой пример UDP-сервера для сервиса echo.
Наберите и откомпилируйте программу. Запустите ее на выполнение. Модифицируйте текст программы UDP-клиента (программа 15–16-1.c ), заменив номер порта с 7 на 51000. Запустите клиента с другого виртуального терминала или с другого компьютера и убедитесь, что клиент и сервер взаимодействуют корректно.