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

7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 1

.pdf
Скачиваний:
31
Добавлен:
23.12.2021
Размер:
528.39 Кб
Скачать

МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ,

СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. проф. М. А. Бонч-Бруевича»

(СПбГУТ)

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

ЛАБОРАТОРНАЯ РАБОТА №1

по дисциплине «Математические методы и вычислительные алгоритмы современных систем связи»

студент гр. ИКПИ-84

_______________

Коваленко Л. А.

преподаватель каф. ПИиВТ

_______________

к.п.н., доцент Коробов С. А.

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

2021

ЦЕЛЬ РАБОТЫ

Ознакомление с основной структурой клиент-серверного приложения на примере написания программ передачи/приема текстовых сообщений с использованием сокетов в блокирующем режиме.

ПОСТАНОВКА ЗАДАЧИ

Необходимо разработать два клиент-серверных приложения на основе протоколов TCP и UDP. Клиент должен подключиться к серверу и отправить ему текстовое сообщение; сервер, получив сообщение, должен вывести его на экран.

ХОД РАБОТЫ

Реализация TCP-сервера на языке C приведена в таблице 1.

Таблица 1. Реализация TCP-сервера на языке C

tcp_server.c

// tcp_server.c

#include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>

#define MESSAGE_LENGTH 11 // включая '\0' #define PORT 1100

#define ERROR_CODE -1

int main(void)

{

//int socket(int domain, int type, int protocol);

//Функция для получения дескриптора конечной точки соединения.

//[1] Параметр domain задает домен соединения: выбирает набор протоколов,

//которые будут использоваться для создания соединения.

//PF_INET -- IPv4 протоколы Интернет.

//[2] Сокет имеет тип type, задающий семантику коммуникации.

//SOCK_STREAM -- обеспечивает создание двусторонних надежных и

//последовательных потоков байтов, поддерживающих соединения.

//[3] Параметр protocol задает конкретный протокол, который работает с сокетом

//IPPROTO_TCP -- протокол TCP (Transmission Control Protocol).

int socket_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket_fd == ERROR_CODE)

{

perror("cannot create socket"); exit(EXIT_FAILURE);

}

struct sockaddr_in sa; // Описывает сокет для работы с протоколами IP socklen_t sa_len = sizeof(sa); // Размер структуры

memset(&sa, 0, sa_len); // Заполнение нулями sa.sin_family = AF_INET;

//uint16_t htons(uint16_t hostshort); (Host to Network Short)

//Существует два порядка хранения байтов в слове и двойном слове. Один

//из них называется порядком хоста (host byte order), другой - сетевым порядком

//(network byte order) хранения байтов. При указании IP-адреса и номера порта

//необходимо преобразовать число из порядка хоста в сетевой.

//В случае uint16_t этим занимается функция htons.

sa.sin_port = htons(PORT);

2

// В случае uint32_t этим занимается функция htonl.

sa.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY -- все адреса локального хоста

//Когда сокет создан (с помощью socket), он существует в пространстве имен

//(семействе адресов), но не имеет назначенного адреса. Для назначения адреса используют

//bind.

//int bind(int socket, const struct sockaddr *address, socklen_t address_len);

//Функция для привязки локального адреса address длиной address_len к сокету sockfd.

if (bind(socket_fd, (struct sockaddr *)&sa, sa_len) == ERROR_CODE)

{

perror("bind failed"); close(socket_fd); exit(EXIT_FAILURE);

}

//int listen(int socket, int backlog);

//Функция для выражения готовности принимать входящие соединения

//и задания размера очереди.

//[1] Дескриптор, ссылающийся на сокет.

//[2] Размер очереди для полностью установленных соединений, ожидающих,

//пока процесс примет их.

if (listen(socket_fd, 1) == ERROR_CODE)

{

perror("listen failed"); close(socket_fd); exit(EXIT_FAILURE);

}

// Получение сообщений от клиентов. char message[MESSAGE_LENGTH]; while (1)

{

//int accept(int socket, struct sockaddr *restrict address,

//socklen_t *restrict address_len);

//Функция для принятия соединения на сокете.

//Функция извлекает первый запрос на соединение из очереди

//ожидающих соединений, создает новый подключенный сокет почти

//с такими же параметрами, что и у s, и выделяет для сокета

//новый файловый дескриптор, который и возвращается.

//Новый сокет более не находится в слушающем состоянии.

//[1] Дескриптор, ссылающийся на исходный сокет.

//[2] Адрес другой стороны.

//[3] Размер структуры.

int connect_fd = accept(socket_fd, NULL, NULL); if (connect_fd == ERROR_CODE)

{

perror("accept failed"); close(socket_fd); exit(EXIT_FAILURE);

}

printf("Message from client: "); while (1)

{

//ssize_t read(int fildes, void *buf, size_t nbyte);

//Функция пытается записать nbyte байтов файлового описателя

//fildes в буфер, адрес которого начинается с buf

ssize_t bytes = read(connect_fd, message, (MESSAGE_LENGTH - 1) * sizeof(char)); if (bytes == ERROR_CODE)

{

perror("read operation failed"); close(socket_fd); exit(EXIT_FAILURE);

}

size_t symbols = (size_t)bytes / sizeof(char); message[symbols] = '\0';

if (symbols == 0 || message[symbols - 1] == '\0')

{

printf("%s", message); break;

}

printf("%s", message); fflush(stdout);

}

printf("\n"); fflush(stdout);

//int shutdown(int socket, int how);

//Закрывает все или часть открытых соединений на сокете.

//[1] Дескриптор, ссылающийся на исходный сокет.

//[2] Определяет, что закрыть:

3

//SHUT_RD -- закрывает все приемы.

//SHUT_WR -- закрывает все передачи.

//SHUT_RDWR -- закрывает все приемы и передачи. if (shutdown(connect_fd, SHUT_RDWR) == ERROR_CODE)

{

perror("shutdown failed"); close(connect_fd); close(socket_fd); exit(EXIT_FAILURE);

}

close(connect_fd);

}

close(socket_fd); return EXIT_SUCCESS;

}

Реализация TCP-клиента на языке C приведена в таблице 2.

Таблица 2. Реализация TCP-клиента на языке C

tcp_client.c

// tcp_client.c

#include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>

#define MIN_MESSAGE_LENGTH 10 #define PORT 1100

#define ERROR_CODE -1 #define HOST "0.0.0.0"

int main(void)

{

//int socket(int domain, int type, int protocol);

//Функция для получения дескриптора конечной точки соединения.

//[1] Параметр domain задает домен соединения: выбирает набор протоколов,

//которые будут использоваться для создания соединения.

//PF_INET -- IPv4 протоколы Интернет.

//[2] Сокет имеет тип type, задающий семантику коммуникации.

//SOCK_STREAM -- обеспечивает создание двусторонних надежных и

//последовательных потоков байтов, поддерживающих соединения.

//[3] Параметр protocol задает конкретный протокол, который работает с сокетом

//IPPROTO_TCP -- протокол TCP (Transmission Control Protocol).

int socket_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket_fd == ERROR_CODE)

{

perror("cannot create socket"); exit(EXIT_FAILURE);

}

struct sockaddr_in sa; // Описывает сокет для работы с протоколами IP socklen_t sa_len = sizeof(sa); // Размер структуры

memset(&sa, 0, sa_len); // Заполнение нулями sa.sin_family = AF_INET;

//uint16_t htons(uint16_t hostshort); (Host to Network Short)

//Существует два порядка хранения байтов в слове и двойном слове. Один

//из них называется порядком хоста (host byte order), другой - сетевым порядком

//(network byte order) хранения байтов. При указании IP-адреса и номера порта

//необходимо преобразовать число из порядка хоста в сетевой.

//В случае uint16_t этим занимается функция htons.

sa.sin_port = htons(PORT);

//int inet_pton(int af, const char *restrict src, void *restrict dst);

//Функция преобразует строку символов src в сетевой адрес (типа af),

//затем копирует полученную структуру с адресом в dst.

//[1] Тип сетевого адреса.

//AF_INET -- сетевой адрес IPv4.

//[2] Строка символов, содержащая сетевой адрес IPv4 в формате "ddd.ddd.ddd.ddd".

//[3] Переменная для получения результирующего числового адреса.

if (inet_pton(AF_INET, HOST, &sa.sin_addr.s_addr) != 1)

{

perror("inet_pton failed");

4

close(socket_fd); exit(EXIT_FAILURE);

}

//int connect(int socket, const struct sockaddr *address, socklen_t address_len);

//Функция инициирует соединение на сокете.

//[1] Дескриптор, ссылающийся на сокет.

//[2] Адрес отправления (в виде структуры).

//[3] Размер структуры.

if (connect(socket_fd, (struct sockaddr *)&sa, sa_len) == ERROR_CODE)

{

perror("connect failed"); close(socket_fd); exit(EXIT_FAILURE);

}

//Ввод сообщения с консоли и его отправление порциями.

//Порция состоит из максимум MIN_MESSAGE_LENGTH байт. char message[MIN_MESSAGE_LENGTH];

printf("Message for server: ");

for (int b_continue = 1 /* TRUE */; b_continue;)

{

fgets(message, MIN_MESSAGE_LENGTH, stdin); // '\0' автоматически добавляется в конец size_t len = strlen(message); // не считает '\0'

//Если не было перевода строки (было введено больше MIN_MESSAGE_LENGTH символов)

if (len && message[len - 1] == '\n')

{

message[len - 1] = '\0'; // замена '\n' на '\0' b_continue = 0 /* FALSE */;

}

//ssize_t write(int fildes, const void *buf, size_t nbyte);

//Функция записывает до nbyte байтов из буфера buf в файл,

//на который ссылается файловый описатель fildes

if (write(socket_fd, message, len * sizeof(char)) == ERROR_CODE)

{

perror("write operation failed"); close(socket_fd); exit(EXIT_FAILURE);

}

}

close(socket_fd); return EXIT_SUCCESS;

}

Реализация UDP-сервера на языке C приведена в таблице 3.

Таблица 3. Реализация UDP-сервера на языке C

udp_server.c

// udp_server.c

#include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>

#define MESSAGE_LENGTH 11 // включая '\0' #define PORT 1100

#define ERROR_CODE -1

int main(void)

{

//int socket(int domain, int type, int protocol);

//Функция для получения дескриптора конечной точки соединения.

//[1] Параметр domain задает домен соединения: выбирает набор протоколов,

//которые будут использоваться для создания соединения.

//PF_INET -- IPv4 протоколы Интернет.

//[2] Сокет имеет тип type, задающий семантику коммуникации.

//SOCK_DGRAM -- передача датаграмм (ненадежных сообщений с ограниченной длиной

//и не поддерживающих соединения).

//[3] Параметр protocol задает конкретный протокол, который работает с сокетом

//IPPROTO_UDP -- протокол UDP (User Datagram Protocol).

int socket_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (socket_fd == ERROR_CODE)

{

5

perror("cannot create socket"); exit(EXIT_FAILURE);

}

struct sockaddr_in sa; // Описывает сокет для работы с протоколами IP socklen_t sa_len = sizeof(sa); // Размер структуры

memset(&sa, 0, sa_len); // Заполнение нулями sa.sin_family = AF_INET;

//uint16_t htons(uint16_t hostshort); (Host to Network Short)

//Существует два порядка хранения байтов в слове и двойном слове. Один

//из них называется порядком хоста (host byte order), другой - сетевым порядком

//(network byte order) хранения байтов. При указании IP-адреса и номера порта

//необходимо преобразовать число из порядка хоста в сетевой.

//В случае uint16_t этим занимается функция htons.

sa.sin_port = htons(PORT);

// В случае uint32_t этим занимается функция htonl.

sa.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY -- все адреса локального хоста

//Когда сокет создан (с помощью socket), он существует в пространстве имен

//(семействе адресов), но не имеет назначенного адреса. Для назначения адреса используют

//bind.

//int bind(int socket, const struct sockaddr *address, socklen_t address_len);

//Функция для привязки локального адреса address длиной address_len к сокету sockfd.

if (bind(socket_fd, (struct sockaddr *)&sa, sa_len) == ERROR_CODE)

{

perror("bind failed"); close(socket_fd); exit(EXIT_FAILURE);

}

// Получение сообщений от клиентов. char message[MESSAGE_LENGTH]; while (1)

{

printf("Message from client: "); while (1)

{

// ssize_t recvfrom(int

socket, void *restrict buffer, size_t length,

//

int

flags, struct sockaddr *restrict address,

//

socklen_t *restrict address_len);

//Функция используется для получения сообщений из сокета (в данном случае

//блокирующая!). [1] Дескриптор, ссылающийся на сокет. [2] Сообщение. [3] Длина

//сообщения в байтах. [4] Флаги. [5] Адрес другой стороны (в виде структуры). [6]

//Размер структуры.

ssize_t bytes =

recvfrom(socket_fd, (void *)message, (MESSAGE_LENGTH - 1) * sizeof(char), 0, (struct sockaddr *)&sa, &sa_len);

if (bytes == ERROR_CODE)

{

perror("read operation failed"); close(socket_fd); exit(EXIT_FAILURE);

}

size_t symbols = (size_t)bytes / sizeof(char); message[symbols] = '\0';

if (symbols == 0 || message[symbols - 1] == '\0')

{

printf("%s", message); break;

}

printf("%s", message); fflush(stdout);

}

printf("\n"); fflush(stdout);

}

close(socket_fd); return EXIT_SUCCESS;

}

6

Реализация UDP-клиента на языке C приведена в таблице 4.

Таблица 4. Реализация UDP-клиента на языке C

