16 дек. 2012 г.

Unix domain sockets: гарантированное освобождение и повторное использование сокета

Ссылки

Для связи процессов на одном компьютере можно использовать доменные сокеты (IPC-сокеты).

Отличия от интернет-сокетов:
  • Более высокая производительность.
  • Доставка SIGPIPE сразу после обрыва соединения с другого конца.
  • Стандартные права доступа unix у файлов сокетов.
См. также здесь.

Производительность можно измерить с помощью netperf:

$ netperf -t TCP_STREAM      # TCP
$ netperf -t STREAM_STREAM   # unix domain (компилировать netperf с опцией --enable-unixdomain)

Проблема

Файл сокета создается при вызове bind(2). Если файл уже существует, возвращается ошибка EADDRINUSE.

Единственный способ повторного использования файла сокета - вызов unlink(2) перед bind.

Есть две схемы:
  • Вызывать unlink перед завершением сервера. Тогда нет гарантии, что при крахе сервера не останется висячего файла, который пользователю придется удалять вручную.
  • Вызывать unlink при запуске сервера. Тогда нет гарантии, что не будет удален файл другого запущенного экземпляра сервера.

Решение

Именованные доменные сокеты бывают двух типов: filesystem sockets и abstract namespace sockets, см. unix(7).

Filesystem sockets

Простое решение - использовать дополнительный lock-файл для каждого сокета.

Этот файл может быть заблокирован тогда и только тогда, когда в данный момент запущен другой экземпляр сервера. Если блокировку удалось захватить, можно безопасно вызывать unlink() для файла сокета.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const char *socket_path = "/tmp/server-socket";

int server = socket(PF_LOCAL, SOCK_STREAM, 0);
assert(server != -1);

struct sockaddr_un server_addr;
bzero(&server_addr, sizeof(server_addr));

server_addr.sun_family = AF_LOCAL;
strcpy(server_addr.sun_path, socket_path);

char lock_path[256];
sprintf(lock_path, "%s.lock", socket_path);

//
// Open lock file
//
int lock_fd = open(lock_path, O_RDONLY | O_CREAT, 0600);
if (lock_fd == -1) {
    printf("can't open lock file\n");
    exit(1);
}

//
// Acquire lock
//
int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
if (ret != 0) {
    printf("address already in use!\n");
    exit(1);
}

//
// Remove socket file
//
unlink(socket_path);

//
// Create socket file
//
ret = bind(server, (struct sockaddr *)&server_addr, sizeof(server_addr));
assert(ret == 0);

Abstract namespace sockets

Еще более простое решение - использовать abstract namespace (поддерживается только в Linux).

Если в  структуре sockaddr_un в поле sun_path в первый байт имени сокета записать '\0', сокет с этим именем будет создан не в файловой системе, а абстрактном пространстве имен.

Такой сокет будет автоматически освобожден при завершении приложения.

Комментариев нет:

Отправить комментарий