Лаборатория Tarantool

Системное программирование

Лекция 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.

Новости

Дедлайны:

  • планировщик корутин: 19 марта, штраф -10
  • shell: 2 апреля, штраф -3
  • файловая система: 16 апреля

Потоки

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

IPC: pipe, mmap

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);

IPC: pipe. Simple sort

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

Замеры и печать времени выполнения

Объединение в один массив

IPC: pipe. Simple sort

Сортировка одним процессом

4 файла по 20 000 000 чисел

~3.5 сек

IPC: pipe. Parallel sort

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()

IPC: pipe. Parallel sort

Сортировка многими процессами

4 файла по 20 000 000 чисел

~2.3 сек

4 процесса

х 1.5

IPC: mmap. Parallel sort

#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;
}

Объединение чисел в один массив

Подсчет времени

IPC: mmap. Parallel sort

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 балл

IPC: pipe, mmap

init/lauchd

Дерево родства процессов

Не могут иметь общих анонимных ресурсов. Как взаимодействовать?

Могут делить anon mmap, pipe

IPC: FIFO

int
mkfifo(const char *path, mode_t mode);

Процесс 1

Процесс 2

read
write

read
write

FIFO

Процесс N

read
write

Процесс 3

read
write

...

IPC: FIFO. Сервер

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, а сервер их вычитывает и печатает

IPC: FIFO. Клиент

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

IPC: FIFO

$> 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
$>
$>

XSI IPC

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

XSI IPC. Именование

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;
}

Ключ генерируется по пути в файловой системе и произвольному числу

XSI IPC. Именование

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]); что-то еще. Файл должен существовать. Второй аргумент - для разрешения коллизий.

XSI IPC. Очередь сообщений [1]

Процесс 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

Максимальные размер сообщения и очереди, в байтах

XSI IPC. Очередь сообщений [2]

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, которой можно смотреть глобальные лимиты

XSI IPC. Очередь сообщений [3]

$> 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

XSI IPC. Очередь сообщений [4]. Клиент

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. Именно в таком порядке

XSI IPC. Очередь сообщений [5]. Сервер

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, это чтение любого с типом < |Тип|

XSI IPC. Очередь сообщений [6]. Сервер

$> 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

Это сообщение получено вне очереди, так как явно задали его тип

XSI IPC. Очередь сообщений [7]. Сервер

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 все делает сам

XSI IPC. ipcs, ipcrm

$> 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

XSI IPC. Семафоры [1]

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();
}

XSI IPC. Семафоры [2]

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 - команда:

  • IPC_RMID - удалить массив
  • GETALL/SETALL - получить/установить значения семафоров в 4-й аргумент
  • GETVAL/SETVAL - получить/установить значение семафора под номером semnum
  • ...

Атомарно применить массив операций к семафорам. Каждая операция:

struct sembuf {
        unsigned short sem_num;
        short sem_op;
        short sem_flg;
};

- к какому семафору применить

- сколько прибавить/вычесть

- флаги. Например, SEM_UNDO

XSI IPC. Семафоры [3]

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

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;

Новый процесс пытается создать семафоры

Если не получилось из-за того, что они уже есть, то печатаем это и просто подключаемся

Если получилось создать, то инициализируем

Начало парсера команд: состояние парсера, чтение строк

                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 печатает все семафоры

                        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

XSI IPC. Семафоры [4]

$> 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

Другой процесс на это блокируется сложным набором операций

Он освободится, когда сможет атомарно все операции выполнить

XSI IPC. Семафоры + очереди [1]

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;

XSI IPC. Семафоры + очереди [2]

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, чтобы ставить их как типы своих сообщений

На каждую отправку увеличивает свой семафор

XSI IPC. Семафоры + очереди [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;
}

Для получения единоличного доступа к очереди с минимум тремя разными сообщениями берутся локи всех семафоров

Возврат только мьютекса всей очереди. Счетчики обратно не увеличиваются

Цикл обработки: лок всего, прочитать тройку, анлок всего

XSI IPC. Семафоры + очереди [4]



$> ./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

XSI IPC. Семафоры через mutex

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);
}

XSI IPC. Разделяемая память [1]

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

XSI IPC. Разделяемая память [2]

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) {}

Создать память

Получить память

Начальные байты памяти используем под mutex

Создается новый процесс, который делает lock, но умирает не сделав unlock

