организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2. |
Семейство протоколов TCP/IP. Сокеты (sockets) в UNIX и основы работы с ними
Системный вызов listen()
Системный вызов listen() является первым из еще неизвестных нам вызовов, применяемым на TCP–сервере. В его задачу входит перевод TCP–сокета в пассивное (слушающее) состояние и создание очередей для порождаемых при установлении соединения присоединенных сокетов, находящихся в состоянии не полностью установленного соединения и полностью установленного соединения. Для этого вызов имеет два параметра: дескриптор TCP–сокета и число, определяющее глубину создаваемых очередей.
Последний параметр на разных UNIX-подобных операционных системах и даже на разных версиях одной и той же системы может иметь различный смысл. Где-то это суммарная длина обеих очередей, где-то он относится к очереди не полностью установленных соединений (например, Linux до версии ядра 2.2) где-то – к очереди полностью установленных соединений (например, Linux, начиная с версии ядра 2.2), где-то – вообще игнорируется.
Системный вызов accept()
Системный вызов accept() позволяет серверу получить информацию о полностью установленных соединениях. Если очередь полностью установленных соединений не пуста, то он возвращает дескриптор для первого присоединенного сокета в этой очереди, одновременно удаляя его из очереди. Если очередь пуста, то вызов ожидает появления полностью установленного соединения. Системный вызов также позволяет серверу узнать полный адрес клиента, установившего соединение. У вызова есть три параметра: дескриптор слушающего сокета, через который ожидается установление соединения; указатель на структуру, в которую при необходимости будет занесен полный адрес сокета клиента, установившего соединение; указатель на целую переменную, содержащую максимально допустимую длину этого адреса. Как и в случае вызова recvfrom() , последний параметр является модернизируемым, а если нас не интересует, кто с нами соединился, то вместо второго и третьего параметров можно указать значение NULL.
Пример простого TCP-сервера
Рассмотрим программу 15–16-4.c, реализующую простой TCP-сервер для сервиса echo.
/* Пример простого TCP-сервера для сервиса 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> void main() { int sockfd, newsockfd; /* Дескрипторы для слушающего и присоединенного сокетов */ int clilen; /* Длина адреса клиента */ int n; /* Количество принятых символов */ char line[1000]; /* Буфер для приема информации */ struct sockaddr_in servaddr, cliaddr; /* Структуры для размещения полных адресов сервера и клиента */ /* Создаем TCP-сокет */ if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); exit(1); } /* Заполняем структуру для адреса сервера: семейство протоколов 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); /* Настраиваем адрес сокета */ if(bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Переводим созданный сокет в пассивное (слушающее) состояние. Глубину очереди для установленных соединений описываем значением 5 */ if(listen(sockfd, 5) < 0){ perror(NULL); close(sockfd); exit(1); } /* Основной цикл сервера */ while(1){ /* В переменную clilen заносим максимальную длину ожидаемого адреса клиента */ clilen = sizeof(cliaddr); /* Ожидаем полностью установленного соединения на слушающем сокете. При нормальном завершении у нас в структуре cliaddr будет лежать полный адрес клиента, установившего соединение, а в переменной clilen – его фактическая длина. Вызов же вернет дескриптор присоединенного сокета, через который будет происходить общение с клиентом. Заметим, что информация о клиенте у нас в дальнейшем никак не используется, поэтому вместо второго и третьего параметров можно было поставить значения NULL. */ if((newsockfd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen)) < 0){ perror(NULL); close(sockfd); exit(1); } /* В цикле принимаем информацию от клиента до тех пор, пока не произойдет ошибки (вызов read() вернет отрицательное значение) или клиент не закроет соединение (вызов read() вернет значение 0). Максимальную длину одной порции данных от клиента ограничим 999 символами. В операциях чтения и записи пользуемся дескриптором присоединенного сокета, т. е. значением, которое вернул вызов accept().*/ while((n = read(newsockfd, line, 999)) > 0){ /* Принятые данные отправляем обратно */ if((n = write(newsockfd, line, strlen(line)+1)) < 0){ perror(NULL); close(sockfd); close(newsockfd); exit(1); } } /* Если при чтении возникла ошибка – завершаем работу */ if(n < 0){ perror(NULL); close(sockfd); close(newsockfd); exit(1); } /* Закрываем дескриптор присоединенного сокета и уходим ожидать нового соединения */ close(newsockfd); } }Листинг 15-16.4. Программа 15–16-4.c . Пример простого TCP-сервера для сервиса echo.
Наберите и откомпилируйте программу. Запустите ее на выполнение. Модифицируйте текст программы TCP-клиента (программа 15–16-3.c ), заменив номер порта с 7 на 51000. Запустите клиента с другого виртуального терминала или с другого компьютера и убедитесь, что клиент и сервер взаимодействуют корректно.