Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Готовые отчеты / ОСиС. Лабораторная работа 9

.pdf
Скачиваний:
8
Добавлен:
21.11.2020
Размер:
1.08 Mб
Скачать

Федеральное агентство связи ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ

ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ. ПРОФ. М. А. БОНЧ-БРУЕВИЧА» (СПбГУТ)

Факультет инфокоммуникационных сетей и систем Кафедра программной инженерии и вычислительной техники

ЛАБОРАТОРНАЯ РАБОТА №9 по дисциплине «Операционные системы и сети»

на тему «Разработка кросс-платформенной программы с использованием POSIX функций»

Выполнил: студент 3-го курса дневного отделения группы ИКПИ-85

Коваленко Леонид Александрович Преподаватель:

доцент кафедры ПИиВТ Дагаев Александр Владимирович

Санкт-Петербург 2020

Цель работы Разработать кросс-платформенную программу с использованием

POSIX функций.

Постановка задачи

Написать кросс-платформенную программу на C с использованием POSIX функций и продемонстрировать ее работу.

Ход работы

Работа выполняется в операционной системе Linux Debian.

Стандарт POSIX описывает множество базовых, системных сервисов, необходимых для функционирования прикладных программ. Цель POSIX — сделать приложения мобильными на уровне исходного кода. Это значит, в частности, что при переносе C-программ на другую операционную платформу потребуется всего лишь перекомпиляция.

Напишем кросс-платформенную программу на C с использованием только POSIX функций.

Суть программы будет заключаться в том, чтобы несколько процессовклиентов могли обмениваться сообщениями с процессом-сервером посредством сокетов (табл. 1, 2).

Таблица 1 — Файл server.c

#include <arpa/inet.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #define PORT 8080 #define BUFSIZE 512 #define CONNECTIONS 4

//Структура для потока записи typedef struct {

pthread_t *read_threads; int *sockets;

} WriteThreadData;

//Структура для потоков чтения typedef struct {

int user_id;

int *socket, *allow; } ReadThreadData;

//Функция для потока записи void *runWriteThread(void *arg) {

2

WriteThreadData *data = (WriteThreadData *)arg; char buffer[BUFSIZE], temp[BUFSIZE];

while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка printf("#> ");

fgets(buffer, BUFSIZE, stdin); // Ввод

//Если ввели 'list', то выводим список пользователей if (strncmp(buffer, "list", sizeof(char) * 4) == 0) {

printf("Клиенты: ");

for (int i = 0; i < CONNECTIONS; ++i) if (data->sockets[i] != -1)

printf("%d ", i); printf("\n"); fflush(stdout); continue;

}

//Если ввели stop X, то прерываем соединение с клиентом X if (strncmp(buffer, "stop", sizeof(char) * 4) == 0) {

int user_id = atoi(buffer + 5); pthread_cancel(data->read_threads[user_id]); close(data->sockets[user_id]); data->sockets[user_id] = -1;

printf("Соединение с клиентом %d прервано\n", user_id); fflush(stdout);

continue;

}

//Если ввели exit, то завершаем работу сервера

if (strncmp(buffer, "exit", sizeof(char) * 4) == 0) { // Освобождаем ресурсы

for (int i = 0; i < CONNECTIONS; ++i) { if (data->sockets[i] != -1) {

pthread_cancel(data->read_threads[i]); close(data->sockets[i]);

}

}

printf("Конец работы\n"); _exit(0);

return NULL;

}

// Если введено @X <message>, то отправляем сообщение пользователю X if (*buffer == '@') {

int user_id;

sscanf(buffer + 1, "%d %[^\n]s", &user_id, temp); strcat(temp, "\n");

if (data->sockets[user_id] == -1) { printf("Клиента %d не существует\n", user_id);

}else if (send(data->sockets[user_id], temp, strlen(temp), 0) <=

0){

pthread_cancel(data->read_threads[user_id]); close(data->sockets[user_id]); data->sockets[user_id] = -1;

printf("Клиент #%d прервал соединение\n", user_id); fflush(stdout);

}

} else { // Иначе отправляем всем

for (int i = 0; i < CONNECTIONS; ++i) { if (data->sockets[i] != -1 &&

send(data->sockets[i], buffer, strlen(buffer), 0) <= 0) { pthread_cancel(data->read_threads[i]); close(data->sockets[i]);

data->sockets[i] = -1;

printf("Клиент #%d прервал соединение\n", i); fflush(stdout);

}

}

3

}

}

}

