mirror of https://github.com/t1meshift/os_labs.git
Add 8th lab
parent
ae84f722a8
commit
be91d8d06c
|
@ -19,5 +19,6 @@ define_lab(lab4)
|
||||||
define_lab(lab5)
|
define_lab(lab5)
|
||||||
define_lab(lab6)
|
define_lab(lab6)
|
||||||
define_lab(lab7)
|
define_lab(lab7)
|
||||||
|
define_lab(lab8)
|
||||||
define_lab(lab9)
|
define_lab(lab9)
|
||||||
define_lab(lab10)
|
define_lab(lab10)
|
|
@ -0,0 +1,35 @@
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
|
# Lab name
|
||||||
|
set(LAB_NAME "lab8")
|
||||||
|
|
||||||
|
# Lab tasks
|
||||||
|
list(APPEND SOURCE_FILES
|
||||||
|
server.c
|
||||||
|
client.c
|
||||||
|
)
|
||||||
|
list(APPEND NON_COMPILABLE_SRC
|
||||||
|
# .execme
|
||||||
|
)
|
||||||
|
|
||||||
|
### Here goes the template
|
||||||
|
|
||||||
|
project("${LAB_NAME}" C)
|
||||||
|
|
||||||
|
add_custom_target("${LAB_NAME}")
|
||||||
|
|
||||||
|
foreach (file IN LISTS SOURCE_FILES)
|
||||||
|
add_executable("${LAB_NAME}_${file}_run" "${file}")
|
||||||
|
add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run")
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
foreach (file IN LISTS NON_COMPILABLE_SRC)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET "${LAB_NAME}" POST_BUILD
|
||||||
|
DEPENDS "${file}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/${file}"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/${file}"
|
||||||
|
)
|
||||||
|
endforeach ()
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Лабораторная работа №8
|
||||||
|
|
||||||
|
> Разработка сервера с использованием цикла ожидания событий
|
||||||
|
|
||||||
|
На сервере создаётся и биндится сокет, слушается клиент. Далее в бесконечном цикле с
|
||||||
|
использованием `select()` обрабатываются запросы (с возможностью подключения нескольких
|
||||||
|
клиентов)
|
||||||
|
|
||||||
|
Для каждого запроса обрабатывается запрос в plain-text-формате, возвращается ответ в том же формате.
|
||||||
|
|
||||||
|
Для асинхронной работы использованы неблокирующие сокеты.
|
||||||
|
Их отличие в том, что во время выполнения операции программа не блокирует своё исполнение.
|
||||||
|
|
||||||
|
Используя функцию `fcntl()`, сокеты переводятся в неблокирующий режим.
|
||||||
|
Вызов любой функции с таким сокетом будет возвращать управление немедленно.
|
||||||
|
|
||||||
|
> Добавить обработку сигналов
|
||||||
|
|
||||||
|
Для обработки сигналов используем системный вызов `signal()`.
|
||||||
|
|
||||||
|
Существуют и другие подходы для создания сервера с циклом ожидания событий. Например, с использованием `fork()`,
|
||||||
|
как это делается, например, в Apache2, где для обработки клиентов заранее делается массив форков (т.к. форк --
|
||||||
|
дорогостоящая операция).
|
||||||
|
Этот способ подразумевает создание дочернего процесса для обслуживания каждого нового клиента.
|
||||||
|
При этом родительский процесс занимается только прослушиванием порта и приёмом соединений.
|
||||||
|
|
||||||
|
Но у такого подхода есть минусы.
|
||||||
|
|
||||||
|
1. Если клиентов очень много, создание нового процесса для обслуживания каждого из них может оказаться слишком
|
||||||
|
дорогостоящей операцией (решается пулом пре-форков).
|
||||||
|
2. Такой способ неявно подразумевает, что все клиенты обслуживаются независимо друг от друга.
|
||||||
|
|
||||||
|
Для решения этих проблем мы можем использовать `select()`.
|
||||||
|
Его плюсы заключаются в разрешении вышеописанных проблем.
|
||||||
|
|
||||||
|
Минусы заключаются в том, что программы получаются более сложными по сравнению с первым способом,
|
||||||
|
а также программа вынуждена отслеживать дескрипторы всех клиентов и работать с ними параллельно
|
||||||
|
и каждый раз нужно пробегаться в цикле по дескрипторам всех сокетов и проверять, произошло ли какое-либо событие.
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "config.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define USAGE_STRING "Usage: %s [address [port]]\n" \
|
||||||
|
"`address' should be valid IPv4 address\n" \
|
||||||
|
"`port' should be in range [1, 65535]\n"
|
||||||
|
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||||
|
#define STR_EQ(a, b) (!strcmp((a),(b)))
|
||||||
|
#define WARNING(fmt, ...) fprintf(stderr,fmt,##__VA_ARGS__)
|
||||||
|
#define FATAL(fmt, ...) do {\
|
||||||
|
WARNING(fmt,##__VA_ARGS__);\
|
||||||
|
exit(1);\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
struct in_addr address = {.s_addr = inet_addr("127.0.0.1")};
|
||||||
|
int port = PORT;
|
||||||
|
|
||||||
|
switch (argc) {
|
||||||
|
case 3:
|
||||||
|
port = strtol(argv[2], NULL, 10);
|
||||||
|
if (port <= 0 || port >= 0xFFFF) {
|
||||||
|
FATAL("Could not parse port\n" USAGE_STRING, argv[0]);
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if (!inet_aton(argv[1], &address)) {
|
||||||
|
FATAL("Could not parse IPv4 address\n" USAGE_STRING, argv[0]);
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FATAL(USAGE_STRING, argv[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sockfd == -1) {
|
||||||
|
perror("socket()");
|
||||||
|
FATAL("Error while creating socket\n");
|
||||||
|
}
|
||||||
|
struct sockaddr_in srv_addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr = address,
|
||||||
|
.sin_port = htons(port)
|
||||||
|
};
|
||||||
|
|
||||||
|
int err = connect(sockfd, (const struct sockaddr *) &srv_addr, sizeof(srv_addr));
|
||||||
|
if (err == -1) {
|
||||||
|
perror("connect()");
|
||||||
|
FATAL("Connection error.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
char buff[4096];
|
||||||
|
for (;;) {
|
||||||
|
memset(buff, 0, 4096);
|
||||||
|
printf("time - print current server time\n"
|
||||||
|
"<filepath> - print contents of the file\n"
|
||||||
|
"quit - quit client\n"
|
||||||
|
"Enter command: ");
|
||||||
|
int n = 0;
|
||||||
|
while ((buff[n++] = getchar()) != '\n');
|
||||||
|
buff[n - 1] = 0;
|
||||||
|
|
||||||
|
// Some commands should be parsed on client side
|
||||||
|
if (STR_EQ(buff, "quit")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send(sockfd, buff, n, 0) == -1) {
|
||||||
|
perror("send()");
|
||||||
|
FATAL("Could not send data.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(buff, 0, 4096);
|
||||||
|
if (recv(sockfd, buff, 4096, 0) == -1) {
|
||||||
|
perror("recv()");
|
||||||
|
FATAL("Receiving failed.\n");
|
||||||
|
}
|
||||||
|
printf("%s\n", buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server port. Also used in client to specify connection port.
|
||||||
|
*/
|
||||||
|
#define PORT 8841
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of connections pending.
|
||||||
|
*/
|
||||||
|
#define PENDING_CONNS_MAX 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout value in seconds.
|
||||||
|
*/
|
||||||
|
#define TIMEOUT_SECS 120
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer size for client request.
|
||||||
|
*/
|
||||||
|
#define BUF_SIZE 4096
|
|
@ -0,0 +1,143 @@
|
||||||
|
#include "config.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||||
|
#define STR_EQ(a, b) (!strcmp((a),(b)))
|
||||||
|
#define WARNING(fmt, ...) fprintf(stderr,fmt,##__VA_ARGS__)
|
||||||
|
#define FATAL(fmt, ...) do {\
|
||||||
|
WARNING(fmt,##__VA_ARGS__);\
|
||||||
|
exit(1);\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
char *handle_cmd(char *buff) {
|
||||||
|
char *ret = NULL;
|
||||||
|
if (!*buff) {
|
||||||
|
ret = strdup("Empty request\n");
|
||||||
|
}
|
||||||
|
else if (isalpha(buff[0])) {
|
||||||
|
if (STR_EQ(buff, "time")) {
|
||||||
|
// Respond with current time
|
||||||
|
time_t t = time(NULL);
|
||||||
|
ret = strdup(ctime(&t));
|
||||||
|
} else {
|
||||||
|
ret = strdup("Unknown command\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get files
|
||||||
|
int fd = open(buff, O_RDONLY);
|
||||||
|
if (fd == -1) {
|
||||||
|
perror("open()");
|
||||||
|
WARNING("Can't open file %s\n", buff);
|
||||||
|
return strdup("ERROR: server failed to obtain file.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat f_stat;
|
||||||
|
if(fstat(fd, &f_stat) == -1) {
|
||||||
|
perror("fstat()");
|
||||||
|
WARNING("Can't stat file %s\n", buff);
|
||||||
|
return strdup("ERROR: server failed to obtain file.\n");
|
||||||
|
}
|
||||||
|
ret = malloc(f_stat.st_size);
|
||||||
|
read(fd, ret, f_stat.st_size);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sig_hnd(int signum) {
|
||||||
|
switch (signum) {
|
||||||
|
case SIGUSR1:
|
||||||
|
WARNING("SIGUSR1 handled\n");
|
||||||
|
break;
|
||||||
|
case SIGUSR2:
|
||||||
|
WARNING("SIGUSR2 handled\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sockfd == -1) {
|
||||||
|
perror("socket()");
|
||||||
|
FATAL("Error while creating socket\n");
|
||||||
|
}
|
||||||
|
fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
||||||
|
|
||||||
|
struct sockaddr_in srv_addr = {.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = htonl(INADDR_ANY),
|
||||||
|
.sin_port = htons(PORT)},
|
||||||
|
clt_addr;
|
||||||
|
|
||||||
|
int err = bind(sockfd, (const struct sockaddr *) &srv_addr, sizeof(srv_addr));
|
||||||
|
if (err == -1) {
|
||||||
|
perror("bind()");
|
||||||
|
FATAL("Error binding socket to address\n");
|
||||||
|
}
|
||||||
|
err = listen(sockfd, PENDING_CONNS_MAX);
|
||||||
|
if (err == -1) {
|
||||||
|
perror("listen()");
|
||||||
|
FATAL("Error putting socket to passive mode\n");
|
||||||
|
}
|
||||||
|
printf("Listening on port %d...\n", PORT);
|
||||||
|
|
||||||
|
signal(SIGUSR1, sig_hnd);
|
||||||
|
signal(SIGUSR2, sig_hnd);
|
||||||
|
|
||||||
|
char buff[4096] = {0};
|
||||||
|
fd_set readset, allset;
|
||||||
|
FD_ZERO(&allset);
|
||||||
|
FD_SET(sockfd, &allset);
|
||||||
|
struct timeval timeout = {.tv_sec = TIMEOUT_SECS, .tv_usec = 0};
|
||||||
|
socklen_t socklen = sizeof(clt_addr);
|
||||||
|
for (;;) {
|
||||||
|
readset = allset;
|
||||||
|
|
||||||
|
if (select(FD_SETSIZE, &readset, NULL, NULL, &timeout) == -1) {
|
||||||
|
perror("select()");
|
||||||
|
FATAL("Server error\n");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < FD_SETSIZE; ++i) {
|
||||||
|
if (FD_ISSET(i, &readset)) {
|
||||||
|
if (i == sockfd) {
|
||||||
|
int sock = accept(sockfd, (struct sockaddr *) &clt_addr, &socklen);
|
||||||
|
if (sock == -1) {
|
||||||
|
perror("accept()");
|
||||||
|
FATAL("Acception failed.\n");
|
||||||
|
}
|
||||||
|
printf("Client accepted...\n");
|
||||||
|
fcntl(sock, F_SETFL, O_NONBLOCK);
|
||||||
|
FD_SET(sock, &allset);
|
||||||
|
} else {
|
||||||
|
int bytes_read = recv(i, buff, sizeof(buff), 0);
|
||||||
|
if (bytes_read <= 0) {
|
||||||
|
close(i);
|
||||||
|
FD_CLR(i, &allset);
|
||||||
|
printf("Client disconnected...\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *read_buff = handle_cmd(buff);
|
||||||
|
|
||||||
|
if (send(i, read_buff, strlen(read_buff), 0) == -1) {
|
||||||
|
perror("send()");
|
||||||
|
FATAL("Error sending data.\n");
|
||||||
|
}
|
||||||
|
free(read_buff);
|
||||||
|
memset(buff, 0, 4096);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue