mirror of https://github.com/t1meshift/os_labs.git
Compare commits
No commits in common. "377e68bf21e0ac6b220abbce7c90978dab798fdf" and "ae84f722a8f5e7ad4d51267bede22ba0ed37faee" have entirely different histories.
377e68bf21
...
ae84f722a8
|
@ -19,7 +19,5 @@ 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)
|
||||||
define_lab(lab12)
|
|
|
@ -8,11 +8,9 @@
|
||||||
- [Лабораторная работа 5](lab5/README.md)
|
- [Лабораторная работа 5](lab5/README.md)
|
||||||
- [Лабораторная работа 6](lab6/README.md)
|
- [Лабораторная работа 6](lab6/README.md)
|
||||||
- [Лабораторная работа 7](lab7/README.md)
|
- [Лабораторная работа 7](lab7/README.md)
|
||||||
- [Лабораторная работа 8](lab8/README.md)
|
|
||||||
- [Лабораторная работа 9](lab9/README.md)
|
- [Лабораторная работа 9](lab9/README.md)
|
||||||
- [Лабораторная работа 10](lab10/README.md)
|
- [Лабораторная работа 10](lab10/README.md)
|
||||||
- [Лабораторная работа 11](lab11/README.md)
|
- [Лабораторная работа 11](lab11/README.md)
|
||||||
- [Лабораторная работа 12](lab12/README.md)
|
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
|
|
|
@ -2,49 +2,16 @@
|
||||||
|
|
||||||
## Задание 1
|
## Задание 1
|
||||||
|
|
||||||
> vector-deadlock.c, main-common.c и др.:
|
|
||||||
> - Выполнить ./vector-deadlock -n 2 -l 1 -v
|
|
||||||
> которая инициирует 2 потока (-n 2) и каждый из них осуществляет одно сложение (-l 1) с опцией (-v).
|
|
||||||
> Объяснить результат. Меняется ли он от вызова к вызову?
|
|
||||||
> - Добавить флаг -d и изменить количество циклов -l .
|
|
||||||
> Всегда ли возникает состояние взаимной блокировки потоков (deadlock)?
|
|
||||||
> - Теперь меняем число потоков -n. Есть ли такое число потоков, при котором блокировка не возникает?
|
|
||||||
|
|
||||||
Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции.
|
Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции.
|
||||||
Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой
|
Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой
|
||||||
программе это маловероятно достижимо.
|
программе это маловероятно достижимо.
|
||||||
|
|
||||||
Обычно программа возвращает следующее:
|
|
||||||
```text
|
|
||||||
->add(0, 1)
|
|
||||||
<-add(0, 1)
|
|
||||||
->add(0, 1)
|
|
||||||
<-add(0, 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
Спустя примерно 30 запусков, удалось получить другой результат:
|
|
||||||
```text
|
|
||||||
->add(0, 1)
|
|
||||||
->add(0, 1)
|
|
||||||
<-add(0, 1)
|
|
||||||
<-add(0, 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами.
|
При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами.
|
||||||
|
|
||||||
Удалось достичь стабильный дедлок при запуске с ключами `-t -n 2 -l 100 -d` (без `-v`)
|
|
||||||
|
|
||||||
В случае, если число потоков = 1, взаимной блокировки не возникнет.
|
В случае, если число потоков = 1, взаимной блокировки не возникнет.
|
||||||
|
|
||||||
|
|
||||||
## Задание 2
|
## Задание 2
|
||||||
|
|
||||||
> vector-global-order.c:
|
|
||||||
> - За счет чего программа избегает блокировок?
|
|
||||||
> - Для чего предусмотрен специальный случай в vector add(), когда исходный и результирующий вектор совпадают?
|
|
||||||
> - Флаги: -t -n 2 -l 100000 -d. Как меняется время в зависимости от числа циклов и числа потоков?
|
|
||||||
> - Что происходит, когда включается ключ -p (при сложении различных векторов и одного и того же)?
|
|
||||||
|
|
||||||
Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок
|
Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок
|
||||||
блокировки.
|
блокировки.
|
||||||
|
|
||||||
|
@ -56,89 +23,43 @@
|
||||||
|
|
||||||
## Задание 3
|
## Задание 3
|
||||||
|
|
||||||
> vector-try-wait.c:
|
|
||||||
> - Нужен ли первый вызов pthread_mutex_trylock()?
|
|
||||||
> - Как меняется число повторных попыток, когда растет число потоков?
|
|
||||||
|
|
||||||
Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока.
|
Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока.
|
||||||
|
|
||||||
```text
|
|
||||||
$ ./vector-try-wait -t -n 2 -l 100 -d
|
|
||||||
Retries: 0
|
|
||||||
Time: 0.00 seconds
|
|
||||||
|
|
||||||
$ ./vector-try-wait -t -n 4 -l 100 -d
|
|
||||||
Retries: 847
|
|
||||||
Time: 0.00 seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками
|
С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками
|
||||||
становится более частым.
|
становится более частым.
|
||||||
|
|
||||||
При использовании `-p` повторных попыток не возникает.
|
|
||||||
|
|
||||||
## Задание 4
|
## Задание 4
|
||||||
|
|
||||||
> vector-avoid-hold-and-wait.c:
|
Данный подход защищает уязвимое место дедлока, созданием глобального мьютекса, но при этом не даёт различным векторам
|
||||||
> - Сравнить с другими подходами.
|
|
||||||
> - Как меняется производительность в зависимости от наличия флага -p?
|
|
||||||
|
|
||||||
Данный подход защищает уязвимое место дедлока созданием глобального мьютекса, но при этом не даёт различным векторам
|
|
||||||
выполняться параллельно.
|
выполняться параллельно.
|
||||||
|
|
||||||
При использовании `-p` время уменьшается.
|
При использовании `-p` время уменьшается.
|
||||||
|
|
||||||
## Задание 5
|
## Задание 5
|
||||||
|
|
||||||
> vector-nolock.c:
|
Указав memory, мы дожидаемся завершения всех операцией с памятью, что своего рода позволяет заменить мьютексы.
|
||||||
> - Сравнить семантику и производительность с другими вариантами при работе с двумя одинаковыми векторами и в случае,
|
|
||||||
> когда каждый поток работает на своем векторе -p.
|
|
||||||
|
|
||||||
Указав memory, программа дожидается завершения всех операцией с памятью, что позволяет заменить мьютексы в данном случае.
|
|
||||||
(https://ru.wikipedia.org/wiki/GCC_Inline_Assembly)
|
(https://ru.wikipedia.org/wiki/GCC_Inline_Assembly)
|
||||||
|
|
||||||
Фактически в программе производится атомарное сложение, и в стандарте C11 для этих целей есть особые типы:
|
|
||||||
(https://en.cppreference.com/w/c/language/atomic)
|
|
||||||
|
|
||||||
Также в C11 ввели поддержку потоков в стандартную библиотеку, что позволяет писать кроссплатформенный код:
|
|
||||||
(https://en.cppreference.com/w/c/thread)
|
|
||||||
|
|
||||||
Но атомарные операции очень дорого стоят.
|
|
||||||
Сравним время выполнения следующих команд:
|
Сравним время выполнения следующих команд:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ ./vector-nolock -t -n 2 -l 1000000 -d
|
./vector-nolock -t -n 2 -l 1000000 -d = 4.08
|
||||||
Time: 7.20 seconds
|
./vector-nolock -t -n 2 -l 1000000 -d -p = 0.65
|
||||||
|
|
||||||
$ ./vector-nolock -t -n 2 -l 1000000 -d -p
|
|
||||||
Time: 1.07 seconds
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d
|
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d = 2.98
|
||||||
Time: 4.46 seconds
|
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p = 0.45
|
||||||
|
|
||||||
$ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p
|
|
||||||
Time: 0.40 seconds
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ ./vector-try-wait -t -n 2 -l 1000000 -d
|
./vector-try-wait -t -n 2 -l 1000000 -d = 1.30
|
||||||
Retries: 5979033
|
./vector-try-wait -t -n 2 -l 1000000 -d -p = 0.18
|
||||||
Time: 2.55 seconds
|
|
||||||
|
|
||||||
$ ./vector-try-wait -t -n 2 -l 1000000 -d -p
|
|
||||||
Retries: 0
|
|
||||||
Time: 0.25 seconds
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ ./vector-global-order -t -n 2 -l 1000000 -d
|
./vector-global-order -t -n 2 -l 1000000 -d = 0.69
|
||||||
Time: 1.23 seconds
|
./vector-global-order -t -n 2 -l 1000000 -d -p = 0.19
|
||||||
|
|
||||||
$ ./vector-global-order -t -n 2 -l 1000000 -d -p
|
|
||||||
Time: 0.26 seconds
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Таким образом видно, что vector-nolock работает медленнее других в любом случае.
|
Таким образом видно, что vector-nolock работает медленнее других в любом случае.
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
IFS=$'\n\t'
|
|
||||||
|
|
||||||
pushd "$1" > /dev/null
|
|
||||||
|
|
||||||
./lab12_cache.c_run > ./data && lscpu && cat /proc/cpuinfo && python2 ./graph_data.py
|
|
||||||
|
|
||||||
popd > /dev/null
|
|
|
@ -1,36 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
|
|
||||||
# Lab name
|
|
||||||
set(LAB_NAME "lab12")
|
|
||||||
|
|
||||||
# Lab tasks
|
|
||||||
list(APPEND SOURCE_FILES
|
|
||||||
cache.c
|
|
||||||
)
|
|
||||||
list(APPEND NON_COMPILABLE_SRC
|
|
||||||
.execme
|
|
||||||
graph_data.py
|
|
||||||
thinkplot.py
|
|
||||||
)
|
|
||||||
|
|
||||||
### 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 ()
|
|
|
@ -1,56 +0,0 @@
|
||||||
# Лабораторная работа №12
|
|
||||||
|
|
||||||
> Проанализируйте cache.c и с ее использованием исследуйте параметры кэша на вашем компьютере. Для этого
|
|
||||||
> 1. постройте графики времени доступа как функции длины массива, шага выборки и размера буфера.
|
|
||||||
> 2. на их основе сформулируйте обоснованные гипотезы о размере кэша, размере блока, наличию кэша более высокого уровня.
|
|
||||||
> 3. сравните свои оценки с реальными значениями, полученными через вызов системных функций или из технического описания вашего компьютера.
|
|
||||||
|
|
||||||
График:
|
|
||||||
|
|
||||||
![](g1.png)
|
|
||||||
|
|
||||||
Как видно из графика, стремительный рост access time происходит на 2^22 B, что примерно равно 4 мегабайтам.
|
|
||||||
Из этого можно предположить, что размер кэша -- 4Мб. На размере блока выше 64 байт происходит увеличение access time,
|
|
||||||
что может быть связано с тем, что физический размер блока -- 64 байта. Также наблюдаются ускорения при размере 2^22, что
|
|
||||||
может говорить о существовании некоторых кэша размером в 4 Мб.
|
|
||||||
|
|
||||||
Видны изменения во времени на 2^19, что равно 512 Кб. Скорее всего, это L2-кэш (исходя из средних размеров кэшей
|
|
||||||
на современных процессорах).
|
|
||||||
|
|
||||||
Вывод `cat /proc/cpuinfo`:
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
cache size : 3072 KB
|
|
||||||
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
|
|
||||||
bogomips : 3792.26
|
|
||||||
clflush size : 64
|
|
||||||
cache_alignment : 64
|
|
||||||
address sizes : 39 bits physical, 48 bits virtual
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Вывод `lscpu`:
|
|
||||||
```text
|
|
||||||
...
|
|
||||||
L1d cache: 64 KiB
|
|
||||||
L1i cache: 64 KiB
|
|
||||||
L2 cache: 512 KiB
|
|
||||||
L3 cache: 3 MiB
|
|
||||||
Vulnerability Itlb multihit: KVM: Mitigation: Split huge pages
|
|
||||||
Vulnerability L1tf: Mitigation; PTE Inversion; VMX conditional cache flushes, SMT vulnerable
|
|
||||||
Vulnerability Mds: Mitigation; Clear CPU buffers; SMT vulnerable
|
|
||||||
Vulnerability Meltdown: Mitigation; PTI
|
|
||||||
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
|
|
||||||
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
|
|
||||||
Vulnerability Spectre v2: Mitigation; Full generic retpoline, IBPB conditional, IBRS_FW, STIBP conditional, RSB filling
|
|
||||||
Vulnerability Srbds: Mitigation; Microcode
|
|
||||||
Vulnerability Tsx async abort: Not affected
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Исходя из этих данных, можно предположить, что в связи с патчами для устранения уязвимостей процессора
|
|
||||||
(Spectre, Meltdown, L1TF и прочие) график может не вполне корректно отражать реальное положение дел.
|
|
||||||
|
|
||||||
Но выводы оказались достаточно приближены к действительности: мы видим два L1-кэша размера 64 Кб (не видно
|
|
||||||
на графике, т.к. 2^16 Б меньше левой границы графика), L2-кэш размера 512 Кб (2^19 Б) и L3-кэш размера 3 Мб
|
|
||||||
(~2^(21.6) Б).
|
|
|
@ -1,72 +0,0 @@
|
||||||
/******************************************************************
|
|
||||||
* CACHE project *
|
|
||||||
* *
|
|
||||||
* Using this program, on as many different kinds of computers as *
|
|
||||||
* possible, investigate these cache parameters: *
|
|
||||||
* -- total cache size *
|
|
||||||
* -- cache width *
|
|
||||||
* -- cache replacement policy *
|
|
||||||
******************************************************************/
|
|
||||||
|
|
||||||
/* I got this program from Brian Harvey, who teaches CS61C at
|
|
||||||
Berkeley. He didn't put a copyright on it, but he should
|
|
||||||
at least get credit for it. Thanks, Brian! */
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/times.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#define CACHE_MIN (32*1024) /* smallest cache */
|
|
||||||
#define CACHE_MAX (32*1024*1024) /* largest cache */
|
|
||||||
#define SAMPLE 10 /* to get a larger time sample */
|
|
||||||
|
|
||||||
int x[CACHE_MAX]; /* array going to stride through */
|
|
||||||
long clk_tck;
|
|
||||||
|
|
||||||
double get_seconds() { /* routine to read time */
|
|
||||||
struct tms rusage;
|
|
||||||
times(&rusage); /* UNIX utility: time in clock ticks */
|
|
||||||
return (double) (rusage.tms_utime)/clk_tck;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
int register i, index, stride, limit, temp;
|
|
||||||
int steps, tsteps, csize;
|
|
||||||
double sec0, sec; /* timing variables */
|
|
||||||
|
|
||||||
clk_tck = sysconf(_SC_CLK_TCK);
|
|
||||||
|
|
||||||
for (csize=CACHE_MIN; csize <= CACHE_MAX; csize=csize*2)
|
|
||||||
for (stride=1; stride <= csize/2; stride=stride*2) {
|
|
||||||
sec = 0; /* initialize timer */
|
|
||||||
limit = csize-stride+1; /* cache size this loop */
|
|
||||||
|
|
||||||
steps = 0;
|
|
||||||
do { /* repeat until collect 1 second */
|
|
||||||
sec0 = get_seconds(); /* start timer */
|
|
||||||
for (i=SAMPLE*stride;i!=0;i=i-1) /* larger sample */
|
|
||||||
for (index=0; index < limit; index=index+stride)
|
|
||||||
x[index] = x[index] + 1; /* cache access */
|
|
||||||
steps = steps + 1; /* count while loop iterations */
|
|
||||||
sec = sec + (get_seconds() - sec0);/* end timer */
|
|
||||||
} while (sec < 1.0); /* until collect 1 second */
|
|
||||||
|
|
||||||
/* Repeat empty loop to loop subtract overhead */
|
|
||||||
tsteps = 0; /* used to match no. while iterations */
|
|
||||||
do { /* repeat until same no. iterations as above */
|
|
||||||
sec0 = get_seconds(); /* start timer */
|
|
||||||
for (i=SAMPLE*stride;i!=0;i=i-1) /* larger sample */
|
|
||||||
for (index=0; index < limit; index=index+stride)
|
|
||||||
temp = temp + index; /* dummy code */
|
|
||||||
tsteps = tsteps + 1; /* count while iterations */
|
|
||||||
sec = sec - (get_seconds() - sec0);/* - overhead */
|
|
||||||
} while (tsteps<steps); /* until = no. iterations */
|
|
||||||
|
|
||||||
printf("Size: %7ld Stride: %7ld read+write: %4.4lf ns\n",
|
|
||||||
csize*sizeof(int), stride*sizeof(int),
|
|
||||||
(double) sec*1e9/(steps*SAMPLE*stride*((limit-1)/stride+1)));
|
|
||||||
}; /* end of both outer for loops */
|
|
||||||
}
|
|
||||||
|
|
BIN
lab12/g1.png
BIN
lab12/g1.png
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
|
@ -1,20 +0,0 @@
|
||||||
import thinkplot
|
|
||||||
import matplotlib.pyplot as pyplot
|
|
||||||
|
|
||||||
d = {}
|
|
||||||
for line in open('data'):
|
|
||||||
t = line.split()
|
|
||||||
size, stride, time = int(t[1]), int(t[3]), float(t[5])
|
|
||||||
d.setdefault(stride, []).append((size, time))
|
|
||||||
|
|
||||||
|
|
||||||
thinkplot.PrePlot(num=7)
|
|
||||||
for stride in sorted(d.keys()):
|
|
||||||
if stride >= 512: continue
|
|
||||||
|
|
||||||
xs, ys = zip(*d[stride])
|
|
||||||
thinkplot.plot(xs, ys, label=str(stride))
|
|
||||||
print stride, len(d[stride])
|
|
||||||
|
|
||||||
pyplot.xscale('log', basex=2)
|
|
||||||
thinkplot.show(xlabel='size (B)', ylabel='access time (ns)')
|
|
|
@ -1,504 +0,0 @@
|
||||||
"""This file contains code for use with "Think Stats",
|
|
||||||
by Allen B. Downey, available from greenteapress.com
|
|
||||||
|
|
||||||
Copyright 2010 Allen B. Downey
|
|
||||||
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
import matplotlib
|
|
||||||
import matplotlib.pyplot as pyplot
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# customize some matplotlib attributes
|
|
||||||
#matplotlib.rc('figure', figsize=(4, 3))
|
|
||||||
|
|
||||||
#matplotlib.rc('font', size=14.0)
|
|
||||||
#matplotlib.rc('axes', labelsize=22.0, titlesize=22.0)
|
|
||||||
#matplotlib.rc('legend', fontsize=20.0)
|
|
||||||
|
|
||||||
#matplotlib.rc('xtick.major', size=6.0)
|
|
||||||
#matplotlib.rc('xtick.minor', size=3.0)
|
|
||||||
|
|
||||||
#matplotlib.rc('ytick.major', size=6.0)
|
|
||||||
#matplotlib.rc('ytick.minor', size=3.0)
|
|
||||||
|
|
||||||
|
|
||||||
class Brewer(object):
|
|
||||||
"""Encapsulates a nice sequence of colors.
|
|
||||||
|
|
||||||
Shades of blue that look good in color and can be distinguished
|
|
||||||
in grayscale (up to a point).
|
|
||||||
|
|
||||||
Borrowed from http://colorbrewer2.org/
|
|
||||||
"""
|
|
||||||
color_iter = None
|
|
||||||
|
|
||||||
colors = ['#081D58',
|
|
||||||
'#253494',
|
|
||||||
'#225EA8',
|
|
||||||
'#1D91C0',
|
|
||||||
'#41B6C4',
|
|
||||||
'#7FCDBB',
|
|
||||||
'#C7E9B4',
|
|
||||||
'#EDF8B1',
|
|
||||||
'#FFFFD9']
|
|
||||||
|
|
||||||
# lists that indicate which colors to use depending on how many are used
|
|
||||||
which_colors = [[],
|
|
||||||
[1],
|
|
||||||
[1, 3],
|
|
||||||
[0, 2, 4],
|
|
||||||
[0, 2, 4, 6],
|
|
||||||
[0, 2, 3, 5, 6],
|
|
||||||
[0, 2, 3, 4, 5, 6],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6],
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def Colors(cls):
|
|
||||||
"""Returns the list of colors.
|
|
||||||
"""
|
|
||||||
return cls.colors
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def ColorGenerator(cls, n):
|
|
||||||
"""Returns an iterator of color strings.
|
|
||||||
|
|
||||||
n: how many colors will be used
|
|
||||||
"""
|
|
||||||
for i in cls.which_colors[n]:
|
|
||||||
yield cls.colors[i]
|
|
||||||
raise StopIteration('Ran out of colors in Brewer.ColorGenerator')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def InitializeIter(cls, num):
|
|
||||||
"""Initializes the color iterator with the given number of colors."""
|
|
||||||
cls.color_iter = cls.ColorGenerator(num)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def ClearIter(cls):
|
|
||||||
"""Sets the color iterator to None."""
|
|
||||||
cls.color_iter = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def GetIter(cls):
|
|
||||||
"""Gets the color iterator."""
|
|
||||||
return cls.color_iter
|
|
||||||
|
|
||||||
|
|
||||||
def PrePlot(num=None, rows=1, cols=1):
|
|
||||||
"""Takes hints about what's coming.
|
|
||||||
|
|
||||||
num: number of lines that will be plotted
|
|
||||||
"""
|
|
||||||
if num:
|
|
||||||
Brewer.InitializeIter(num)
|
|
||||||
|
|
||||||
# TODO: get sharey and sharex working. probably means switching
|
|
||||||
# to subplots instead of subplot.
|
|
||||||
# also, get rid of the gray background.
|
|
||||||
|
|
||||||
if rows > 1 or cols > 1:
|
|
||||||
pyplot.subplots(rows, cols, sharey=True)
|
|
||||||
global SUBPLOT_ROWS, SUBPLOT_COLS
|
|
||||||
SUBPLOT_ROWS = rows
|
|
||||||
SUBPLOT_COLS = cols
|
|
||||||
|
|
||||||
|
|
||||||
def SubPlot(rows, cols, plot_number):
|
|
||||||
"""Configures the number of subplots and changes the current plot.
|
|
||||||
|
|
||||||
rows: int
|
|
||||||
cols: int
|
|
||||||
plot_number: int
|
|
||||||
"""
|
|
||||||
pyplot.subplot(rows, cols, plot_number)
|
|
||||||
|
|
||||||
|
|
||||||
class InfiniteList(list):
|
|
||||||
"""A list that returns the same value for all indices."""
|
|
||||||
def __init__(self, val):
|
|
||||||
"""Initializes the list.
|
|
||||||
|
|
||||||
val: value to be stored
|
|
||||||
"""
|
|
||||||
list.__init__(self)
|
|
||||||
self.val = val
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
"""Gets the item with the given index.
|
|
||||||
|
|
||||||
index: int
|
|
||||||
|
|
||||||
returns: the stored value
|
|
||||||
"""
|
|
||||||
return self.val
|
|
||||||
|
|
||||||
|
|
||||||
def Underride(d, **options):
|
|
||||||
"""Add key-value pairs to d only if key is not in d.
|
|
||||||
|
|
||||||
If d is None, create a new dictionary.
|
|
||||||
|
|
||||||
d: dictionary
|
|
||||||
options: keyword args to add to d
|
|
||||||
"""
|
|
||||||
if d is None:
|
|
||||||
d = {}
|
|
||||||
|
|
||||||
for key, val in options.iteritems():
|
|
||||||
d.setdefault(key, val)
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def Clf():
|
|
||||||
"""Clears the figure and any hints that have been set."""
|
|
||||||
Brewer.ClearIter()
|
|
||||||
pyplot.clf()
|
|
||||||
|
|
||||||
|
|
||||||
def Figure(**options):
|
|
||||||
"""Sets options for the current figure."""
|
|
||||||
Underride(options, figsize=(6, 8))
|
|
||||||
pyplot.figure(**options)
|
|
||||||
|
|
||||||
|
|
||||||
def Plot(xs, ys, style='', **options):
|
|
||||||
"""Plots a line.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xs: sequence of x values
|
|
||||||
ys: sequence of y values
|
|
||||||
style: style string passed along to pyplot.plot
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
"""
|
|
||||||
color_iter = Brewer.GetIter()
|
|
||||||
|
|
||||||
if color_iter:
|
|
||||||
try:
|
|
||||||
options = Underride(options, color=color_iter.next())
|
|
||||||
except StopIteration:
|
|
||||||
print 'Warning: Brewer ran out of colors.'
|
|
||||||
Brewer.ClearIter()
|
|
||||||
|
|
||||||
options = Underride(options, linewidth=3, alpha=0.8)
|
|
||||||
pyplot.plot(xs, ys, style, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Scatter(xs, ys, **options):
|
|
||||||
"""Makes a scatter plot.
|
|
||||||
|
|
||||||
xs: x values
|
|
||||||
ys: y values
|
|
||||||
options: options passed to pyplot.scatter
|
|
||||||
"""
|
|
||||||
options = Underride(options, color='blue', alpha=0.2,
|
|
||||||
s=30, edgecolors='none')
|
|
||||||
pyplot.scatter(xs, ys, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Pmf(pmf, **options):
|
|
||||||
"""Plots a Pmf or Hist as a line.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pmf: Hist or Pmf object
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
"""
|
|
||||||
xs, ps = pmf.Render()
|
|
||||||
if pmf.name:
|
|
||||||
options = Underride(options, label=pmf.name)
|
|
||||||
Plot(xs, ps, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Pmfs(pmfs, **options):
|
|
||||||
"""Plots a sequence of PMFs.
|
|
||||||
|
|
||||||
Options are passed along for all PMFs. If you want different
|
|
||||||
options for each pmf, make multiple calls to Pmf.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pmfs: sequence of PMF objects
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
"""
|
|
||||||
for pmf in pmfs:
|
|
||||||
Pmf(pmf, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Hist(hist, **options):
|
|
||||||
"""Plots a Pmf or Hist with a bar plot.
|
|
||||||
|
|
||||||
The default width of the bars is based on the minimum difference
|
|
||||||
between values in the Hist. If that's too small, you can override
|
|
||||||
it by providing a width keyword argument, in the same units
|
|
||||||
as the values.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
hist: Hist or Pmf object
|
|
||||||
options: keyword args passed to pyplot.bar
|
|
||||||
"""
|
|
||||||
# find the minimum distance between adjacent values
|
|
||||||
xs, fs = hist.Render()
|
|
||||||
width = min(Diff(xs))
|
|
||||||
|
|
||||||
if hist.name:
|
|
||||||
options = Underride(options, label=hist.name)
|
|
||||||
|
|
||||||
options = Underride(options,
|
|
||||||
align='center',
|
|
||||||
linewidth=0,
|
|
||||||
width=width)
|
|
||||||
|
|
||||||
pyplot.bar(xs, fs, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Hists(hists, **options):
|
|
||||||
"""Plots two histograms as interleaved bar plots.
|
|
||||||
|
|
||||||
Options are passed along for all PMFs. If you want different
|
|
||||||
options for each pmf, make multiple calls to Pmf.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
hists: list of two Hist or Pmf objects
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
"""
|
|
||||||
for hist in hists:
|
|
||||||
Hist(hist, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Diff(t):
|
|
||||||
"""Compute the differences between adjacent elements in a sequence.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
t: sequence of number
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
sequence of differences (length one less than t)
|
|
||||||
"""
|
|
||||||
diffs = [t[i+1] - t[i] for i in range(len(t)-1)]
|
|
||||||
return diffs
|
|
||||||
|
|
||||||
|
|
||||||
def Cdf(cdf, complement=False, transform=None, **options):
|
|
||||||
"""Plots a CDF as a line.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cdf: Cdf object
|
|
||||||
complement: boolean, whether to plot the complementary CDF
|
|
||||||
transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dictionary with the scale options that should be passed to
|
|
||||||
Config, Show or Save.
|
|
||||||
"""
|
|
||||||
xs, ps = cdf.Render()
|
|
||||||
scale = dict(xscale='linear', yscale='linear')
|
|
||||||
|
|
||||||
for s in ['xscale', 'yscale']:
|
|
||||||
if s in options:
|
|
||||||
scale[s] = options.pop(s)
|
|
||||||
|
|
||||||
if transform == 'exponential':
|
|
||||||
complement = True
|
|
||||||
scale['yscale'] = 'log'
|
|
||||||
|
|
||||||
if transform == 'pareto':
|
|
||||||
complement = True
|
|
||||||
scale['yscale'] = 'log'
|
|
||||||
scale['xscale'] = 'log'
|
|
||||||
|
|
||||||
if complement:
|
|
||||||
ps = [1.0-p for p in ps]
|
|
||||||
|
|
||||||
if transform == 'weibull':
|
|
||||||
xs.pop()
|
|
||||||
ps.pop()
|
|
||||||
ps = [-math.log(1.0-p) for p in ps]
|
|
||||||
scale['xscale'] = 'log'
|
|
||||||
scale['yscale'] = 'log'
|
|
||||||
|
|
||||||
if transform == 'gumbel':
|
|
||||||
xs.pop(0)
|
|
||||||
ps.pop(0)
|
|
||||||
ps = [-math.log(p) for p in ps]
|
|
||||||
scale['yscale'] = 'log'
|
|
||||||
|
|
||||||
if cdf.name:
|
|
||||||
options = Underride(options, label=cdf.name)
|
|
||||||
|
|
||||||
Plot(xs, ps, **options)
|
|
||||||
return scale
|
|
||||||
|
|
||||||
|
|
||||||
def Cdfs(cdfs, complement=False, transform=None, **options):
|
|
||||||
"""Plots a sequence of CDFs.
|
|
||||||
|
|
||||||
cdfs: sequence of CDF objects
|
|
||||||
complement: boolean, whether to plot the complementary CDF
|
|
||||||
transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'
|
|
||||||
options: keyword args passed to pyplot.plot
|
|
||||||
"""
|
|
||||||
for cdf in cdfs:
|
|
||||||
Cdf(cdf, complement, transform, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Contour(obj, pcolor=False, contour=True, imshow=False, **options):
|
|
||||||
"""Makes a contour plot.
|
|
||||||
|
|
||||||
d: map from (x, y) to z, or object that provides GetDict
|
|
||||||
pcolor: boolean, whether to make a pseudocolor plot
|
|
||||||
contour: boolean, whether to make a contour plot
|
|
||||||
imshow: boolean, whether to use pyplot.imshow
|
|
||||||
options: keyword args passed to pyplot.pcolor and/or pyplot.contour
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
d = obj.GetDict()
|
|
||||||
except AttributeError:
|
|
||||||
d = obj
|
|
||||||
|
|
||||||
Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)
|
|
||||||
|
|
||||||
xs, ys = zip(*d.iterkeys())
|
|
||||||
xs = sorted(set(xs))
|
|
||||||
ys = sorted(set(ys))
|
|
||||||
|
|
||||||
X, Y = np.meshgrid(xs, ys)
|
|
||||||
func = lambda x, y: d.get((x, y), 0)
|
|
||||||
func = np.vectorize(func)
|
|
||||||
Z = func(X, Y)
|
|
||||||
|
|
||||||
x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)
|
|
||||||
axes = pyplot.gca()
|
|
||||||
axes.xaxis.set_major_formatter(x_formatter)
|
|
||||||
|
|
||||||
if pcolor:
|
|
||||||
pyplot.pcolormesh(X, Y, Z, **options)
|
|
||||||
if contour:
|
|
||||||
cs = pyplot.contour(X, Y, Z, **options)
|
|
||||||
pyplot.clabel(cs, inline=1, fontsize=10)
|
|
||||||
if imshow:
|
|
||||||
extent = xs[0], xs[-1], ys[0], ys[-1]
|
|
||||||
pyplot.imshow(Z, extent=extent, **options)
|
|
||||||
|
|
||||||
|
|
||||||
def Pcolor(xs, ys, zs, pcolor=True, contour=False, **options):
|
|
||||||
"""Makes a pseudocolor plot.
|
|
||||||
|
|
||||||
xs:
|
|
||||||
ys:
|
|
||||||
zs:
|
|
||||||
pcolor: boolean, whether to make a pseudocolor plot
|
|
||||||
contour: boolean, whether to make a contour plot
|
|
||||||
options: keyword args passed to pyplot.pcolor and/or pyplot.contour
|
|
||||||
"""
|
|
||||||
Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)
|
|
||||||
|
|
||||||
X, Y = np.meshgrid(xs, ys)
|
|
||||||
Z = zs
|
|
||||||
|
|
||||||
x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)
|
|
||||||
axes = pyplot.gca()
|
|
||||||
axes.xaxis.set_major_formatter(x_formatter)
|
|
||||||
|
|
||||||
if pcolor:
|
|
||||||
pyplot.pcolormesh(X, Y, Z, **options)
|
|
||||||
|
|
||||||
if contour:
|
|
||||||
cs = pyplot.contour(X, Y, Z, **options)
|
|
||||||
pyplot.clabel(cs, inline=1, fontsize=10)
|
|
||||||
|
|
||||||
|
|
||||||
def Config(**options):
|
|
||||||
"""Configures the plot.
|
|
||||||
|
|
||||||
Pulls options out of the option dictionary and passes them to
|
|
||||||
the corresponding pyplot functions.
|
|
||||||
"""
|
|
||||||
names = ['title', 'xlabel', 'ylabel', 'xscale', 'yscale',
|
|
||||||
'xticks', 'yticks', 'axis']
|
|
||||||
|
|
||||||
for name in names:
|
|
||||||
if name in options:
|
|
||||||
getattr(pyplot, name)(options[name])
|
|
||||||
|
|
||||||
loc = options.get('loc', 0)
|
|
||||||
legend = options.get('legend', True)
|
|
||||||
if legend:
|
|
||||||
pyplot.legend(loc=loc)
|
|
||||||
|
|
||||||
|
|
||||||
def Show(**options):
|
|
||||||
"""Shows the plot.
|
|
||||||
|
|
||||||
For options, see Config.
|
|
||||||
|
|
||||||
options: keyword args used to invoke various pyplot functions
|
|
||||||
"""
|
|
||||||
# TODO: figure out how to show more than one plot
|
|
||||||
Config(**options)
|
|
||||||
pyplot.show()
|
|
||||||
|
|
||||||
|
|
||||||
def Save(root=None, formats=None, **options):
|
|
||||||
"""Saves the plot in the given formats.
|
|
||||||
|
|
||||||
For options, see Config.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
root: string filename root
|
|
||||||
formats: list of string formats
|
|
||||||
options: keyword args used to invoke various pyplot functions
|
|
||||||
"""
|
|
||||||
Config(**options)
|
|
||||||
|
|
||||||
if formats is None:
|
|
||||||
formats = ['pdf', 'eps']
|
|
||||||
|
|
||||||
if root:
|
|
||||||
for fmt in formats:
|
|
||||||
SaveFormat(root, fmt)
|
|
||||||
Clf()
|
|
||||||
|
|
||||||
|
|
||||||
def SaveFormat(root, fmt='eps'):
|
|
||||||
"""Writes the current figure to a file in the given format.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
root: string filename root
|
|
||||||
fmt: string format
|
|
||||||
"""
|
|
||||||
filename = '%s.%s' % (root, fmt)
|
|
||||||
print 'Writing', filename
|
|
||||||
pyplot.savefig(filename, format=fmt, dpi=300)
|
|
||||||
|
|
||||||
|
|
||||||
# provide aliases for calling functons with lower-case names
|
|
||||||
preplot = PrePlot
|
|
||||||
subplot = SubPlot
|
|
||||||
clf = Clf
|
|
||||||
figure = Figure
|
|
||||||
plot = Plot
|
|
||||||
scatter = Scatter
|
|
||||||
pmf = Pmf
|
|
||||||
pmfs = Pmfs
|
|
||||||
hist = Hist
|
|
||||||
hists = Hists
|
|
||||||
diff = Diff
|
|
||||||
cdf = Cdf
|
|
||||||
cdfs = Cdfs
|
|
||||||
contour = Contour
|
|
||||||
pcolor = Pcolor
|
|
||||||
config = Config
|
|
||||||
show = Show
|
|
||||||
save = Save
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
color_iter = Brewer.ColorGenerator(7)
|
|
||||||
for color in color_iter:
|
|
||||||
print color
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,35 +0,0 @@
|
||||||
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 ()
|
|
|
@ -1,38 +0,0 @@
|
||||||
# Лабораторная работа №8
|
|
||||||
|
|
||||||
> Разработка сервера с использованием цикла ожидания событий
|
|
||||||
|
|
||||||
На сервере создаётся и биндится сокет, слушается клиент. Далее в бесконечном цикле с
|
|
||||||
использованием `select()` обрабатываются запросы (с возможностью подключения нескольких
|
|
||||||
клиентов)
|
|
||||||
|
|
||||||
Для каждого запроса обрабатывается запрос в plain-text-формате, возвращается ответ в том же формате.
|
|
||||||
|
|
||||||
Для асинхронной работы использованы неблокирующие сокеты.
|
|
||||||
Их отличие в том, что во время выполнения операции программа не блокирует своё исполнение.
|
|
||||||
|
|
||||||
Используя функцию `fcntl()`, сокеты переводятся в неблокирующий режим.
|
|
||||||
Вызов любой функции с таким сокетом будет возвращать управление немедленно.
|
|
||||||
|
|
||||||
> Добавить обработку сигналов
|
|
||||||
|
|
||||||
Для обработки сигналов используем системный вызов `signal()`.
|
|
||||||
|
|
||||||
Существуют и другие подходы для создания сервера с циклом ожидания событий. Например, с использованием `fork()`,
|
|
||||||
как это делается, например, в Apache2, где для обработки клиентов заранее делается массив форков (т.к. форк --
|
|
||||||
дорогостоящая операция).
|
|
||||||
Этот способ подразумевает создание дочернего процесса для обслуживания каждого нового клиента.
|
|
||||||
При этом родительский процесс занимается только прослушиванием порта и приёмом соединений.
|
|
||||||
|
|
||||||
Но у такого подхода есть минусы.
|
|
||||||
|
|
||||||
1. Если клиентов очень много, создание нового процесса для обслуживания каждого из них может оказаться слишком
|
|
||||||
дорогостоящей операцией (решается пулом пре-форков).
|
|
||||||
2. Такой способ неявно подразумевает, что все клиенты обслуживаются независимо друг от друга.
|
|
||||||
|
|
||||||
Для решения этих проблем мы можем использовать `select()`.
|
|
||||||
Его плюсы заключаются в разрешении вышеописанных проблем.
|
|
||||||
|
|
||||||
Минусы заключаются в том, что программы получаются более сложными по сравнению с первым способом,
|
|
||||||
а также программа вынуждена отслеживать дескрипторы всех клиентов и работать с ними параллельно
|
|
||||||
и каждый раз нужно пробегаться в цикле по дескрипторам всех сокетов и проверять, произошло ли какое-либо событие.
|
|
|
@ -1,93 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#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
|
|
143
lab8/server.c
143
lab8/server.c
|
@ -1,143 +0,0 @@
|
||||||
#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