// Функция для потока чтения void *runReadThread(void *arg) {

ReadThreadData data = *(ReadThreadData *)arg; *data.allow = 1;

char buffer[BUFSIZE]; while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка

if (recv(*data.socket, buffer, BUFSIZE - 1, 0) <= 0) { close(*data.socket);

*data.socket = -1;

printf("\nКлиент #%d прервал соединение\n#> ", data.user_id); fflush(stdout);

return NULL;

}

printf("\nКлиент %d: %s#> ", data.user_id, buffer); fflush(stdout);

}

}

int main() {

struct sockaddr_in address;

int socket_descriptor = 0, option = 1, new_socket_descriptor, address_length = sizeof(address);

// Создание конечной точки соединения

if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == 0) { printf("Ошибка создания конечной точки соединения\n"); perror("socket");

return 1;

}

// Установка флагов

if (setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,

&option, sizeof(option))) { printf("Ошибка установки флагов на сокете\n"); perror("setsockopt");

return 2;

}

address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT);

// Привязка имени к сокету

if (bind(socket_descriptor, (struct sockaddr *)&address, sizeof(address))

<

0) {

printf("Ошибка привязки имени к сокету\n"); perror("bind");

return 3;

}

//Задание размера очереди в число CONNECTIONS if (listen(socket_descriptor, CONNECTIONS) < 0) {

printf("Ошибка прослушивания соединения на сокете\n"); perror("listen");

return 4;

}

//Настройка потока записи и др.

pthread_t read_threads[CONNECTIONS]; int sockets[CONNECTIONS];

memset(sockets, -1, sizeof(int) * CONNECTIONS); WriteThreadData write_thread_data = {read_threads, sockets}; pthread_t write_thread_descriptor; pthread_create(&write_thread_descriptor, NULL, runWriteThread,

4

&write_thread_data); int user_id = 0;

// Осуществление соединений while (1) {

// Ожидание соединения

if ((new_socket_descriptor =

accept(socket_descriptor, (struct sockaddr *)&address, (socklen_t *)&address_length)) < 0) {

printf("Ошибка принятия соединения на сокете\n"); perror("accept");

return 5;

}

//Поиск в базе клиентов свободного слота for (int i = 0; i < CONNECTIONS; ++i)

if (sockets[i] == -1) { user_id = i; break;

}

sockets[user_id] = new_socket_descriptor; printf("\nНовый клиент #%d присоединился\n#> ", user_id); fflush(stdout);

//Создание потока для чтения без окончания его ожидания int allow = 0;

ReadThreadData data = {user_id, &sockets[user_id], &allow}; pthread_create(&read_threads[user_id], NULL, runReadThread, &data); while (allow == 0)

continue;

}

return 0;

}

Таблица 2 — Файл client.c

#include <arpa/inet.h> #include <pthread.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #define PORT 8080 #define BUFSIZE 512

//Структура для потоков записи и чтения typedef struct {

int socket;

pthread_t *read_thread, *write_thread; } ThreadsData;

//Функция для потока записи

void *runWriteThread(void *arg) { ThreadsData *data = (ThreadsData *)arg; char buffer[BUFSIZE];

while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка printf("#> ");

fgets(buffer, BUFSIZE, stdin); // Ввод

// Если ввели exit, то завершаем работу клиента

if (strncmp(buffer, "exit", sizeof(char) * 4) == 0) { // Освобождаем ресурсы pthread_cancel(*data->read_thread); close(data->socket);

return NULL;

}

// Отправляем сообщение

if (send(data->socket, buffer, strlen(buffer), 0) <= 0) {

5

// Освобождаем ресурсы, если ошибка записи pthread_cancel(*data->read_thread); close(data->socket);

return NULL;

}

}

}

// Функция для потока чтения void *runReadThread(void *arg) {

ThreadsData *data = (ThreadsData *)arg; char buffer[BUFSIZE];

while (1) {

memset(buffer, '\0', BUFSIZE); // Очистка // Читаем сообщение

if (recv(data->socket, buffer, BUFSIZE - 1, 0) <= 0) { // Освобождаем ресурсы, если ошибка чтения pthread_cancel(*data->write_thread); close(data->socket);

return NULL;

}

printf("\nСервер: %s#> ", buffer); fflush(stdout);

}

}

