Практикум по дисциплине «Операционные системы и среды» Методичес. Практикум по дисциплине Операционные системы и среды
Скачать 3.29 Mb.
|
Сокеты в LinuxСоздание сокета. Системный вызов socket()Для создания сокета в операционной системе служит системный вызов socket(). Для транспортных протоколов семейства TCP/IP существует два вида сокетов: UDP-сокет – сокет для работы с датаграммами, и TCP сокет – потоковый сокет. Однако понятие сокета (см. лекцию 14, раздел "Полные адреса. Понятие сокета (socket)") не ограничивается рамками только этого семейства протоколов. Рассматриваемый интерфейс сетевых системных вызовов (socket(), bind(), recvfrom() , sendto() и т. д.) в операционной системе UNIX может применяться и для других стеков протоколов (и для протоколов, лежащих ниже транспортного уровня). При создании сокета необходимо точно специфицировать его тип. Эта спецификация производится с помощью трех параметров вызова socket(). Первый параметр указывает, к какому семейству протоколов относится создаваемый сокет, а второй и третий параметры определяют конкретный протокол внутри данного семейства. Второй параметр служит для задания вида интерфейса работы с сокетом – будет это потоковый сокет, сокет для работы с датаграммами или какой-либо иной. Третий параметр указывает протокол для заданного типа интерфейса. В стеке протоколов TCP/IP существует только один протокол для потоковых сокетов – TCP и только один протокол для датаграммных сокетов – UDP, поэтому для транспортных протоколов TCP/IP третий параметр игнорируется. В других стеках протоколов может быть несколько протоколов с одинаковым видом интерфейса, например, датаграммных, различающихся по степени надежности. Для транспортных протоколов TCP/IP мы всегда в качестве первого параметра будем указывать предопределенную константу AF_INET (Address family – Internet) или ее синоним PF_INET (Protocol family – Internet). Второй параметр будет принимать предопределенные значения SOCK_STREAM для потоковых сокетов и SOCK_DGRAM – для датаграммных. Поскольку третий параметр в нашем случае не учитывается, в него мы будем подставлять значение 0. Ссылка на информацию о созданном сокете помещается в таблицу открытых файлов процесса подобно тому, как это делалось для pip’ов и FIFO (см. семинар 5). Системный вызов возвращает пользователю файловый дескриптор, соответствующий заполненному элементу таблицы, который далее мы будем называть дескриптором сокета. Такой способ хранения информации о сокете позволяет, во-первых, процессам-детям наследовать ее от процессов-родителей, а, во-вторых, использовать для сокетов часть системных вызовов, которые уже знакомы нам по работе с pip’ами и FIFO: close(), read(), write(). Адреса сокетов. Настройка адреса сокета. Системный вызов bind()Когда сокет создан, необходимо настроить его адрес. Для этого используется системный вызов bind(). Первый параметр вызова должен содержать дескриптор сокета, для которого производится настройка адреса. Второй и третий параметры задают этот адрес. Во втором параметре должен быть указатель на структуру struct sockaddr, содержащую удаленную и локальные части полного адреса. Указатели типа struct sockaddr * встречаются во многих сетевых системных вызовах; они используются для передачи информации о том, к какому адресу привязан или должен быть привязан сокет. Системные вызовы sendto() и recvfrom()Для отправки датаграмм применяется системный вызов sendto(). В число параметров этого вызова входят: дескриптор сокета, через который отсылается датаграмма; адрес области памяти, где лежат данные, которые должны составить содержательную часть датаграммы, и их длина; флаги, определяющие поведение системного вызова (в нашем случае они всегда будут иметь значение 0); указатель на структуру, содержащую адрес сокета получателя, и ее фактическая длина. Системный вызов возвращает отрицательное значение при возникновении ошибки и количество реально отосланных байт при нормальной работе. Нормальное завершение системного вызова не означает, что датаграмма уже покинула ваш компьютер! Датаграмма сначала помещается в системный сетевой буфер, а ее реальная отправка может произойти после возврата из системного вызова. Вызов sendto() может блокироваться, если в сетевом буфере не хватает места для датаграммы. Для чтения принятых датаграмм и определения адреса получателя (при необходимости) служит системный вызов recvfrom(). В число параметров этого вызова входят: Дескриптор сокета, через который принимается датаграмма. Адрес области памяти, куда следует положить данные, составляющие содержательную часть датаграммы. Максимальная длина, допустимая для датаграммы. Если количество данных датаграммы превышает заданную максимальную длину, то вызов по умолчанию рассматривает это как ошибочную ситуацию. Флаги, определяющие поведение системного вызова (в нашем случае они будут полагаться равными 0). Указатель на структуру, в которую при необходимости может быть занесен адрес сокета отправителя. Если этот адрес не требуется, то можно указать значение NULL. Указатель на переменную, содержащую максимально возможную длину адреса отправителя. После возвращения из системного вызова в нее будет занесена фактическая длина структуры, содержащей адрес отправителя. Если предыдущий параметр имеет значение NULL, то и этот параметр может иметь значение NULL. Системный вызов recvfrom() по умолчанию блокируется, если отсутствуют принятые датаграммы, до тех пор, пока датаграмма не появится. При возникновении ошибки он возвращает отрицательное значение, при нормальной работе – длину принятой датаграммы. Пример программы UDP-клиентаРассмотрим, наконец, простой пример программы 15–16-1.с. Эта программа является UDP-клиентом для стандартного системного сервиса echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого нужно обратиться, в качестве аргумента командной строки, например: a.out 192.168.253.12 Ниже следует текст программы /* Простой пример UDP клиента для сервиса echo */ #include #include #include #include #include #include #include #include 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 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; } Наберите и откомпилируйте программу. Перед запуском "узнайте у своего системного администратора", запущен ли в системе стандартный UDP-сервис echo и если нет, попросите стартовать его. Запустите программу с запросом к сервису своего компьютера, к сервисам других компьютеров. Если в качестве IP-адреса указать несуществующий адрес, адрес выключенной машины или машины, на которой не работает сервис echo, то программа бесконечно блокируется в вызове recvfrom(), ожидая ответа. Протокол UDP не является надежным протоколом. Если датаграмму доставить по назначению не удалось, то отправитель никогда об этом не узнает! Пример программы UDP-сервераПоскольку UDP-сервер использует те же самые системные вызовы, что и UDP-клиент, мы можем сразу приступить к рассмотрению примера UDP-сервера (программа 15–16-2.с) для сервиса echo. /* Простой пример UDP-сервера для сервиса echo */ #include #include #include #include #include #include #include #include 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. Запустите клиента с другого виртуального терминала или с другого компьютера и убедитесь, что клиент и сервер взаимодействуют корректно. Пример программы TCP-клиентаРассмотрим пример – программу 15–16-3.с. Это простой TCP-клиент, обращающийся к стандартному системному сервису echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Заметим, что это порт 7 TCP – не путать с портом 7 UDP из примера в разделе "Пример программы UDP-клиента"! Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого требуется обратиться, в качестве аргумента командной строки, например: a.out 192.168.253.12 Для того чтобы подчеркнуть, что после установления логического соединения клиент и сервер могут обмениваться информацией неоднократно, клиент трижды запрашивает текст с экрана, отсылает его серверу и печатает полученный ответ. Ниже представлен текст программы. /* Простой пример TCP-клиента для сервиса echo */ #include #include #include #include #include #include #include #include void main(int argc, char **argv) { int sockfd; /* Дескриптор сокета */ int n; /* Количество переданных или прочитанных символов */ int i; /* Счетчик цикла */ char sendline[1000],recvline[1000]; /* Массивы для отсылаемой и принятой строки */ struct sockaddr_in servaddr; /* Структура для адреса сервера */ /* Сначала проверяем наличие второго аргумента в командной строке. При его отсутствии прекращаем работу */ if(argc != 2){ printf("Usage: a.out exit(1); } /* Обнуляем символьные массивы */ bzero(sendline,1000); bzero(recvline,1000); /* Создаем TCP сокет */ if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */ exit(1); } /* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс – из аргумента командной строки, номер порта 7. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(51000); if(inet_aton(argv[1], &servaddr.sin_addr) == 0){ printf("Invalid IP address\n"); close(sockfd); exit(1); } /* Устанавливаем логическое соединение через созданный сокет с сокетом сервера, адрес которого мы занесли в структуру */ if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Три раза в цикле вводим строку с клавиатуры, отправляем ее серверу и читаем полученный ответ */ for(i=0; i<3; i++){ printf("String => "); fflush(stdin); fgets(sendline, 1000, stdin); if( (n = write(sockfd, sendline, strlen(sendline)+1)) < 0){ perror("Can\'t write\n"); close(sockfd); exit(1); } if ( (n = read(sockfd,recvline, 999)) < 0){ perror("Can\'t read\n"); close(sockfd); exit(1); } printf("%s", recvline); } /* Завершаем соединение */ close(sockfd); } Листинг 15-16.3. Программа 15–16-3.c . Простой пример TCP-клиента для сервиса echo. Наберите и откомпилируйте программу. Перед запуском "узнайте у своего системного администратора", запущен ли в системе стандартный TCP-сервис echo и, если нет, попросите это сделать. Запустите программу с запросом к сервису своего компьютера, к сервисам других компьютеров. Если в качестве IP-адреса указать несуществующий адрес или адрес выключенной машины, то программа сообщит об ошибке при работе вызова connect() (правда, возможно, придется подождать окончания timeout’а). При задании адреса компьютера, на котором не работает сервис echo, об ошибке станет известно сразу же. Протокол TCP является надежным протоколом. Если логическое соединение установить не удалось, то отправитель будет знать об этом. Лабораторная работа № 21 (2 часа) Установка ОС WinXP Технические средства Виртуальная машина Подготовка дискового пространства Реальными параметрами, является процессор с частотой не менее 300 Мгц и объем оперативной памяти не менее 128 мегабайт. Немаловажным этапом окажется и предварительная подготовка жесткого диска перед установкой Windows XP. Если имеется операционная система линейки Win9x/ME, оптимальным вариантом будет создание дополнительного раздела для ХР, которая, как и Windows 2000, "умеет" загружаться с логического диска. При отсутствии необходимого программного инструментария для работы с дисковыми разделами (Paragon Partition Manager, Acronis Partiton Expert): в процессе установки Windows XP предложит услуги по созданию нового раздела . Необходимо учесть, что для нужд этой системы придется выделить 1,5 гигабайта на жестком диске. Выбор файловой системы NTFS или FAT32 Кроме этого, ХР позволяет осуществлять форматирование созданного или имеющегося разделов в файловые системы FAT32 или NTFS. Впрочем, конвертация FAT32 в NTFS осуществляется без потери информации посредством встроенной утилиты convert, достаточно открыть меню Выполнить кнопки Пуск, где ввести команду CONVERT Х: /FS:NTFS (в данном случае Х: является разделом с установленной Windows XP). Безусловным преимуществом FAT32 является тот факт, что эта файловая система быстрее и требует меньше памяти для работы. Если система работает только с FAT32, то в память не грузятся драйверы и сервисы, необходимые для NTFS. Еще одно преимущество FAT32 - доступ к диску посредством загрузочной дискеты, созданной средствами Windows 9x/ME. Проблема доступа к NTFS-разделу также решается при помощи соответствующих приложений (пример - российская разработка "NTFS for Win98" от компании Парагон).Однако, главным доводом в пользу NTFS является устойчивость этой файловой системы: NTFS содержит две копии аналога FAT, именуемые MFT (Master File Table). В отличие от FAT, MFT больше напоминает таблицу базы данных. Если оригинал MFT повреждён в случае аппаратной ошибки (bad-сектор), то система при следующей загрузке использует копию MFT, и автоматически создаёт новый оригинал, но уже с учётом повреждений. Помимо этого, NTFS обладает встроенными средствами шифрования файлов, что обеспечивает определённую уверенность в сохранности данных (если система не будет переустанавливаться). Кроме того: в NTFS возможно сжатие отдельных каталогов и файлов, в отличие от DriveSpace, который позволял сжимать только диски целиком. Очень удобная возможность: компрессия "на лету", например, больших графических BMP-файлов, с целью экономии дискового пространства, причём для пользователя этот процесс будет абсолютно "прозрачным". Установка Windows XP Не стоит бояться установки Windows XP: на самом деле все гораздо проще, чем думают некоторые пользователи. Ниже приведены несколько скриншотов с пояснениями для тех, кто впервые устанавливает Windows XP. Для начала следует задать в BIOS приоритет загрузки с CD-ROM, и поместить диск с дистрибутивом ХР в лоток привода. На первом этапе программа установки появится сообщение о начальном копировании необходимых файлов, причем, этот процесс может длиться довольно долго - несколько минут (рис.1). Далее нажимаем ENTER для начала процесса инсталляции системы (рис.2). Следующий шаг принятие Лицензионного Соглашения, (нажатие клавиши F8). Далее необходимо выбрать раздел для установки (можно и на ЛОГИЧЕСКИЙ диск, поскольку Windows 2000/XP загружаются из таких разделов) с последующим форматированием в файловые системы FAT32 или NTFS (рис.3),(рис. 4). |