// Освобождаем ресурсы, если ошибка записи 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