Родитель ждет, пока потомок испортит mutex

XSI IPC. Разделяемая память

	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;
}

Mutex оказался испорчен

В начале у этого mutex был задан атрибут robust. Это означает, что его можно чинить функцией consistent().

POSIX IPC. Семафоры [1]

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 нет

POSIX IPC. Семафоры [2]

#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

POSIX IPC. Семафоры [3]

$> 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

...

IPC. Доменные UNIX сокеты [1]

int
socketpair(int domain, int type, int protocol,
           int socket_vector[2]);

Работает как двунаправленный pipe - можно писать/читать с обоих концов

IPC. Доменные UNIX сокеты [2]

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

IPC. Доменные UNIX сокеты [3]

int
socketpair(int domain, int type, int protocol,
           int socket_vector[2]);

Тип сокета описывает способ работы с ним. Почти всегда однозначно соответствует одному протоколу.

Возможные типы:

  • SOCK_DGRAM - через сокет отправляются/читаются пакеты ограниченного размера, может не быть гарантий доставки, порядка. Обычно это UDP.
  • SOCK_STREAM - сокет выглядит как сплошной поток байт, гарантии доставки и порядка. Обычно это TCP.
  • SOCK_RAW - как dgram, но пакеты читаются/пишутся в почти "сыром" виде. Нужно самому собирать протоколы вроде TCP, UDP, составляя их заголовки и т.д.
  • ...

IPC. Доменные UNIX сокеты [4]

int
socketpair(int domain, int type, int protocol,
           int socket_vector[2]);

Домен определяет семейство протоколов, в котором уже уточняют тип сокета и возможно конкретный протокол

Возможные домены:

  • AF_UNIX/AF_LOCAL - два синонима одного домена - локальной машины. Сокеты такого домена могут не использовать никаких протоколов, так как по сети передавать все равно не могут.
  • AF_INET - протоколы на базе IPv4.
  • AF_INET6 - протоколы на базе IPv6.
  • AF_PACKET - сырые сокеты, протоколы пользователь сам какие хочет собирает/разбирает.

IPC. Доменные UNIX сокеты [5]

#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

Ответвляется второй процесс, который читает все, что есть в сокете

Родитель отправляет второму процессу число байт с клавиатуры

IPC. Доменные UNIX сокеты [6]

$> 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

Потоковый сокет передает большие данные не разбивая. Слишком большие не передает совсем

Пакетный сокет передает любые данные, возможно разбив на пакеты

IPC. Доменные UNIX сокеты [7]

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

IPC. Доменные UNIX сокеты [8]

Способы использования

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() работают только пакетные типы сокетов, и нужно указывать адресатов руками

IPC. Доменные UNIX сокеты [9]

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 ее повторением

IPC. Доменные UNIX сокеты [10]

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);
}

Наследование в С повторением

Родительская структура содержится в потомке как атрибут

Можно без проблем кастовать к базе. Потомок в памяти сначала хранит родителя

Все атрибуты родителя содержатся в потомке

Тоже можно кастовать. В памяти в начале все равно выглядит как родитель

IPC. Доменные UNIX сокеты [11]

struct sockaddr_un {
        sa_family_t sun_family;
        char sun_path[108];
};

Должно быть AF_UNIX, чтобы функции, использующие указатель на базу (struct sockaddr *) могли по sockaddr.sa_family понять тип адреса

Путь к файлу сокета. Если сделать bind() и файла нет, то он создастся. Если он есть, то будет ошибка - файл надо удалять после использования.

IPC. Доменные UNIX сокеты [12]

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. 

Здесь определяется, кому надо посылать пакеты дальше по кольцу

Процесс выбирает свою роль по идентификатору

IPC. Доменные UNIX сокеты [13]

$> 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

IPC. Доменные UNIX сокеты [14]

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

IPC. Доменные UNIX сокеты [15]

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, чтобы подключиться к серверу

Числа с клавиатуры отправляются на сервер

IPC. Доменные UNIX сокеты [16]

$> 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. Связь с ядром. Интерфейсы и примеры.

Системное программирование 7

By Vladislav Shpilevoy

Системное программирование 7

IPC. Pipe, FIFO. XSI: message queue, semaphore, shared memory. POSIX semaphore. Sockets: API, byte ordering. Доменные сокеты.

  • 1,513