udp_client.c

// udp_client.c

#include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h>

#define MIN_MESSAGE_LENGTH 10 #define PORT 1100

#define ERROR_CODE -1 #define HOST "0.0.0.0"

int main(void)

{

//int socket(int domain, int type, int protocol);

//Функция для получения дескриптора конечной точки соединения.

//[1] Параметр domain задает домен соединения: выбирает набор протоколов,

//которые будут использоваться для создания соединения.

//PF_INET -- IPv4 протоколы Интернет.

//[2] Сокет имеет тип type, задающий семантику коммуникации.

//SOCK_DGRAM -- передача датаграмм (ненадежных сообщений с ограниченной длиной

//и не поддерживающих соединения).

//[3] Параметр protocol задает конкретный протокол, который работает с сокетом

//IPPROTO_UDP -- протокол UDP (User Datagram Protocol).

int socket_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (socket_fd == ERROR_CODE)

{

perror("cannot create socket"); exit(EXIT_FAILURE);

}

struct sockaddr_in sa; // Описывает сокет для работы с протоколами IP memset(&sa, 0, sizeof(sa));

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

//in_addr_t inet_addr(const char *cp);

//Функция inet_addr() преобразует обычный вид IP-адреса cp (из номеров и точек)

//в двоичный код в сетевом порядке расположения байтов.

if ((sa.sin_addr.s_addr = inet_addr(HOST)) == INADDR_NONE)

{

perror("inet_addr failed"); exit(EXIT_FAILURE);

}

//Ввод сообщения с консоли и отправление его порциями.

//Порция состоит из максимум MIN_MESSAGE_LENGTH байт. char message[MIN_MESSAGE_LENGTH];

printf("Message for server: ");

for (int b_continue = 1 /* TRUE */; b_continue;)

{

fgets(message, MIN_MESSAGE_LENGTH, stdin); // '\0' автоматически добавляется в конец size_t len = strlen(message); // не считает '\0'

//Если не было перевода строки (было введено больше MIN_MESSAGE_LENGTH символов)

if (len && message[len - 1] == '\n')

{

message[len - 1] = '\0'; // замена '\n' на '\0' b_continue = 0 /* FALSE */;

}

// ssize_t sendto(int

socket, const void *message, size_t length,

//

int

flags, const struct sockaddr *dest_addr,

//

socklen_t dest_len);

//Функция используется для отправления сообщения в сокет

//[1] Дескриптор, ссылающийся на сокет.

//[2] Сообщение.

//[3] Длина сообщения в байтах.

//[4] Флаги.

//[5] Адрес другой стороны (в виде структуры).

//[6] Размер структуры.

7

if (sendto(socket_fd, message, len * sizeof(char), 0, (struct sockaddr *)&sa, sizeof(sa)) == ERROR_CODE)

{

perror("send operation failed"); close(socket_fd); exit(EXIT_FAILURE);

}

}

close(socket_fd); return EXIT_SUCCESS;

}

Сборочный файл представлен в таблице 5.

Таблица 5. Сборочный файл

Makefile

.PHONY : clean CC = gcc

CFLAGS = -O0 -std=c11 -Wall -Waddress -Wextra -Werror \ -Wpedantic -Wfloat-equal -Waggregate-return \ -old-style-cast -Wshadow -Wundef -Wpointer-arith \ -Wcast-align -Wcast-qual -fsanitize=address \ -Wstrict-prototypes -Wstrict-overflow=5 \ -Wwrite-strings -Wformat=2 -pedantic-errors \ -Wswitch-default -Wswitch-enum -Wconversion \ -Wunreachable-code -Wredundant-decls -Wenum-compare \ -Wduplicated-branches -Wduplicated-cond -Wlogical-op \ -Wsign-conversion

SOURCES = tcp_client.c tcp_server.c udp_client.c udp_server.c EXECUTABLES = $(SOURCES:.c=)

all: $(SOURCES) $(EXECUTABLES)

.c:

$(CC) $(CFLAGS) $? -o $(?:.c=)

clean:

rm -rf $(EXECUTABLES)

Все используемые в программах заголовочные файлы и функции входят в стандарт POSIX.

Работа TCP-сервера и TCP-клиента изображена на рисунке 1.

Рисунок 1. Работа TCP-сервера и TCP-клиента

8

Работа UDP-сервера и UDP-клиента изображена на рисунке 2.

Рисунок 2. Работа UDP-сервера и UDP-клиента

ЗАКЛЮЧЕНИЕ

В результате выполнения лабораторной работы мы разработали два клиент-серверных приложения на основе протоколов TCP и UDP.

9