Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Системное программирование
Лекция 7:
IPC. Pipe, FIFO. XSI и POSIX. Сокеты: доменные, обычные.
The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.
Дедлайны:
int
pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
int
pthread_mutex_lock(pthread_mutex_t *mutex);
int
pthread_mutex_unlock(pthread_mutex_t *mutex);
int
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int
pthread_cond_signal(pthread_cond_t *cond);
int
pthread_cond_broadcast(pthread_cond_t *cond);
Задача
.text
.data
.bss
.stack
.heap
Pipe
fdtable
fd[1]
fdtable
fd[0]
Процесс 1
Процесс 2
int
pipe(int fildes[2]);
Mmap
Процесс 1
Процесс 2
write
read
read
write
read
write
VMA
void *
mmap(void *addr, size_t len,
int prot, int flags,
int fd, off_t offset);
struct worker {
int *array;
int size;
};
int
cmp(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
void
sorter(struct worker *worker, const char *filename)
{
FILE *file = fopen(filename, "r");
int size = 0;
int capacity = 1024;
int *array = malloc(capacity * sizeof(int));
while (fscanf(file, "%d", &array[size]) > 0) {
++size;
if (size == capacity) {
capacity *= 2;
array = realloc(array, capacity * sizeof(int));
}
}
qsort(array, size, sizeof(int), cmp);
fclose(file);
worker->array = array;
worker->size = size;
}
int
main(int argc, const char **argv)
{
struct timeval start;
gettimeofday(&start, NULL);
int nfiles = argc - 1;
struct worker *workers = malloc(sizeof(struct worker) * nfiles);
struct worker *w = workers;
int total_size = 0;
for (int i = 0; i < nfiles; ++i, ++w) {
sorter(w, argv[i + 1]);
total_size += w->size;
}
int *total_array = malloc(total_size * sizeof(int));
int *pos = total_array;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
memcpy(pos, w->array, w->size * sizeof(int));
pos += w->size;
}
struct timeval tmp;
gettimeofday(&tmp, NULL);
uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec -
start.tv_sec * 1000000 + start.tv_usec;
printf("presort time = %lf\n", (microsecs + 0.0) / 1000000);
return 0;
}
Описание сортировки одного файла
Выполнение чтения из одного файла и сортировка
int
main(int argc, const char **argv)
{
struct timeval start;
gettimeofday(&start, NULL);
int nfiles = argc - 1;
struct worker *workers = malloc(sizeof(struct worker) * nfiles);
struct worker *w = workers;
int total_size = 0;
for (int i = 0; i < nfiles; ++i, ++w) {
sorter(w, argv[i + 1]);
total_size += w->size;
}
int *total_array = malloc(total_size * sizeof(int));
int *pos = total_array;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
memcpy(pos, w->array, w->size * sizeof(int));
pos += w->size;
}
struct timeval tmp;
gettimeofday(&tmp, NULL);
uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec -
start.tv_sec * 1000000 + start.tv_usec;
printf("presort time = %lf\n", (microsecs + 0.0) / 1000000);
return 0;
}
Сортировка каждого файла в свой worker
Замеры и печать времени выполнения
Объединение в один массив
Сортировка одним процессом
4 файла по 20 000 000 чисел
~3.5 сек
struct worker {
int fd[2];
int *array;
int size;
int id;
};
void
sorter(struct worker *worker, const char *filename)
{
close(worker->fd[0]);
FILE *file = fopen(filename, "r");
int size = 0;
int capacity = 1024;
int *array = malloc(capacity * sizeof(int));
while (fscanf(file, "%d", &array[size]) > 0) {
++size;
if (size == capacity) {
capacity *= 2;
array = realloc(array, capacity * sizeof(int));
}
}
qsort(array, size, sizeof(int), cmp);
fclose(file);
printf("Worker %d sorted %d numbers\n", worker->id, size);
write(worker->fd[1], &size, sizeof(size));
write(worker->fd[1], array, sizeof(int) * size);
close(worker->fd[1]);
free(array);
}
int
main(int argc, const char **argv)
{
struct timeval start;
gettimeofday(&start, NULL);
int nfiles = argc - 1;
struct worker *workers = malloc(sizeof(struct worker) * nfiles);
struct worker *w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
pipe(w->fd);
w->id = i;
if (fork() == 0) {
sorter(w, argv[i + 1]);
free(workers);
return 0;
}
close(w->fd[1]);
}
int total_size = 0;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
read_from_pipe(w->fd[0], (char *) &w->size, sizeof(w->size));
w->array = malloc(w->size * sizeof(int));
read_from_pipe(w->fd[0], (char *) w->array,
w->size * sizeof(int));
printf("Got %d numbers from worker %d\n", w->size, w->id);
close(w->fd[0]);
wait(NULL);
total_size += w->size;
}
int *total_array = malloc(total_size * sizeof(int));
int *pos = total_array;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
memcpy(pos, w->array, w->size * sizeof(int));
pos += w->size;
}
struct timeval tmp;
gettimeofday(&tmp, NULL);
uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec -
start.tv_sec * 1000000 + start.tv_usec;
printf("presort time = %lf\n", (microsecs + 0.0) / 1000000);
return 0;
}
void
read_from_pipe(int fd, char *in, int size)
{
int total = 0;
int rc = read(fd, in, size);
while (rc > 0 && size > 0) {
size -= rc;
in += rc;
total += rc;
rc = read(fd, in, size);
}
}
Pipe для передачи данных родителю, и id для печати
Закрыть неиспользуемый конец pipe
Родитель слушает свой fd[0]. Передаем туда через fd[1] готовый результат. Сначала кол-во чисел, потом сами числа
Эта функция выполняется в отдельном процессе
int
main(int argc, const char **argv)
{
struct timeval start;
gettimeofday(&start, NULL);
int nfiles = argc - 1;
struct worker *workers = malloc(sizeof(struct worker) * nfiles);
struct worker *w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
pipe(w->fd);
w->id = i;
if (fork() == 0) {
sorter(w, argv[i + 1]);
free(workers);
return 0;
}
close(w->fd[1]);
}
int total_size = 0;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
read_from_pipe(w->fd[0], (char *) &w->size, sizeof(w->size));
w->array = malloc(w->size * sizeof(int));
read_from_pipe(w->fd[0], (char *) w->array,
w->size * sizeof(int));
printf("Got %d numbers from worker %d\n", w->size, w->id);
close(w->fd[0]);
wait(NULL);
total_size += w->size;
}
Процесс создает рабочих, результат которых может читать через w->fd[0]
w->fd[1] закрывается за ненадобностью. Остается однонаправленный канал из сына в родителя
Прочитать результат из сыновьего процесса и подождать его завершения
int *total_array = malloc(total_size * sizeof(int));
int *pos = total_array;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
memcpy(pos, w->array, w->size * sizeof(int));
pos += w->size;
}
struct timeval tmp;
gettimeofday(&tmp, NULL);
uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec -
start.tv_sec * 1000000 + start.tv_usec;
printf("presort time = %lf\n", (microsecs + 0.0) / 1000000);
return 0;
}
void
read_from_pipe(int fd, char *in, int size)
{
int total = 0;
int rc = read(fd, in, size);
while (rc > 0 && size > 0) {
size -= rc;
in += rc;
total += rc;
rc = read(fd, in, size);
}
}
Объединить массивы в один
Посчитать время
Большие объемы данных читаются в несколько вызовов read()
Сортировка многими процессами
4 файла по 20 000 000 чисел
~2.3 сек
4 процесса
х 1.5
#define MEM_SIZE 65536
#define IS_READABLE 0
#define IS_WRITABLE 1
#define MEM_META_SIZE 2
struct worker {
char *mem;
int *array;
int size;
int id;
};
Родитель с каждым сыном делит память размера 65536 байт
Первые два байта зарезервированы для синхронизации чтения/записи
void
write_to_shared_mem(char *mem, const char *src, int size)
{
volatile char *is_readable = &mem[IS_READABLE];
volatile char *is_writable = &mem[IS_WRITABLE];
mem += MEM_META_SIZE;
int mem_size = MEM_SIZE - MEM_META_SIZE;
int saved_size = mem_size;
char *saved_mem = mem;
while (1) {
while (! *is_writable)
sched_yield();
int to_copy = mem_size > size ? size : mem_size;
memcpy(mem, src, to_copy);
size -= to_copy;
mem_size -= to_copy;
mem += to_copy;
src += to_copy;
*is_writable = 0;
*is_readable = 1;
if (size == 0)
break;
mem = saved_mem;
mem_size = saved_size;
}
}
Первые два байта зарезервированы для синхронизации чтения/записи
Ждем, пока читатель уйдет из памяти
Пишем сколько влезет
Если за раз не записалось, то ждем, пока все вычитают и пишем еще
void
read_from_shared_mem(char *mem, char *dst, int size)
{
volatile char *is_readable = &mem[IS_READABLE];
volatile char *is_writable = &mem[IS_WRITABLE];
mem += MEM_META_SIZE;
int mem_size = MEM_SIZE - MEM_META_SIZE;
int saved_size = mem_size;
char *saved_mem = mem;
while (1) {
while (! *is_readable)
sched_yield();
int to_copy = mem_size > size ? size : mem_size;
memcpy(dst, mem, to_copy);
size -= to_copy;
mem_size -= to_copy;
mem += to_copy;
dst += to_copy;
*is_readable = 0;
*is_writable = 1;
if (size == 0)
break;
mem = saved_mem;
mem_size = saved_size;
}
}
Ждем, пока писатель уйдет из памяти
Читаем сколько влезет
Если за раз не прочиталось, то ждем, пока все напишут еще
void
sorter(struct worker *worker, const char *filename)
{
FILE *file = fopen(filename, "r");
int size = 0;
int capacity = 1024;
int *array = malloc(capacity * sizeof(int));
while (fscanf(file, "%d", &array[size]) > 0) {
++size;
if (size == capacity) {
capacity *= 2;
array = realloc(array, capacity * sizeof(int));
}
}
qsort(array, size, sizeof(int), cmp);
fclose(file);
printf("Worker %d sorted %d numbers\n", worker->id, size);
write_to_shared_mem(worker->mem, (char *) &size, sizeof(size));
write_to_shared_mem(worker->mem, (char *) array, sizeof(int) * size);
free(array);
}
Сначала отправляем родителю количество чисел, чтобы он успел сделать malloc. Затем сами числа
int
main(int argc, const char **argv)
{
struct timeval start;
gettimeofday(&start, NULL);
int nfiles = argc - 1;
struct worker *workers = malloc(sizeof(struct worker) * nfiles);
struct worker *w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
w->id = i;
w->mem = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_SHARED, -1, 0);
w->mem[IS_READABLE] = 0;
w->mem[IS_WRITABLE] = 1;
if (fork() == 0) {
sorter(w, argv[i + 1]);
free(workers);
return 0;
}
}
int total_size = 0;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
read_from_shared_mem(w->mem, (char *) &w->size,
sizeof(w->size));
w->array = malloc(w->size * sizeof(int));
read_from_shared_mem(w->mem, (char *) w->array,
w->size * sizeof(int));
printf("Got %d numbers from worker %d\n", w->size, w->id);
wait(NULL);
total_size += w->size;
}
Каждый рабочий процесс получает свою память, разделяемую с родителем
Читаем из рабочего сначала кол-во чисел, потом числа
int *total_array = malloc(total_size * sizeof(int));
int *pos = total_array;
w = workers;
for (int i = 0; i < nfiles; ++i, ++w) {
memcpy(pos, w->array, w->size * sizeof(int));
pos += w->size;
}
struct timeval tmp;
gettimeofday(&tmp, NULL);
uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec -
start.tv_sec * 1000000 + start.tv_usec;
printf("presort time = %lf\n", (microsecs + 0.0) / 1000000);
return 0;
}
Объединение чисел в один массив
Подсчет времени
void
write_to_shared_mem(char *mem, ...)
{
volatile char *is_readable = &mem[IS_READABLE];
volatile char *is_writable = &mem[IS_WRITABLE];
/* ... */
void
read_from_shared_mem(char *mem, ...)
{
volatile char *is_readable = &mem[IS_READABLE];
volatile char *is_writable = &mem[IS_WRITABLE];
/* ... */
Почему в mmap реализации флаги синхронизации volatile?
Чтобы компилятор не выкинул эти переменные. Они могут поменяться из другого процесса
1 балл
init/lauchd
Дерево родства процессов
Не могут иметь общих анонимных ресурсов. Как взаимодействовать?
Могут делить anon mmap, pipe
int
mkfifo(const char *path, mode_t mode);
Процесс 1
Процесс 2
read
write
read
write
FIFO
Процесс N
read
write
Процесс 3
read
write
...
int main()
{
mkfifo("/tmp/fifo_server", S_IRWXU | S_IRWXO);
int fd = open("/tmp/fifo_server", O_RDONLY);
while (1) {
pid_t new_client;
if (read(fd, &new_client, sizeof(pid_t)) > 0)
printf("new client %d\n", (int) new_client);
else
sched_yield();
}
}
Сервер создает FIFO канал с правами на чтение/запись в него кому угодно
После создания канала с ним можно работать как с файлом, но данные он на диске не хранит - все в ОП в ядре
Клиенты пишут в канал свои pid, а сервер их вычитывает и печатает
int main()
{
int fd = open("/tmp/fifo_server", O_WRONLY);
pid_t pid = getpid();
write(fd, &pid, sizeof(pid));
printf("my pid %d is sent to server\n", (int) pid);
close(fd);
return 0;
}
Клиент открывает FIFO, как обычный файл
Пишет туда pid
$> gcc 4_fifo_server.c -o server
$> ./server
$> gcc 4_fifo_client.c -o client
$> ./client
my pid 62686 is sent to server
new client 62686
$> ./client
my pid 62690 is sent to server
new client 62690
$> ./client
my pid 62692 is sent to server
new client 62692
$> ls -al /tmp/fifo_server
prwx---r-x 1 v.shpilevoy /tmp/fifo_server
$> echo '123' > /tmp/fifo_server
...
$> cat /tmp/fifo_server
123
$>
$>
X/Open System Interfaces
Стандарт именованных IPC объектов
int
msgget(key_t key, int msgflg);
int
semget(key_t key, int nsems, int semflg);
int
shmget(key_t key, size_t size, int shmflg);
Именование не строками, а особым типом key_t
key_t
ftok(const char *path, int id);
key_t
ftok(const char *pathname, int proj_id)
{
struct stat st;
if (stat(pathname, &st) < 0)
return (key_t) -1;
key_t key = ((st.st_ino & 0xffff) | ((st.st_dev & 0xff) << 16)
| ((proj_id & 0xff) << 24));
return key;
}
Ключ генерируется по пути в файловой системе и произвольному числу
void
try_key(const char *path, int id)
{
key_t key = ftok(path, id);
printf("key(\"%s\", %d) = ", path, id);
if (key == -1)
printf("%s\n", strerror(errno));
else
printf("%d\n", (int) key);
}
int main(int argc, const char **argv)
{
try_key("/not/exising/path", 100);
try_key(argv[0], 100);
try_key(argv[0], 101);
try_key(argv[0], 101 + 1024);
return 0;
}
$> gcc 5_key_t.c
$> ./a.out
$> key("/not/exising/path", 100) =
No such file or directory
key("./a.out", 100) = 1678028750
key("./a.out", 101) = 1694805966
key("./a.out", 1125) = 1694805966
От второго аргумента используются только 8 бит
Путь в ftok - это временный файл; ваш исполняемый файл (argv[0]); что-то еще. Файл должен существовать. Второй аргумент - для разрешения коллизий.
Процесс 1
Процесс 2
write
read
Type t1
Type t2
Type t3
Type tN
int
msgget(key_t key, int msgflg);
int
msgctl(int msqid, int cmd, struct msqid_ds *buf);
int
msgsnd(int msqid, const void *msgp, size_t msgsz,
int msgflg);
ssize_t
msgrcv(int msqid, void *msgp, size_t msgsz,
long msgtyp, int msgflg);
Создание/подключение к очереди
Удаление очереди, сбор информации о ней
Отправка в очередь и чтение из нее
При чтении можно выбрать тип сообщения
Тип кладется в тело сообщения при отправке в первые несколько байт
$> cat /proc/sys/kernel/msgmax
8192
$> cat /proc/sys/kernel/msgmnb
16384
Максимальные размер сообщения и очереди, в байтах
int main(int argc, const char **argv)
{
key_t key = ftok(argv[0], 0);
int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO);
printf("key = %d, queue_id = %d\n", (int) key, queue_id);
struct msqid_ds queue_stat;
msgctl(queue_id, IPC_STAT, &queue_stat);
printf("message count = %d\n", (int) queue_stat.msg_qnum);
printf("max queue bytes = %d\n", (int) queue_stat.msg_qbytes);
int rc = msgget(key, IPC_CREAT | IPC_EXCL);
if (rc == -1)
printf("second msgget returned '%s'\n", strerror(errno));
msgctl(queue_id, IPC_RMID, NULL);
#ifdef IPC_INFO
printf("\nSystem wide settings:\n");
struct msginfo info;
msgctl(0, IPC_INFO, (struct msqid_ds *) &info);
printf("max message size = %d\n", info.msgmax);
printf("max queue bytes = %d\n", info.msgmnb);
printf("max number of message queues = %d\n", info.msgmni);
#endif
return 0;
}
Создаю очередь с возможностью записи/чтения
При помощи msgctl с командой IPC_STAT смотрю ее характеристики
Убеждаюсь, что IPC_EXCL | IPC_CREAT вернет ошибку, если очередь есть
Дропаю очередь при помощи msgctl с командой IPC_RMID
На Linux есть команда IPC_INFO, которой можно смотреть глобальные лимиты
$> gcc 6_msgget.c
$> ./a.out
key = 311359, queue_id = 1245184
message count = 0
max queue bytes = 2048
second msgget returned 'File exists'
Mac
Linux
$> gcc 6_msgget.c
$> ./a.out
key = 3014816, queue_id = 327680
message count = 0
max queue bytes = 16384
second msgget returned 'File exists'
System wide settings:
max message size = 8192
max queue bytes = 16384
max number of message queues = 32000
int
pack_msg(char *msg, long type, const char *data, int data_size)
{
char *pos = msg;
memcpy(pos, &type, sizeof(type));
pos += sizeof(type);
memcpy(pos, &data_size, sizeof(data_size));
pos += sizeof(data_size);
memcpy(pos, data, data_size);
pos += data_size;
return pos - msg;
}
void
send_msg(int queue_id, long type, const char *data,
int data_size)
{
char msg[512];
int size = pack_msg(msg, type, data, data_size);
msgsnd(queue_id, msg, size, 0);
printf("sent %d bytes\n", size);
}
Запаковка сообщения с заданным типом
Тип ложится в первые sizeof(long) байт
Размер данных в следующие sizeof(int) байт
Данные ложатся в хвост
type
size
data
sizeof(long)
sizeof(int)
size
Функция отправки пакует сообщение в локальном буфере и посылает в очередь
int
main(int argc, const char **argv)
{
key_t key = ftok("./server", 0);
int queue_id = msgget(key, 0);
printf("connected to queue %d\n", queue_id);
const char *text = "hello, world";
send_msg(queue_id, 100, text, strlen(text) + 1);
text = "hello, world of type 50";
send_msg(queue_id, 50, text, strlen(text) + 1);
text = "hello, world of type 60";
send_msg(queue_id, 60, text, strlen(text) + 1);
return 0;
}
Подключение к очереди, созданной сервером
Отправить сообщения типов 100, 50, 60. Именно в таком порядке
int
unpack_msg(const char *msg, int size, long *type, const char **data,
int *data_size)
{
const char *pos = msg;
memcpy(type, pos, sizeof(*type));
pos += sizeof(type);
memcpy(data_size, pos, sizeof(*data_size));
pos += sizeof(*data_size);
*data = pos;
pos += *data_size;
return pos - msg;
}
void
recv_msg(int queue_id, long type)
{
char msg[512];
ssize_t recv = msgrcv(queue_id, msg, sizeof(msg), type, 0);
printf("received %d\n", (int) recv);
const char *data;
int data_size;
unpack_msg(msg, recv, &type, &data, &data_size);
printf("type = %ld, size = %d, str = %s\n", type, data_size, data);
}
Распаковка сообщения. Так же, как запаковка
Вытащить первые sizeof(long) байт как тип
Дальше вытащить размер из sizeof(int) байт
Потом данные
Получение состоит из взятия сообщения из очереди и печати его содержимого
int main(int argc, const char **argv)
{
key_t key = ftok("./server", 0);
int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO);
printf("connected to queue %d\n", queue_id);
while (1) {
struct msqid_ds queue_stat;
if (msgctl(queue_id, IPC_STAT, &queue_stat) != 0) {
printf("error = %s\n", strerror(errno));
msgctl(queue_id, IPC_RMID, NULL);
return -1;
}
if (queue_stat.msg_qnum > 0) {
printf("message count = %d\n",
(int) queue_stat.msg_qnum);
break;
}
sched_yield();
}
recv_msg(queue_id, 0);
recv_msg(queue_id, 60);
recv_msg(queue_id, -200);
msgctl(queue_id, IPC_RMID, NULL);
return 0;
}
Создаю очередь. Теперь клиенты могут класть в нее сообщения
В цикле пытаюсь дождаться, пока появятся сообщения
При помощи IPC_STAT получаю описание очереди и смотрю, сколько там сообщений
Клиент нашелся. Читаю сразу 3 сообщения
Тип 0 - это чтение любого сообщения, в порядке очереди
Тип > 0 - это чтение только этого типа. Здесь читаю сообщение типа 60 вне очереди
Тип < 0, это чтение любого с типом < |Тип|
$> gcc 7_msgsend_server.c -o server
$> ./server
connected to queue 1310720
$> gcc 7_msgsend_client.c -o client
message count = 1
received 25
type = 100, size = 13, str = hello, world
received 36
type = 60, size = 24, str = hello, world of type 60
received 36
type = 50, size = 24, str = hello, world of type 50
$> ./client
connected to queue 1310720
sent 25 bytes
sent 36 bytes
sent 36 bytes
Это сообщение получено вне очереди, так как явно задали его тип
void
recv_msg(int queue_id, long type)
{
char msg[512];
ssize_t recv;
while(1) {
recv = msgrcv(queue_id, msg, sizeof(msg), type, IPC_NOWAIT);
if (recv != -1)
break;
if (errno != ENOMSG) {
printf("error = %s\n", strerror(errno));
return;
}
}
printf("received %d\n", (int) recv);
const char *data;
int data_size;
unpack_msg(msg, recv, &type, &data, &data_size);
printf("type = %ld, size = %d, str = %s\n", type, data_size, data);
}
При использовании IPC_NOWAIT msgrcv не блокируется, когда нет сообщений
Пока он успешно не прочтет сообщение, пытаться читать снова и снова
int main(int argc, const char **argv)
{
key_t key = ftok("./server", 0);
int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO);
printf("connected to queue %d\n", queue_id);
recv_msg(queue_id, 0);
recv_msg(queue_id, 60);
recv_msg(queue_id, -200);
msgctl(queue_id, IPC_RMID, NULL);
return 0;
}
Здесь ожиданий больше нет - recv_msg все делает сам
$> ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 196608 vladislav 600 33554432 2 dest
0x00000000 163841 vladislav 600 16777216 2 dest
0x00000000 294914 vladislav 600 524288 2 dest
0x00000000 393219 vladislav 600 524288 2 dest
0x00000000 425988 vladislav 600 524288 2 dest
0x00000000 753669 vladislav 600 524288 2 dest
0x00000000 983046 vladislav 600 524288 2 dest
0x002e0094 1015815 root 707 1024 0
0x002e0095 1048584 root 707 1024 0
------ Semaphore Arrays --------
key semid owner perms nsems
$> ipcrm -m 1048584
Удаление разделяемой памяти через ipcrm
struct semaphore {
int counter;
};
void
lock(struct semaphore *sem)
{
if (sem->counter == 0)
wait_not_0();
sem->counter--;
}
void
unlock(struct semaphore *sem)
{
sem->counter++;
wakeup_waiters();
}
int
semget(key_t key, int nsems, int semflg);
int
semctl(int semid, int semnum, int cmd, ...);
int
semop(int semid, struct sembuf *sops, size_t nsops);
Создание массива семафоров размера nsems. Флаги как у очередей - IPC_CREAT, IPC_EXCL, права доступа
Контрольные операции над массивом семафоров. cmd - команда:
Атомарно применить массив операций к семафорам. Каждая операция:
struct sembuf {
unsigned short sem_num;
short sem_op;
short sem_flg;
};
- к какому семафору применить
- сколько прибавить/вычесть
- флаги. Например, SEM_UNDO
7_ipc/8_sem.c [1]
void
print_all_sems(int semid)
{
unsigned short values[3];
if (semctl(semid, -1, GETALL, values) == -1) {
printf("error = %s\n", strerror(errno));
} else {
printf("Values are %d %d %d\n", values[0], values[1],
values[2]);
}
}
void
init_sems(int semid)
{
unsigned short values[3] = {1, 2, 3};
semctl(semid, -1, SETALL, values);
printf("Sem array is initialized\n");
print_all_sems(semid);
}
Функция инициализации 3-х семафоров значениями 1, 2, 3 при помощи semctl SETALL
Печать всех значений через semctl GETALL
7_ipc/8_sem.c [2]
int
main()
{
key_t key = ftok("./a.out", 0);
int semid = semget(key, 3, IPC_CREAT | IPC_EXCL | S_IRWXU | S_IRWXO);
if (semid == -1) {
if (errno != EEXIST) {
printf("error = %s\n", strerror(errno));
return -1;
}
printf("Sem array already exists\n");
semid = semget(key, 0, 0);
if (semid == -1) {
printf("error = %s\n", strerror(errno));
return -1;
}
printf("Connected to %d\n", semid);
} else {
printf("Created sems with id %d\n", semid);
init_sems(semid);
}
struct sembuf ops[100], *op;
int ops_count = 0, sem_num;
bool is_inside_txn = false;
char cmd, *line = NULL;
size_t line_size = 0;
while (1) {
getline(&line, &line_size, stdin);
if (line == NULL)
continue;
/* Trim '\n'. */
line[strlen(line) - 1] = 0;
Новый процесс пытается создать семафоры
Если не получилось из-за того, что они уже есть, то печатаем это и просто подключаемся
Если получилось создать, то инициализируем
Начало парсера команд: состояние парсера, чтение строк
7_ipc/8_sem.c [3]
if (strcmp(line, "begin") == 0) {
is_inside_txn = true;
} else if (strcmp(line, "commit") == 0) {
if (semop(semid, ops, ops_count) == -1) {
printf("error = %s\n", strerror(errno));
} else {
is_inside_txn = false;
ops_count = 0;
print_all_sems(semid);
}
} else if (strcmp(line, "rollback") == 0) {
is_inside_txn = false;
ops_count = 0;
} else if (strcmp(line, "delete") == 0) {
if (semctl(semid, -1, IPC_RMID) == -1)
printf("error = %s\n", strerror(errno));
return 0;
} else if (strcmp(line, "quit") == 0) {
return 0;
} else if (strcmp(line, "show") == 0) {
print_all_sems(semid);
} else {
Основные команды
При помощи begin/commit можно накапливать и применять операции в semop
Командой delete можно удалить семафоры и завершить программу
Командой quit можно завершить программу без удаления семафоров
show печатает все семафоры
7_ipc/8_sem.c [4]
sscanf(line, "%d %c", &sem_num, &cmd);
op = &ops[ops_count];
if (cmd == '-') {
op->sem_op = -1;
} else if (cmd == '+') {
op->sem_op = 1;
} else {
printf("Unknown operation\n");
continue;
}
op->sem_num = sem_num - 1;
op->sem_flg = SEM_UNDO;
if (! is_inside_txn) {
if (semop(semid, op, 1) == -1)
printf("error = %s\n", strerror(errno));
else
print_all_sems(semid);
} else {
ops_count++;
}
}
}
return 0;
}
Парсер операций над семафорами
"<semnum> +" - увеличить семафор semnum на 1
"<semnum> -" - уменьшить семафор semnum на 1
$> gcc 8_sem.c
$> ./a.out
Created sems with id 327692
Sem array is initialized
Values are 1 2 3
$> ./a.out
Sem array already exists
Connected to 327692
2 -
Values are 1 1 3
2 -
Values are 1 0 3
show
Values are 1 0 3
^C
$>
show
Values are 1 2 3
1 -
Values are 0 2 3
$> ./a.out
Sem array already exists
Connected to 327692
begin
1 -
2 -
2 -
commit
...
1 +
Values are 1 2 3
Values are 0 0 3
Новый пользователь подключился к существующим семафорам
Можно уменьшать значения семафоров
И это будет видно всем
Благодаря флагу SEM_UNDO можно безопасно завершить процесс
И все локи вернутся
Теперь сделаем один из семафоров 0
Другой процесс на это блокируется сложным набором операций
Он освободится, когда сможет атомарно все операции выполнить
Type 1
Type 2
Type 3
Type 1
Type 2
Type 3
Процесс-источник 1
Процесс-источник 2
Процесс-источник 3
Процессы-собиратели
Процессы-собиратели
...
Задача - синхронизировать доступ к очереди так, чтобы только один процесс-собиратель мог атомарно за раз прочитать 3 сообщения - по одному от каждого источника
semaphores[0 - 2] = message_count_of_the_process;
semaphores[3] = queue_mutex;
int sem_get_or_create(key_t key)
{
int semid = semget(key, 4, IPC_CREAT | IPC_EXCL | S_IRWXU | S_IRWXO);
if (semid == -1) {
if (errno != EEXIST) {
printf("error = %s\n", strerror(errno));
return -1;
}
semid = semget(key, 0, 0);
if (semid == -1) {
printf("error = %s\n", strerror(errno));
return -1;
}
printf("Connected to sems %d\n", semid);
} else {
printf("Created sems with id %d\n", semid);
unsigned short values[4] = {0, 0, 0, 1};
semctl(semid, -1, SETALL, values);
}
return semid;
}
void sem_inc(int semid, int myid)
{
struct sembuf op;
op.sem_flg = SEM_UNDO;
op.sem_num = myid - 1;
op.sem_op = 1;
semop(semid, &op, 1);
}
Обычное создание семафоров
Три счетчика сообщений по нулям. Мьютекс на очередь свободен (семафор [3] равен 1)
Функция увеличения одного семафора через semop
int
msg_queue_get_or_create(key_t key)
{
int queueid = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO);
if (queueid == -1)
printf("error = %s\n", strerror(errno));
else
printf("Created/connected to queue %d\n", queueid);
return queueid;
}
int
pack_msg(char *msg, long type, const char *data, int data_size)
{
char *pos = msg;
memcpy(pos, &type, sizeof(type));
pos += sizeof(type);
memcpy(pos, &data_size, sizeof(data_size));
pos += sizeof(data_size);
memcpy(pos, data, data_size);
pos += data_size;
return pos - msg;
}
void
send_msg(int queueid, int myid, const char *data, int data_size)
{
char msg[512];
int size = pack_msg(msg, myid, data, data_size);
msgsnd(queueid, msg, size, 0);
printf("Sent %d bytes\n", size);
}
Создание или подключение к очереди (нет IPC_EXCL)
Упаковка и отправка сообщения, как раньше
int
main(int argc, const char **argv)
{
if (argc < 2) {
printf("No id\n");
return -1;
}
key_t key = ftok("./event_gen", 0);
int semid = sem_get_or_create(key);
if (semid == -1)
return -1;
int queueid = msg_queue_get_or_create(key);
if (queueid == -1)
return -1;
int myid = atoi(argv[1]);
char *line = NULL;
size_t line_size = 0;
while(1) {
if (getline(&line, &line_size, stdin) <= 0)
break;
send_msg(queueid, myid, line, strlen(line));
sem_inc(semid, myid);
}
if (myid == 1) {
msgctl(queueid, IPC_RMID, NULL);
semctl(semid, -1, IPC_RMID);
}
return 0;
}
Одинаковый ключ можно использовать для разных IPC
Источники сообщений получают номера от 1 до 3, чтобы ставить их как типы своих сообщений
На каждую отправку увеличивает свой семафор
int unpack_msg(const char *msg, int size, long *type, const char **data,
int *data_size)
{
const char *pos = msg;
memcpy(type, pos, sizeof(*type));
pos += sizeof(type);
memcpy(data_size, pos, sizeof(*data_size));
pos += sizeof(*data_size);
*data = pos;
pos += *data_size;
return pos - msg;
}
void recv_msg(int queueid, long type)
{
char msg[512];
ssize_t recv = msgrcv(queueid, msg, sizeof(msg), type, 0);
printf("Received %d\n", (int) recv);
const char *data;
int data_size;
unpack_msg(msg, recv, &type, &data, &data_size);
printf("Type = %ld, size = %d, str = %s\n", type, data_size, data);
}
void read_data(int queueid)
{
for (int i = 0; i < 3; ++i)
recv_msg(queueid, i + 1);
}
Чтение сообщения, его распаковка
Потребитель событий читает по одному сообщению от каждого источника
void unlock_events(int semid)
{
struct sembuf op;
op.sem_flg = SEM_UNDO;
op.sem_num = 3;
op.sem_op = 1;
semop(semid, &op, 1);
}
void lock_events(int semid)
{
struct sembuf op[4];
for (int i = 0; i < 4; ++i) {
op[i].sem_flg = SEM_UNDO;
op[i].sem_num = i;
op[i].sem_op = -1;
}
semop(semid, op, 4);
}
int main()
{
key_t key = ftok("./event_gen", 0);
int semid = semget(key, 4, 0);
int queueid = msgget(key, 0);
while(1) {
lock_events(semid);
read_data(queueid);
unlock_events(semid);
}
return 0;
}
Для получения единоличного доступа к очереди с минимум тремя разными сообщениями берутся локи всех семафоров
Возврат только мьютекса всей очереди. Счетчики обратно не увеличиваются
Цикл обработки: лок всего, прочитать тройку, анлок всего
$> ./event_gen 2
Connected to sems 393229
Created/connected to queue 1376256
$> gcc 9_sem_event_source.c -o
event_gen
$> ./event_gen 1
Created sems with id 393229
Created/connected to queue 1376256
$> ./event_gen 3
Connected to sems 393229
Created/connected to queue 1376256
$> gcc 9_sem_event_cons.c
$> ./a.out
$> ./a.out
msg 1 from 1
Sent 25 bytes
msg 1 from 2
Sent 25 bytes
msg 1 from 3
Sent 25 bytes
Received 25
Type = 1, size = 13, str = msg 1 from 1
Received 25
Type = 2, size = 13, str = msg 1 from 2
Received 25
Type = 3, size = 13, str = msg 1 from 3
msg 2 from 3
Sent 25 bytes
msg 3 from 3
Sent 25 bytes
msg 4 from 3
Sent 25 bytes
msg 2 from 2
Sent 25 bytes
msg 3 from 2
Sent 25 bytes
msg 4 from 2
Sent 25 bytes
msg 2 from 1
Sent 25 bytes
Received 25
Type = 1, size = 13, str = msg 2 from 1
Received 25
Type = 2, size = 13, str = msg 2 from 2
Received 25
Type = 3, size = 13, str = msg 2 from 3
struct semaphore {
int counter;
pthread_mutex_t mutex;
pthread_cond_t cond;
};
static inline void
semaphore_get(struct semaphore *sem)
{
pthread_mutex_lock(&sem->mutex);
while (sem->counter == 0)
pthread_cond_wait(&sem->cond, &sem->mutex);
sem->counter--;
pthread_mutex_unlock(&sem->mutex);
}
static inline void
semaphore_put(struct semaphore *sem)
{
pthread_mutex_lock(&sem->mutex);
sem->counter++;
pthread_cond_signal(&sem->cond);
pthread_mutex_unlock(&sem->mutex);
}
int
shmget(key_t key, size_t size, int shmflg);
int
shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *
shmat(int shmid, const void *shmaddr, int shmflg);
int
shmdt(const void *shmaddr);
Создание/подключение, аналогично семафорам и очередям
Характеристики, удаление
shmget только создает память. Чтобы ее получить в процесс, нужно сделать shmat - аналог mmap
Аналог munmap
int
main()
{
key_t key = ftok("./a.out", 0);
int id = shmget(key, 1024, IPC_CREAT | S_IRWXU | S_IRWXO);
printf("created mem with id %d\n", id);
char *mem = shmat(id, NULL, 0);
int align = __alignof__(pthread_mutex_t);
pthread_mutex_t *mutex = (pthread_mutex_t *)
(mem + align - mem % align);
volatile char *data = (char *) mutex + sizeof(*mutex);
*data = 0;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(mutex, &attr);
pthread_mutexattr_destroy(&attr);
printf("Created mutex between processed\n");
if (fork() == 0) {
printf("Child holds a lock\n");
pthread_mutex_lock(mutex);
*data = 1;
printf("And the child dies\n");
exit(1);
}
printf("Parent waits for 1 in shmem\n");
while (*data != 1) {}
7_ipc/11_shm.c [1]
Создать память
Получить память
Начальные байты памяти используем под mutex
Создается новый процесс, который делает lock, но умирает не сделав unlock
Родитель ждет, пока потомок испортит mutex
printf("Parent tries to lock\n");
if (pthread_mutex_lock(mutex) == EOWNERDEAD) {
printf("Owner is dead, restore\n");
pthread_mutex_consistent(mutex);
}
printf("Destroy mutex\n");
pthread_mutex_unlock(mutex);
pthread_mutex_destroy(mutex);
printf("Free shmem\n");
shmdt(mem);
shmctl(id, IPC_RMID, NULL);
return 0;
}
7_ipc/11_shm.c [2]
Mutex оказался испорчен
В начале у этого mutex был задан атрибут robust. Это означает, что его можно чинить функцией consistent().
sem_t *
sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
int
sem_init(sem_t *sem, int pshared, unsigned int value);
int
sem_wait(sem_t *sem);
int
sem_post(sem_t *sem);
int
sem_unlink(const char *name);
int
sem_close(sem_t *sem);
Создание именованного семафора. Как в XSI. Не требует существования файла
Создание анонимного семафора. Будет доступен только в своих потоках и потомках
Вычесть 1 из семафора или заблокироваться, пока он не станет > 0
Добавить в семафор 1, пробудить ждущих
Удалить семафор. Реально удалится только когда последний пользователь сделает unlink
Закрыть, но не удалять. Освободит ресурсы в вызвавшем процессе
Деструктора вроде SEM_UNDO нет
#define WORKER_COUNT 10
sem_t *sem;
void *worker_f(void *arg)
{
int id = (int) arg;
while (1) {
sem_wait(sem);
printf("Thread %d got the sem\n", id);
sleep(3);
sem_post(sem);
sleep(1);
}
}
int main()
{
pthread_t workers[WORKER_COUNT];
sem = sem_open("/my_sem", O_CREAT, O_RDWR, 5);
if (sem == SEM_FAILED) {
printf("error = %s\n", strerror(errno));
return -1;
}
for (int i = 0; i < WORKER_COUNT; ++i)
pthread_create(&workers[i], NULL, worker_f, (void *) i);
getchar();
sem_close(sem);
return 0;
}
Потоки конкурируют за семафор. 10 потоков на семафор со значением 5
Создается именованный семафор по выдуманному пути
Создать потоки и завершить процесс, когда нажмут enter
$> gcc 8_sem_posix.c
$> ./a.out
Thread 0 got the sem
Thread 1 got the sem
Thread 2 got the sem
Thread 3 got the sem
Thread 4 got the sem
Thread 5 got the sem
Thread 6 got the sem
Thread 7 got the sem
Thread 8 got the sem
Thread 9 got the sem
Thread 3 got the sem
Thread 1 got the sem
Thread 4 got the sem
Thread 2 got the sem
Thread 0 got the sem
...
int
socketpair(int domain, int type, int protocol,
int socket_vector[2]);
Работает как двунаправленный pipe - можно писать/читать с обоих концов
int
socketpair(int domain, int type, int protocol,
int socket_vector[2]);
Протокол - некое соглашение о формате передаваемых данных и их обработке
Пример описания протокола:
"Давайте передавать данные пакетами по максимум 512 байт. Первые 4 байта - размер пакета в big-endian, дальше тип сообщения, ... Если у пакетов в сети нарушается порядок, то исправляем его"
$> cat /etc/protocols
ip 0 IP # internet protocol, pseudo protocol number
hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
egp 8 EGP # exterior gateway protocol
igp 9 IGP # any private interior gateway (Cisco)
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
...
$> man protocols
Список поддерживаемых протоколов в Linux
Руководство к знакомству с протоколами. Но нужно редко - обычно по умолчанию используют protocol = 0
int
socketpair(int domain, int type, int protocol,
int socket_vector[2]);
Тип сокета описывает способ работы с ним. Почти всегда однозначно соответствует одному протоколу.
Возможные типы:
int
socketpair(int domain, int type, int protocol,
int socket_vector[2]);
Домен определяет семейство протоколов, в котором уже уточняют тип сокета и возможно конкретный протокол
Возможные домены:
#define MAX_MSG_SIZE 1024 * 1024
void worker(int sock)
{
char buffer[MAX_MSG_SIZE];
ssize_t size;
while ((size = read(sock, buffer, sizeof(buffer))) > 0)
printf("Client received %d\n", (int) size);
}
int main(int argc, const char **argv)
{
int socket_type = strcmp(argv[1], "stream") == 0 ?
SOCK_STREAM : SOCK_DGRAM;
int sockets[2];
socketpair(AF_UNIX, socket_type, 0, sockets);
if (fork() == 0) {
close(sockets[0]);
worker(sockets[1]);
return 0;
}
close(sockets[1]);
char buffer[MAX_MSG_SIZE];
int size;
while (scanf("%d", &size) > 0) {
printf("Server sent %d\n", size);
write(sockets[0], buffer, size);
}
return 0;
}
Пользователь с клавиатуры выбирает тип сокета - пакеты или поток
Создается канал, как pipe
Ответвляется второй процесс, который читает все, что есть в сокете
Родитель отправляет второму процессу число байт с клавиатуры
$> gcc 13_socketpair.c
$> ./a.out stream
30000
Server sent 30000
Client received 30000
50000
Server sent 50000
Client received 50000
$> ./a.out dgram
50000
Server sent 50000
Client received 50000
100000
Server sent 100000
Client received 100000
1000000
Server sent 1000000
Client received 219264
Client received 219264
Client received 219264
Client received 219264
Client received 122944
Потоковый сокет передает большие данные не разбивая. Слишком большие не передает совсем
Пакетный сокет передает любые данные, возможно разбив на пакеты
int
socket(int domain, int type, int protocol);
int
bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int
listen(int sockfd, int backlog);
int
connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int
accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
Создание сокета с заданными доменом, протоколом, типом
Привязка имени к сокету
Прослушка входящих подключений через connect()
Способ подключить сокет к другому уже именованному сокету. Тогда bind не нужен
Если же сделали bind + listen, то сокет принимает клиентов через accept
Способы использования
int fd = socket();
connect(fd, remote_addr);
/* Ready to read/write fd. */
Подключение к уже именованному
Создание именованного без соединения
int fd = socket();
bind(fd, addr);
/** Ready to read/write fd. */
Создание именованного с соединением
int fd = socket();
bind(fd, addr);
listen(fd);
while(1) {
int remote_fd = accept(fd);
/*
* Ready to read/write
* remote_fd.
*/
}
Connect() создает на сервере парный сокет, и эти пара может общаться как socketpair()
int fd2 = socket();
bind(fd2, addr2);
/** Ready to read/write fd2. */
read/write
send/recv
sendto/recvfrom
Без connect() работают только пакетные типы сокетов, и нужно указывать адресатов руками
int
bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
Имя сокета - его адрес
- AF_UNIX, AF_INET, ...
- данные наследных структур, резерв ...
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};
struct sockaddr_nl {
sa_family_t nl_family;
unsigned short nl_pad;
pid_t nl_pid;
__u32 nl_groups;
};
Наследование структуры sockaddr ее повторением
struct base {
int base_field1;
char *base_field2;
};
struct derivative {
struct base base;
int deriv_field3;
bool deriv_field4;
};
void do_sth(struct base *obj);
int main()
{
struct derivative der;
do_sth((struct base *)&der);
}
Наследование в С включением
struct base {
int base_field1;
char *base_field2;
char padding[32];
};
struct derivative {
int base_field1;
char *base_field2;
int deriv_field3;
bool deriv_field4;
};
void do_sth(struct base *obj);
int main()
{
struct derivative der;
do_sth((struct base *)&der);
}
Наследование в С повторением
Родительская структура содержится в потомке как атрибут
Можно без проблем кастовать к базе. Потомок в памяти сначала хранит родителя
Все атрибуты родителя содержатся в потомке
Тоже можно кастовать. В памяти в начале все равно выглядит как родитель
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};
Должно быть AF_UNIX, чтобы функции, использующие указатель на базу (struct sockaddr *) могли по sockaddr.sa_family понять тип адреса
Путь к файлу сокета. Если сделать bind() и файла нет, то он создастся. Если он есть, то будет ошибка - файл надо удалять после использования.
void
initiator_f(int sock, struct sockaddr_un *next_addr)
{
int number;
while(scanf("%d", &number) > 0) {
if (sendto(sock, &number, sizeof(number), 0,
(struct sockaddr *) next_addr,
sizeof(*next_addr)) == -1) {
printf("error = %s\n", strerror(errno));
continue;
}
printf("Sent %d\n", number);
int buffer = 0;
ssize_t size = recv(sock, &buffer, sizeof(buffer), 0);
if (size == -1)
printf("error = %s\n", strerror(errno));
else
printf("Received %d\n", buffer);
}
}
Процесс 1 - инициатор
Процесс 2
Процесс 3
N
N + 1
N + 2
Инициатор читает число с клавиатуры и посылает его дальше по кольцу
Инициатор ждет ответа от кого-нибудь и печатает его
void
worker_f(int sock, struct sockaddr_un *next_addr)
{
while(1) {
int buffer = 0;
ssize_t size = recv(sock, &buffer, sizeof(buffer), 0);
if (size == -1) {
printf("error = %s\n", strerror(errno));
continue;
}
printf("Received %d\n", buffer);
buffer++;
if (sendto(sock, &buffer, sizeof(buffer), 0,
(struct sockaddr *) next_addr,
sizeof(*next_addr)) == -1)
printf("error = %s\n", strerror(errno));
else
printf("Sent %d\n", buffer);
}
}
Остальные процессы сначала читают, а потом переотправляют + 1
int
main(int argc, const char **argv)
{
if (argc < 2) {
printf("No id\n");
return -1;
}
int myid = atoi(argv[1]);
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock == -1) {
printf("error = %s\n", strerror(errno));
return -1;
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
sprintf(addr.sun_path, "sock%d", myid);
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
printf("error = %s\n", strerror(errno));
return -1;
}
struct sockaddr_un next_addr;
next_addr.sun_family = AF_UNIX;
sprintf(next_addr.sun_path, "sock%d", (myid + 1) % 3);
ssize_t size;
int buffer;
if (myid == 0)
initiator_f(sock, &next_addr);
else
worker_f(sock, &next_addr);
return 0;
}
Участники создают свои сокеты и делают bind к адресам sock1, sock2 и sock3.
Здесь определяется, кому надо посылать пакеты дальше по кольцу
Процесс выбирает свою роль по идентификатору
$> gcc 14_sock_dgram.c
$> ./a.out 0
$> ./a.out 1
$> ./a.out 2
1
Sent 1
Received 1
Sent 2
Received 2
Sent 3
Received 3
void *
worker_f(void *arg)
{
printf("New client created\n");
int client_sock = (int) arg;
while(1) {
int buffer = 0;
ssize_t size = read(client_sock, &buffer, sizeof(buffer));
if (size == -1) {
printf("error = %s\n", strerror(errno));
continue;
}
if (size == 0) {
printf("Closed connection\n");
break;
}
printf("Received %d\n", buffer);
buffer++;
if (write(client_sock, &buffer, sizeof(buffer)) == -1)
printf("error = %s\n", strerror(errno));
else
printf("Sent %d\n", buffer);
}
close(client_sock);
return NULL;
}
Функция выполняется в отдельном потоке и обслуживает одного клиента
Клиент присылает число, которое надо увеличить
И отправить обратно
int main(int argc, const char **argv)
{
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
sprintf(addr.sun_path, "%s", "sock_server");
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
printf("error = %s\n", strerror(errno));
return -1;
}
if (listen(sock, 128) == -1) {
printf("error = %s\n", strerror(errno));
return -1;
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, 1);
while(1) {
pthread_t worker_thread;
int client_sock = accept(sock, NULL, NULL);
if (client_sock == -1) {
printf("error = %s\n", strerror(errno));
continue;
}
pthread_create(&worker_thread, &attr, worker_f,
(void *) client_sock);
}
pthread_attr_destroy(&attr);
close(sock);
return 0;
}
Сервер делает socket + bind + listen, чтобы слушать клиентов
Каждый клиент помещается в свой поток
Прием клиентов ведется функцией accept
int main()
{
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
sprintf(addr.sun_path, "%s", "sock_server");
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
printf("error = %s\n", strerror(errno));
return -1;
}
int number;
while (scanf("%d", &number) > 0) {
write(sock, &number, sizeof(number);
printf("Sent %d\n", number);
number = 0;
int rc = read(sock, &number, sizeof(number));
if (rc == 0) {
printf("Closed connection\n");
break;
}
if (rc == -1)
printf("error = %s\n", strerror(errno));
else
printf("Received %d\n", number);
}
close(sock);
return 0;
}
Обратно ожидаются увеличенными на 1
socket + connect, чтобы подключиться к серверу
Числа с клавиатуры отправляются на сервер
$> gcc 15_sock_server.c
-o server
$> ./server
$> gcc 15_sock_client.c
-o client
$> ./client
$> ./client
New client created
1
Sent 1
Received 2
100
Sent 100
Received 101
Received 100
Sent 101
New client created
Received 1
Sent 2
^C
$>
Closed connection
^C
$>
Closed connection
В следующий раз:
Сеть. Модели TCP/IP, OSI. Связь с ядром. Интерфейсы и примеры.
By Vladislav Shpilevoy
IPC. Pipe, FIFO. XSI: message queue, semaphore, shared memory. POSIX semaphore. Sockets: API, byte ordering. Доменные сокеты.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.