В этой статье познакомимся с Unix сокетами. Рассмотрим способы их применения и пару их видов. Напишем сокетные клиент и сервер на python, а также проверим клиент в работе с rsyslog’ом.
Сокеты
Сокеты являются механизмом межпроцессорного взаимодействия (IPC - inter-process communication) внутри одного компьютера или между различными компьютерами по сети. Если вы имели дело или слышали о таком понятии как UDP и TCP порты, то вам может быть интересно узнать, что они являются дополнительным уровнем абстракции над сокетами. Детали этого рассмотрим далее.
Если речь заходит о Unix- как правило подразумевается взаимодействие между двумя процессам в рамках одного компьютера. В процессе создания такого сокета создается файл на файловой системе. При этом другой процесс может обратиться к этому файлу с целью записать в него данные или прочитать данные из сокета.
Передача информации между двумя процессами через unix- сокет при передаче информации биты “летят” даже не через loopback интерфейс, всё взаимодействие происходит внутри одного ядра, а значит, в рамках оперативной памяти и кэшей процессора, за исключением первичного обращения к файловой системе.
DATAGRAM и STREAM сокеты
Если в Linux выполнить команду netstat
с флагом -x
, то в ответ команда выведет список unix-сокетов. Причем, скорее всего все сокеты будут принадлежать к одному из двух типов: DGRAM и STREAM. Этих типов на самом деле больше, еще существуют RAW и SEQUENCED.
user@vm:~$ netstat -x
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
unix 3 [ ] STREAM CONNECTED 35012
unix 3 [ ] DGRAM CONNECTED 29295
DATAGRAM сокеты фактически используют протокол UDP (User Datagram Protocol) для передачи информации. Входное сообщение они делят на датаграммы и отправляют независимо друг от друга. Гарантий сохранения общего порядка информации нет. То есть вторая датаграмма может прийти получателю раньше первой. Проверки доставки нет, гарантий отсутствия дубликатов также нет. Ответственность по решению этих проблем лежит на пользовательском приложении, использующем сокеты.
STREAM сокеты в отличие от DATAGRAM, используют протокол TCP. Сокеты сами заботятся о сохранении верного порядка информации при передаче. Контролируют отсутствие дубликатов, запрашивают и подтверждают статус доставки.
Помните, я говорил, что TCP/UDP сокеты лишь дополнительный уровень абстракции? А это потому что тот же самый TCP сокет- это UNIX сокет типа STREAM, который “забинджен” не на файл файловой системы, а на пару ip-адрес - порт. Аналогично обстоят дела и с UDP сокетами.
Сервер
Теперь, когда тема сокетов стала чуть понятнее, самое время создать свое приложение, которое будет слушать сокет. Представлю вашему вниманию такой код:
import socket
import os
DEFAULT_PATH = '/tmp/test_server_socket'
def get_server_socket(path):
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
server_socket.bind(path)
return server_socket
def serve():
server = get_server_socket(DEFAULT_PATH)
try:
while True:
data, address = server.recvfrom(1024)
print(data.decode('utf-8'))
except:
server.close()
if __name__ == "__main__":
serve()
В этой программе есть одна проблема. Если её закрыть после того, как будет исполнена функция .bind(path)
, файл в файловой системе так и останется. Соответственно, при повторном запуске возникнет исключение, говорящее о том, что такой файл-сокет-адрес уже заняты. Следовательно, придется вручную удалять создающийся сокет.
Способ решения этой проблемы на самом деле не очевидный. Например, на stackoverflow самый популярный способ решения этой проблемы предлагает удалять файл по пути “path” перед тем, как создавать новый сокет. Выглядит не слишком чисто. Куда лучше импортировать модуль stat
и использовать функцию stat.S_ISSOCK()
, чтобы определить, является ли файл по пути path
сокет- файлом, чтобы удалять его.
Клиент
Код клиента ничем принципиально не отличается от кода сервера.
import socket
import time
import random
import sys
def get_connected_client(socket_path):
client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
client.connect(socket_path)
return client
def send_to_socket(client, infinite=False):
while True:
message = f'Message from python {random.randint(1, 100)}'
client.send(message.encode())
if not infinite:
break
time.sleep(2)
client.close()
if __name__ == '__main__':
path_in_list = sys.argv[1:2]
if not path_in_list:
print('Path to socket is needed')
sys.exit(1)
path = path_in_list[0]
client = get_connected_client(path)
send_to_socket(client)
Уже на данном этапе вы можете проверить, работают ли сокеты, запустив сервер и клиент.
RSYSLOG
Теперь, проверим, можно ли с другими приложениями работать. Для этого я предпочел поэкспериментировать на rsyslog’ом. Если его нет в вашем дистрибутиве- найдите себе дистрибутив с rsyslog’ом или установите его.
Rsyslog- backend системы логирования linux. Он не единственный backend и логирует не всё, еще есть journald, например. Зачастую приложения требуется верно сконфигурировать, чтобы они отправляли свои логи в rsyslog. Даже journald можно сконфигурировать так, чтобы он слал логи в rsyslog, делается это в файле /etc/systemd/journald.conf
. Можно в нём такой параметр прописать:
ForwardToSyslog=yes
Может возникнуть вопрос: а откуда journald знает, как логи в rsyslog отправить? Может возникнуть и другой вопрос, как rsyslog поймет, что ему хотят логи прислать?
Насчёт первого вопроса- journald знает, что syslog будет слушать сокет по пути /run/systemd/journal/syslog
. Насчёт второго- откуда rsyslog знает, что ему нужно открыть сокет по заданному пути? Ответ- он не знает, но существуют договоренности в мире Linux. Требуется прописать верный конфиг в /etc/rsyslog.conf
или в /etc/rsyslog.d/...
.
module(load="imuxsock")
if $inputname == "imuxsock" then {
action(type="omfile" File="/tmp/file.log")
}
Директива module(load="imuxsock")
помимо того, что подключит модуль imuxsock
, еще и по пути /run/systemd/journal/syslog
откроет DATAGRAM сокет, который станет слушать.
Синергия rsyslog и python клиента
Если звезды сошлись, а вы знаете, как иметь дело с rsyslog, то у вас должно быть всё успешно. Вы увидите те сообщения, которые отправляете через python в сокет rsyslog’а в файле /tmp/file.log
. Правда, у меня при этом еще кто- то умудряется 2 байта из начала сообщения красть (это делает rsyslog по неясно причине).
Вывод
Выводы оставляю на совести читателя.