
7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 1
.pdfМИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ,
СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. проф. М. А. Бонч-Бруевича»
(СПбГУТ)
Факультет инфокоммуникационных сетей и систем Кафедра программной инженерии и вычислительной техники
ЛАБОРАТОРНАЯ РАБОТА №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