Pull to refresh

Comments 17

неплохо бы еще проверять, что вернула socket.socket

Это уже конкретно про питон... А что она может возвращать кроме объекта оборачивающего сокет? Вроде же бросать исключение должна если что не так?

на питоне практически не пишу, так что действительно могу ошибаться. Смущает обращение к полученному объекту "sock.settimeout" без проверки и вне try..except

почитал, по идее сам вызов socket.socket в случае неудачи вызовет исключение. сишный стереотип сработал

ну что ж, всё-таки пойнт отчасти валидный - try должен захватывать больше строк :) спасибо!

Если вы создали сокет, попытались его открыть и отвалились по таймауту - не переиспользуйте его! Для новой попытки обязательно создавайте новый сокет!

Я дико извиняюсь, но создание сокета (вызовом socket) - это и есть его открытие. Что открыли, то надо закрыть.

А connect - это не открытие сокета, а установление соединения - действие, которое пытается изменить состояние сокета, но не открывает и не закрывает его.

ну, можно так это рассматривать, особенно отталкиваясь от соображения что socket(...) с точки зрения POSIX возвращает файловый дескриптор

но положа руку на сердце сказать "открыли" нельзя - по большому счету здесь ни моё ни ваше утверждение не 100%-корректно. сокет это абстракция и то что он бывает "открытым" или закрытым этого никто не сказал :)

в частности мануал по функции поясняет её действие в таких словах:

socket() creates an endpoint for communication and returns a file descriptor that refers
to that endpoint.

в связи со сложностями перевода "endpoint" на русский точный смысл воспроизвести затруднительно но глагол здесь "creates" а не "opens"

Резюмируя - "открытие" не годится в синонимы ни к "созданию" ни к "соединению" - но по большому счету кажется, хоть как назови, только ошибок избегай :) в качестве "мнемонического пояснения" ваше рассуждение, конечно, вполне годится!

Я бы тут хотел не спорить о терминах, а разделить выделение/освобождение ресурса (в данном случае, сокета) и управление состоянием этого ресурса.

Некоторую путаницу в POSIX вносит то, что выделенный но не присоединенный сокет - штука довольно бесполезная, а close объединяет семантику закрытия соединения и освобождения дескриптора.

В этом смысле Go и Plan9 более последовательны, net.Dial и dial основременно создают объект и устанавливают соединение (но усложняют настройку сокета, которую хотелось бы сделать до попытки установления соединения).

даже в 2025 году есть ещё плохо документированные особенности - и с популярными языками, с привычными операциями - в которых несложно сделать ошибку, причём такую что будет трудновато её уловить

сначала не читают пепов, а потом живут без with

Замените пепы на маны - суть не поменяется. Если язык без RAII, то помнить о противоположных операциях выделения и освобождения ресурсов приходится всегда. Будь-то malloc/free, open/close или connect/disconnect. В противном случае неплохо бы знать как готовить местный RAII, особенно обёртки над библиотеками без RAII - как раз with в питоне про это же.

суть была в том что выделить можно в начале, высвободить в конце, а между ними долбить connect до опупения на одном и том же сокете, и вот это неправильно.

Вот код с with но правильнее он от этого не стал.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.settimeout(2.0)
    while True:
        try:
            sock.connect((HOST, PORT))
            break
        except Exception as e:
            print(f"TCP connect retry to {HOST}:{PORT} ({e})")
            time.sleep(0.5)
    # proceed using connection

А не приходит ли эта проблема от самого принципа работы API сокетов?

Пояснительная бригада говорит, что соединение по TCP в сети идентифицируется как пара пар значений, ((local host, local port), (remote host, remote port)). Цифру "local port" клиент обычно явно не указывают, её выбирает ОС автоматически при вызове bind или connect. Если коннект по каким-то причинам порвался, для переустановки соединения клиент должен поменять "local port". Иначе клиент считает, что переустанавливает соединение, а сервер считает, что продолжается старое.

Сокет просто запоминает цифру local port, когда ОС её назначила. И забыть сокет её просто так не может. Чтобы забыть, приходится создавать новый сокет.

А не приходит ли эта проблема от самого принципа работы API сокетов?

id сокета aka sockfd и tcp порт это не одни и те же сущности. Главным образом потому, что сокет не обязан быть именно TCP. bind резолвит локальный порт, только если ему явно был передан нулевой порт в адресе. При connect вы ручками указвыаете к какому порту подключаться, так что нет там никакого автоматически. Если connect провалился, то вас обычно уже ничего не спасёт, ибо указанный удалённый сервер недоступен. Пересоздавайте сокет заново и пробуйте снова.

Если вы описывали разрыв соединения, то там есть некоторый таймаут пока TCP соединение решит, что попытки передачи кончились и клиент/сервер потерялся и не отвечает. TCP пакеты завернуты в пакеты IP и у того уже есть эти четыре значения.

Так что нет, API сокетов тут не причём. По крайней мере не в том виде, в котором вы его описываете.

Если автор кода явно говорит AF_INET и SOCK_STREAM, было бы странно ожидать не TCP, а какой нибудь "PPP over CAN". Хотя... С применением впн скоро и не такое увидим)))

Пересоздавайте сокет заново и пробуйте

В том-то и вопрос. Не все понимают, почему нужно именно пересоздать сокет, а не воспользоваться старым. Я тоже не до конца понимаю, и просто строю предположение.

Но я хорошо понимаю, что творится на физическом уровне, и на уровне IP-пакетов. Поэтому, так сказать, стучусь снизу.

а не воспользоваться старым

самый простой ответ - нужно сбросить состояние сокета до некоторого рабочего, для чего требуется занулить одни поля и выставить в дефолтное положение другие и перекинуть из одной очереди в другую. Что по сути на 90+% пересекается с созданием нового сокета, поэтому просто забываем о старом sockfd и генерим новый. Получаем меньше кода в ядре, меньше кода в пространстве пользователя, меньше багов в среднем.

Если автор кода явно говорит AF_INET и SOCK_STREAM, было бы странно ожидать не TCP

Собственно, ничто не мешает прокинуть другие параметры и получить ровно те же проблемы, поэтому и написал, что проблема не специфична для TCP сокета, а в принципе для любого сокета - хоть датаграммами кидайся, хоть голыми IP пакетами, хоть виртуалкам сокеты тереби.

close() closes a file descriptor, so that it no longer refers to any file and may be reused.

То бишь по крайней мере если мы пишем прямо на С и используем системные функции без каких-то обёрток сделанных языком более высокого уровня, то сокет (точнее файловый дескриптор созданный вызовом socket(...)) переиспользовать все-таки можно - главное закрыть его!

Тут речь идёт не о переиспользовании переменной в юзерспейсе, а о том, что после закрытия файлового дескриптора в ядре, это место в таблице файловых дескрипторов процесса станет свободным, и при открытии нового файла или сокета, ядро просто переиспользует первый свободный дескриптор.

Например, если вы закроете STDIN (дескриптор которого имеет номер 0), а затем откроете любой файл, то у этого файла дескриптор будет 0 за счёт переиспользования. Но этот дескриптор уже не будет иметь никакого отношения к тому, что было нулём раньше. И в этом плане никакое переиспользование в юзерспейсе не выйдет.

Sign up to leave a comment.

Articles