2011. Машбук (1114722), страница 51
Текст из файла (страница 51)
В частности, имеетсявозможность организации взаимодействия с предварительным установлениемсоединения. Данная модель ориентирована на организацию клиент-серверных систем,когда организуется один серверный узел процессов (обратим внимание, что не процесс, аименно узел процессов), который принимает сообщения от клиентский процессов и ихкак-то обрабатывает. Общая схема подобного взаимодействия представлена ниже(Рис.
101). Заметим, что тип сокета в данном случае не важен, т.е. можно использовать каксокеты, являющиеся виртуальными каналами, так и дейтаграммные сокеты.189Рис. 101. Схема работы с сокетами с предварительным установлением соединения.В данной модели можно выделить две группы процессов: процессы сервернойчасти и процессы клиентской части. На стороне сервера открывается основной сокет.Поскольку необходимо обеспечить возможность другим процессам обращаться ксерверному сокету по имени, то в данном случае необходимо связывание сокета с именем(вызов bind()). Затем серверный процесс переводится в режим прослушивания(посредством системного вызова listen()): это означает, что данный процесс можетпринимать запросы на соединение с ним от клиентских процессов. При этом, в вызовеlisten() оговаривается очередь запросов на соединение, которая может формироваться кданному процессу серверной части.Каждый клиентский процесс создает свой сокет.
Заметим, что на стороне клиентасвязывать сокет необязательно (поскольку сервер может работать с любым клиентом, вчастности, и с «анонимным» клиентом). Затем клиентский процесс может передатьсерверному процессу сообщение, что он с ним хочет соединиться, т.е. передать запрос насоединение (посредством системного вызова connect()). В данном случае возможны триальтернативы.Во-первых, может оказаться, что клиент обращается к connect() до того, как серверперешел в режим прослушивания. В этом случае клиент получает отказ с уведомлением,что сервер в данный момент не прослушивает сокет.Во-вторых, клиент может обратиться к connect(), а у серверного процесса в данныймомент сформирована очередь необработанных запросов, и эта очередь пока не насыщена.В этом случае запрос на соединение встанет в очередь, и клиентский процессзаблокируется и будет ожидать обслуживания.И, наконец, в-третьих, может оказаться, что указанная очередь переполнена.
В этомслучае клиент получает отказ с соответствующим уведомлением, что сервер в данныймомент занят.Итак, данная схема организована таким образом, что к одному серверному узлуможет быть множество соединений. Для организации этой модели имеется системныйвызов accept(), который работает следующим образом.
При обращении к данномусистемному вызову, если в очереди имеется необработанная заявка на соединение,создается новый локальный сокет, который связывается с клиентом. После этого клиентможет посылать сообщения серверу через этот новый сокет. А сервер, в свою очередь,получая через данный сокет сообщения от клиента, «понимает», от какого клиентапришло это сообщение (т.е.
клиент получает некоторое внутрисистемное именование,190даже если он не делал связывание своего сокета). И, соответственно, в этом случае серверможет посылать ответные сообщения клиенту.Завершение работы состоит из двух шагов. Первый шаг заключается в отключениидоступа к сокету посредством системного вызова shutdown().
Можно закрыть сокет начтение, на запись, на чтение-запись. Тем самым системе передается информация, чтосокет более не нужен. С помощью этого вызова обеспечивается корректное прекращениеработы с сокетом (в частности, это важно при организации виртуального канала, которыйдолжен гарантировать доставку посланных данных). Второй шаг заключается в закрытиисокета с помощью системного вызова close() (т.е. закрытие сокета как файла).Далее эту концептуальную модель можно развивать.
Например, сервер можетпорождать сыновний процесс для каждого вызова accept(), и тогда все действия по работес данным клиентом ложатся на этот сыновний процесс. На родительский процессвозлагается задача прослушивание «главного» сокета и обработка поступающих запросовна соединение. Вот почему речь идет не об одном процессе сервере, а о серверном узле, вкотором может находиться целый набор процессов.Теперь рассмотрим модель сокетов без предварительного соединения (Рис. 102). Вэтой модели используются лишь дейтаграммные сокеты. В отличие от предыдущеймодели, которая была иерархически организованной, то эта модель обладаетпроизвольной организацией взаимодействия. Это означает, что в данной модели укаждого взаимодействующего процесса имеется сокет, через который он может получатьинформацию от различных источников, в отличие от предыдущей модели, где имеется«главный» известный всем клиентам сокет сервера, через который неявно передаетсяуправляющая информация (заказы на соединение), а затем с каждым клиентомсвязывается один локальный сокет.
Этот механизм позволяет серверу взаимодействовать склиентом, не зная его имя явно. В текущей модели ситуация симметричная: любойпроцесс через свой сокет может послать информацию любому другому сокету. Этоозначает, что механизм отправки имеет соответствующую адресную информацию (т.е.информацию об отправителе и получателе).Рис. 102.
Схема работы с сокетами без предварительного установления соединения.Поскольку в данной модели используются дейтаграммные сокеты, тонеобходимость обращаться к вызову shutdown() отпадает: в этой модели сообщенияпроходят (и уходят) одной порцией данных, поэтому можно сразу закрывать сокетпосредством вызова close().191Пример. Работа с локальными сокетами.Рассмотрим небольшой пример, иллюстрирующий работу с сокетами в рамкахлокального домена (AF_UNIX). Ниже приведена небольшая программа, которая взависимости от параметра командной строки исполняет роль клиента или сервера. Клиенти сервер устанавливают соединение с использованием датаграммных сокетов.
Клиентчитает строку со стандартного ввода и пересылает серверу; сервер посылает ответ взависимости от того, какова была строка. При введении строки «quit» и клиент, и серверзавершаются.#include <sys/types.h>#include <sys/socket.h>#include <sys/un.h>#include <stdio.h>#include <string.h>#define SADDRESS "mysocket"#define CADDRESS "clientsocket"#define BUFLEN 40int main(int argc, char **argv){struct sockaddr_un party_addr, own_addr;int sockfd;int is_server;char buf[BUFLEN];int party_len;int quitting;if (argc != 2) {printf("Usage: %s client|server.\n", argv[0]);return 0;}quitting = 1;/* определяем, кто мы: клиент или сервер*/is_server = !strcmp(argv[1], "server");memset(&own_addr, 0, sizeof(own_addr));own_addr.sun_family = AF_UNIX;strcpy(own_addr.sun_path,is_server?SADDRESSCADDRESS);:/* создаем сокет */if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0){printf("can't create socket\n");return 0;}/* связываем сокет */unlink(own_addr.sun_path);if(bind(sockfd,(structsockaddr*)&own_addr,sizeof(own_addr.sun_family)+ strlen(own_addr.sun_path))< 0){printf("can't bind socket!");192return 0;}if (!is_server){/* это – клиент */memset(&party_addr, 0, sizeof(party_addr));party_addr.sun_family = AF_UNIX;strcpy(party_addr.sun_path, SADDRESS);printf("type the string: ");while (gets(buf)) {/* не пора ли выходить? */quitting = (!strcmp(buf, "quit"));/* считали строку и передаем ее серверу */if (sendto(sockfd, buf, strlen(buf) + 1, 0,(structsockaddr*)&party_addr,sizeof(party_addr.sun_family) +strlen(SADDRESS)) != strlen(buf) + 1){printf("client:errorwritingsocket!\n");return 0;}/*получаем ответ и выводим его на печать*/if (recvfrom(sockfd, buf, BUFLEN, 0, NULL, 0)< 0){printf("client:errorreadingsocket!\n");return 0;}printf("client: server answered: %s\n", buf);if (quitting) break;printf("type the string: ");} // whileclose(sockfd);return 0;} // if (!is_server)/* это – сервер */while (1){/* получаем строку от клиента и выводим на печать*/party_len = sizeof(party_addr);if(recvfrom(sockfd,buf,BUFLEN,0,(structsockaddr *) &party_addr, &party_len) < 0){printf("server: error reading socket!");return 0;193}printf("server: received from client: %s \n",buf);/* не пора ли выходить? */quitting = (!strcmp(buf, "quit"));if (quitting)strcpy(buf, "quitting now!");elseif (!strcmp(buf, "ping!"))strcpy(buf, "pong!");elsestrcpy(buf, "wrong string!");/* посылаем ответ */if (sendto(sockfd, buf, strlen(buf) + 1, 0,(struct sockaddr *) &party_addr,party_len)!= strlen(buf)+1){printf("server: error writing socket!\n");return 0;}if (quitting) break;} // whileclose(sockfd);return 0;}Пример.
Работа с сокетами в рамках сети.В качестве примера работы с сокетами в домене AF_INET напишем простенькийweb-сервер, который будет понимать только одну команду :GET /<имя файла>Сервер запрашивает у системы сокет, связывает его с адресом, считающимсяизвестным, и начинает принимать клиентские запросы. Для обработки каждого запросапорождается отдельный потомок, в то время как родительский процесс продолжаетпрослушивать сокет. Потомок разбирает текст запроса и отсылает клиенту либосодержимое требуемого файла, либо диагностику (“плохой запрос” или “файл ненайден”).#include <sys/types.h>#include <sys/socket.h>#include <sys/stat.h>#include <netinet/in.h>#include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#define PORTNUM 8080#define BACKLOG 5#define BUFLEN 80#define FNFSTR "404 Error File Not Found "#define BRSTR "Bad Request "194int main(int argc, char **argv){struct sockaddr_in own_addr, party_addr;int sockfd, newsockfd, filefd;int party_len;char buf[BUFLEN];int len;int i;/* создаем сокет */if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){printf("can't create socket\n");return 0;}/* связываем сокет */memset(&own_addr, 0, sizeof(own_addr));own_addr.sin_family = AF_INET;own_addr.sin_addr.s_addr = INADDR_ANY;own_addr.sin_port = htons(PORTNUM);if (bind(sockfd, (struct sockaddr *) &own_addr,sizeof(own_addr)) < 0){printf("can't bind socket!");return 0;}/* начинаем обработку запросов на соединение */if (listen(sockfd, BACKLOG) < 0){printf("can't listen socket!");return 0;}while (1) {memset(&party_addr, 0, sizeof(party_addr));party_len = sizeof(party_addr);/* создаем соединение */if ((newsockfd = accept(sockfd, (struct sockaddr*)&party_addr, &party_len)) < 0){printf("error accepting connection!");return 0;}if (!fork()){/*это – сын, он обрабатывает запрос ипосылает ответ*/close(sockfd);/* этот сокет сыну не нужен*/if ((len = recv(newsockfd, &buf, BUFLEN, 0))< 0){195printf("error reading socket!");return 0;}/* разбираем текст запроса */printf("received: %s \n", buf);if (strncmp(buf, "GET /", 5)){/*плохой запрос!*/if (send(newsockfd, BRSTR, strlen(BRSTR)+ 1, 0) != strlen(BRSTR) + 1){printf("error writing socket!");return 0;}shutdown(newsockfd, 1);close(newsockfd);return 0;}for (i=5; buf[i] && (buf[i] > ' '); i++);buf[i] = 0;/* открываем файл */if ((filefd = open(buf+5, O_RDONLY)) < 0){/* нет файла! */if(send(newsockfd,FNFSTR,strlen(FNFSTR) + 1, 0) != strlen(FNFSTR)+ 1){printf("error writing socket!");return 0;}shutdown(newsockfd, 1);close(newsockfd);return 0;}/* читаем из файла порции данных и посылаемих клиенту */while (len = read(filefd, &buf, BUFLEN))if (send(newsockfd, buf, len, 0) < 0) {printf("error writing socket!");return 0;}close(filefd);shutdown(newsockfd, 1);close(newsockfd);return 0;}/* процесс – отец.
Он закрывает новый сокет ипродолжает прослушивать старый */196close(newsockfd);}}19744.1Файловые системыОсновные концепцииПод файловой системой (ФС) будем понимать часть операционной системы,представляющую собой совокупность организованных наборов данных, хранящихся навнешних запоминающих устройствах, и программных средств, гарантирующихименованный доступ к этим данным и их защиту.С точки зрения пользователя, файловая система является первым виртуальнымресурсом (который появился в операционных системах), достаточно понятным идостаточно просто используемым во время работы пользователя за компьютером.