int main() {

int socket_descriptor = 0; struct sockaddr_in serv_addr;

// Создание конечной точки соединения

if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Ошибка создания конечной точки соединения\n"); perror("socket");

return 1;

}

serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT);

// Преобразование адреса из текста в двоичную форму

if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { printf("Неверный адрес либо адрес не поддерживается\n"); perror("inet_pton");

return 2;

}

// Инициализация соединения на сокете

if (connect(socket_descriptor, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {

printf("Соединение не удалось установить\n"); perror("connect");

return 3;

}

// Создание потоков для чтения и записи

pthread_t read_thread_descriptor, write_thread_descriptor; ThreadsData socketThreadsData = {socket_descriptor,

&read_thread_descriptor,

&write_thread_descriptor}; printf("Отправьте сообщения / прервите соединение (команда exit)\n"); pthread_create(&write_thread_descriptor, NULL, runWriteThread,

&socketThreadsData); pthread_create(&read_thread_descriptor, NULL, runReadThread,

&socketThreadsData); pthread_join(write_thread_descriptor, NULL); pthread_join(read_thread_descriptor, NULL); return 0;

}

6

Запустим процесс-сервер и затем процесс-клиент (рис. 1).

Рисунок 1 — Процесс-сервер слева и процесс-клиент справа Сервер идентифицирует клиентов при помощи уникальных чисел

(неслучайных, начиная с 0). Клиент об идентификаторе не знает. Он просто отправляет сообщения серверу и прерывает соединение по желанию.

Сервер в нашем случае может обслуживать не более 4 клиентов. Хотя данное значение можно спокойно поменять в строке «#define CONNECTIONS 4». Никаких вспомогательных действий для этого не потребуется.

Отправим одно сообщение от клиента серверу, а затем от сервера клиенту (рис. 2).

Рисунок 2 — Сервер и единственный клиент обмениваются сообщениями Теперь запустим ещё два процесса-клиента (рис. 3).

Рисунок 3 — Ещё два процесса-клиента подключились Теперь отправим сообщения процессу-серверу от этих двух процессов-

клиентов (рис. 4).

7

Рисунок 4 — Два процесса отправили сообщения серверу Так как клиент чаще всего требует к себе индивидуального подхода,

можно отправлять сообщения процессам-клиентам отдельно. Для этого используется шаблон: @X <message> (X — идентификатор клиента). Отправим каждому клиенту индивидуальное сообщение, а затем общее (рис. 5).

Рисунок 5 — Отправка индивидуальных сообщений и одного общего Список клиентов в процессе-сервере можно увидеть, введя list (рис. 6)

Рисунок 6 — Работа команды list

Клиенты могут прервать соединение либо при помощи команды exit,

либо одним из стандартных способов закрытия процесса (Ctrl+C или др.). 8

Кроме того, сервер сам может прервать соединение с клиентом при помощи команды stop X (X — идентификатор клиента). Далее первый вариант показан на примере клиента #0, второй вариант на примере клиента #1, третий на примере клиента #2 (рис. 7).

Рисунок 7 — Проверка работы способов прерывания соединения Можно снова запустить все три процесса-клиента, не закрывая сервер

(рис. 8).

Рисунок 8 — Снова запускаем три процесса-клиента Если попытаться отправить сообщение несуществующему клиенту

(например, @3), то появится сообщение об ошибке (рис. 9).

9

Рисунок 9 — Сообщение о неправильном адресате

Команда exit (со стороны сервера) освобождает все ресурсы и завершает процесс-сервер. Процессам-клиентам ничего не остается, кроме как завершить свою работу (рис. 10).

Рисунок 10 — Конец работы сервера и клиентов

Все используемые в программе функции относятся к C POSIX Library. В работе мы использовали следующие POSIX функции (табл. 3).

Таблица 3 — Используемые в программе POSIX функции

_exit

connect

listen

pthread_create

socket

accept

fflush

memset

pthread_join

sscanf

atoi

fgets

perror

recv

strcat

 

 

 

 

 

bind

htons

printf

send

strlen

 

 

 

 

 

close

inet_pton

pthread_cancel

setsockopt

strncmp

 

 

 

 

 

Заключение В результате выполнения лабораторной работы мы разработали кросс-

платформенную программу с использованием POSIX функций.

10