lab11
.docxМИНОБРНАУКИ РОССИИ
Санкт-Петербургский государственный
электротехнический университет
«ЛЭТИ» им. В.И. Ульянова (Ленина)
Кафедра вычислительной техники
Отчет по лабораторной работе №11
по дисциплине «Организация процессов и программирование в среде Linux»
Студент гр. |
|
Преподаватель |
Разумовский Г.В. |
Тема: Взаимодействие процессов
через сокеты
Цель работы: знакомство с механизмом взаимодействия процессов через сокеты.
Задание:
Написать две программы (сервер и клиент) , которые обмениваются сообщениями через потоковые сокеты. Клиенты проверяют возможность соединения с сервером и в случае отсутствия соединения или истечения времени ожидания отправки сообщения завершают работу. После соединения с сервером они генерируют случайную последовательность чисел и выводят ее на экран, а затем отсылают серверу. Сервер в течение определенного времени ждет запросы от клиентов и в случае их отсутствия завершает работу. При поступлении запроса от клиента сервер порождает обслуживающий процесс, который принимает последовательность чисел, упорядочивает ее и выводит на экран, а затем отсылает обратно клиенту и завершают работу. Клиент полученную последовательность выводит на экран и заканчивает свою работу.
Ход работы
Программа server.cpp
//может быть только 1 сервер
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
//Вопросы от Разумовского:
//какие параметры всего? сколько времени я жду перед тем, как включиться?
int main(int argc, char* argv[])
{
if (argc != 2){
cout << "Должно быть введено время ожидания: ./server {sec}\n";
exit(0);
}
// Инициализация и подготовка
int socket_fd; // дескриптор сокета-приемника, получающего сокет, желающий установить соединение с сервером
int socket_listener; // сокет-"слушатель", который слушает все запросы к серверу
char socket_client_name[255];
fd_set fds; // набор дескрипторов
pid_t process_id; // id нового процесса
sockaddr_in socket_address_in; //структура с адресом сервера (IP)
timeval time_value; // структура с максимальным временем ожидания послания сообщения
const int MORBING_TIME = atoi(argv[1]);
// ---------- Создание сокета ----------
// создание сокета-слушателя, который принимает все запросы к серверу
// идет установка порта, протокола IPv4 и адреса
// затем привязать к ним настройки(порта, протокола IPv4 и адреса)
// чтобы остальные могли идентифицировать его сейчас (до этого у нас не было точного его адреса,
// и остальные не могли знать, как к нему подключиться)
socket_listener = socket(AF_INET, SOCK_STREAM, 0); // создание принимающего сокета
if(socket_listener < 0)
{
perror("Ошибка создания сокета");
exit(1);
}
socket_address_in.sin_family = AF_INET; // сетевое взаимодействие по протоколу IPv4
socket_address_in.sin_port = htons(3434); // номер порта в сетевом порядке байт
socket_address_in.sin_addr.s_addr = inet_addr("127.0.0.1"); // "127.0.0.1" - это "localhost"
//Привязка сокета к IP-адресу и номеру порта
if(bind(socket_listener, (sockaddr*)&socket_address_in, sizeof(socket_address_in)) < 0) // привязка к сетевому адресу
{
perror("Ошибка соединения с сетью");
exit(2);
}
// ---------- Получение сообщения от клиентов и отправка к ним сообщения ----------
// идет установка тайм-аута для ожидания любых запросов от клиентов,
// каждый раз когда мы устанавливаем новое соединение, тайм-аут устанавливается в 0,
// отсчитываем 15 секунд,
// затем устанавливаем максимальное кол-во соединений для сервера (длина очереди - 10),
// а уже потом мы ожидаем какие-либо запросы от сокета-слушателя, принимаем его с сокетом-принимателем
// и запустить новый процесс, который обрабатывает сообщение и отправляет его в обратном направлении
time_value.tv_sec = MORBING_TIME;
time_value.tv_usec = 0;
listen(socket_listener, 10); // создание очереди для привязанных сокетов
while(true) // ожидание сокета
{
FD_ZERO(&fds); // очищаем набор дескрипторов
FD_SET(socket_listener, &fds); // добавляем наши дескрипторы в набор
time_value.tv_sec = MORBING_TIME; // это здесь нужно, т.к. потом он передается по указателю
time_value.tv_usec = 0;
//мониторинг файловых дескрипторов, чтобы найти готовые к операциям ввода-вывода, вернуть количество файловых дескрипторов, содержащихся в трех возвращенных наборах дескрипторов/0, если тайм-аут, если успех, -1, если ошибка
// select(количество запрошенных дескрипторов, проверка готовности к чтению, время)
// возвращает число сокетов, от которых поступила информация (если 0, время ожидания истекло)
if(select(FD_SETSIZE, &fds, NULL, NULL, &time_value) == 0)
{
cout << "---------- Время ожидания запроса от клиента вышло ----------\n";
close(socket_fd);
exit(1);
}
else
{
// Возвращает дескриптор сокета для обмена с клиентом, или -1 в случае ошибки
socket_fd = accept(socket_listener, NULL, NULL);
if(socket_fd < 0)
{
perror("Ошибка установки соединения");
exit(1);
}
sprintf(socket_client_name, "%d", socket_fd); // все агрументы execl должны быть const char*
process_id = fork();
if(process_id == 0)
execl("executable", " ", socket_client_name, NULL);
}
}
// ---------- Уничтожение ----------
if (process_id != 0) // если это НЕ новый процесс, закрываем ЕГО сокет
close(socket_listener);
return 0;
}
Программа executable.cpp
// Обслуживающая программа для сервера
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
// Подготовка
int buffer_len; // размер локального буфера
int local_socket_fd = atoi(argv[1]); // дескриптор клиентского сокета
char subsequence_buf[10]; // локальный буфер для отправляемого сообщения
// ---------- Получение сообщения от клиентов и последовательности обработчиков и отправка сообщения клиентам ----------
// Читаем сообщение от клиента (которое было на сервере, но мы разветвили процесс, поэтому оно у нас есть),
// затем мы сортируем цифры в нем,
// после чего мы отправляем его обратно (мы получили клиентский адрес в качестве АРГУМЕНТА)
// (откуда было получено сообщение, туда же его и отправили)
timeval time_value;
time_value.tv_sec = 5;
//Если через 5 секунд подсервер не получит сообщение, то он прекращает работу
//Программа не будет прекращаться в ином случае (ошибка сообщения и прочее)
time_value.tv_usec = 0;
setsockopt(local_socket_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&time_value, sizeof(time_value)); // установка параметров для сокета-получателя
buffer_len = recv(local_socket_fd, subsequence_buf, 10, 0); // получение необработанного сообщения
cout << "---------- Сообщение \"" << subsequence_buf << "\" было получено ----------\n";
// в третьем задании нам нужно получить последовательность цифр,
// отсортировать ее, затем вернуть клиенту отсортированную последовательность
// например, "4278600937" --> "0023467789"
sort(subsequence_buf, subsequence_buf + sizeof(subsequence_buf));
if (send(local_socket_fd, subsequence_buf, buffer_len, 0) > 0)
cout << "---------- Сообщение \"" << subsequence_buf << "\" было отправлено ----------\n\n";
//отправка обработанного сообщения (от клиента к серверу к подсерверу к клиенту)
//Мы отправляем сразу клиенту по local_socket_fd (в обход сервера, так удобнее)
//это - сокет сервера, который он принимает от клиента
else cout << "---------- Ошибка отправки сообщения (клиенту) ----------\n\n";
// ---------- Уничтожение ----------
close(local_socket_fd);
exit(0);
}
Программа client.cpp
//Клиентов может быть много
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 2){
cout << "Должно быть введено время ожидания: ./client {sec}\n";
exit(0);
}
// Подготовка
int socket_fd; // дескриптор сокета клиента
int is_connected = 0; // подключен ли клиент к сокету
int begin_time = 0; // время начала отчета
char message_send[10]; // отправляемое сообщение от клиента к серверу
char message_receive[10]; // обработанное полученное сообщение (от сервера к клиенту)
fd_set readfds; // набор дескрипторов
sockaddr_in socket_address_in; // структура с адресом сервера
timeval time_value; // структура с максимальным временем ожидания отправки сообщения
const int MORBING_TIME = atoi(argv[1]);
// ---------- Создание сокета ----------
// создается сокет, он будет отправлен на сервер
// установка порта соединения, протокола IPv4 и адреса для находжения сервера
// такие же настройки должны быть в сервере
srand(time(NULL));
/*
создает сокет (домен, протокол), в случае успеха возвращает дескриптор файла для нового сокета
AF_INET -- для взаимодействия через сеть по протоколу TCP/IP (IPv4)
SOCK_STREAM -- потоковый сокет
*/
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd < 0)
{
perror("Ошибка создания сокета");
exit(1);
}
socket_address_in.sin_family = AF_INET; // взаимодействие с сетью по протоколу IPv4
socket_address_in.sin_port = htons(3434); // номер порта в сетевом порядке байт
socket_address_in.sin_addr.s_addr = inet_addr("127.0.0.1"); // "127.0.0.1" - это "localhost"
// ---------- Ожидание взаимодействия с сервером ----------
begin_time = time(NULL);
cout << "---------- Ожидание соединения с сервером (" << MORBING_TIME << " секунд) ----------\n";
while ((time(NULL) - begin_time) < MORBING_TIME
&& (is_connected = connect(socket_fd, (sockaddr*)&socket_address_in, sizeof(socket_address_in))) < 0)
{} // просто ждем 15 секунд, пока не будет соединения с сервером
if (is_connected == -1)
{
cout << "---------- Не удалось соединиться с сервером ----------\n";
close(socket_fd);
exit(-1);
}
// ---------- Создание последовательности и отправка сообщения на сервер ----------
// Создаем случайную последовательность из 10 цифр,
// устанавливает время тайм-аута,
// отправляем сообщение и выводим его в терминал
for (int i = 0; i < 10; i++)
message_send[i] = '0' + rand()%10;
//sprintf(message_send, "%lu", rand()%9000000000 + 1000000000); // случайное число из 10 цифр
// время ожидания
time_value.tv_sec = MORBING_TIME;
time_value.tv_usec = 0;
/*
установка параметров сокета
SOL_SOCKET -- задает параметры на уровне сокета
SO_SNDTIMEO -- задает значение тайм-аута для сокета
*/
setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&time_value, sizeof(time_value)); // задаем параметры сокета перед отправкой
send(socket_fd, message_send, sizeof(message_send), 0); // отправка сообщения на сервер
cout << "---------- Сообщение \"" << message_send <<"\" отправлено ----------\n";
// ---------- Получение сообщения от сервера ----------
// ожидаем запрос от сервера,
// устанавливаем тайм-аут на получение сообщения и выводим полученное сообщение
FD_ZERO(&readfds); // очищаем набор дескрипторов
FD_SET(socket_fd, &readfds); // добавление дескриптора в набор
time_value.tv_sec = MORBING_TIME;
time_value.tv_usec = 0;
// select(количество запрошенных дескрипторов, проверка готовности к чтению, время)
// возвращает число сокетов, от которых поступила информация (если 0, время ожидания истекло)
if(select(FD_SETSIZE, &readfds, NULL, NULL, &time_value) == 0)
cout << "---------- Время ожидания вышло ----------\n";
else
{
recv(socket_fd, message_receive, sizeof(message_receive), 0); //получение сообщения
cout << "---------- Сообщение \"" << message_receive << "\" получено ----------\n";
}
// ---------- Уничтожение ----------
close(socket_fd);
return 0;
}
Демонстрация результата программы
Как видно из кода программы, если на вход серверу и клиенту не задать время ожидания, то программы запускаться не будут
Санкт-Петербург
2022