Определите, закрыл ли сверстник считывающий конец гнезда

У меня есть ситуация с программированием сокета, когда клиент закрывает конец записи сокета, чтобы сервер знал, что ввод завершен (через прием EOF), но держит конец чтения открытым для чтения результата (одна строка текста). Было бы полезно, чтобы сервер знал, что клиент успешно прочитал результат и закрыл сокет (или, по крайней мере, закрыл конец чтения). Есть ли хороший способ проверить/дождаться такого статуса?

Ответ 1

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

Это плохой дизайн. Если серверу необходимо знать, что клиент получил данные, клиент должен его подтвердить, а это значит, что он не может завершить работу с концом записи. Клиент должен:

  • отправьте сообщение о завершении внутри сети в качестве данных.
  • прочитайте и подтвердите все дальнейшие ответы до тех пор, пока не закончится конец потока.
  • закрыть сокет.

Сервер должен обнаружить сообщение о завершении внутри диапазона и:

  • прекратить чтение запросов из сокета
  • отправить все выдающиеся ответы и прочитать подтверждения
  • закрыть сокет.

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

Ответ 2

getsockopt с TCP_INFO представляется наиболее очевидным выбором, но он не является кросс-платформенным.

Вот пример для Linux:

import socket
import time
import struct
import pprint


def tcp_info(s):
    rv = dict(zip("""
            state ca_state retransmits probes backoff options snd_rcv_wscale
            rto ato snd_mss rcv_mss unacked sacked lost retrans fackets
            last_data_sent last_ack_sent last_data_recv last_ack_recv
            pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering
            rcv_rtt rcv_space
            total_retrans
            pacing_rate max_pacing_rate bytes_acked bytes_received segs_out segs_in
            notsent_bytes min_rtt data_segs_in data_segs_out""".split(),
                  struct.unpack("BBBBBBBIIIIIIIIIIIIIIIIIIIIIIIILLLLIIIIII",
                                s.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 160))))
    wscale = rv.pop("snd_rcv_wscale")

    # bit field layout is up to compiler
    # FIXME test the order of nibbles
    rv["snd_wscale"] = wscale >> 4
    rv["rcv_wscale"] = wscale & 0xf
    return rv

for i in range(100):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 7878))
    s.recv(10)
    pprint.pprint(tcp_info(s))

Я сомневаюсь, что существует настоящая кросс-платформенная альтернатива.

В основном существует довольно много состояний:

  • вы написали данные в сокет, но он еще не был отправлен
  • данные были отправлены, но не получены
  • данные были отправлены и потеряны (зависит от таймера)
  • данные были получены, но еще не подтверждены
  • подтверждение еще не получено
  • подтверждение потеряно (зависит от таймера)
  • данные были получены удаленным хостом, но не считаны приложением
  • данные были зачитаны приложением, но сокет все еще жив
  • данные были зачитаны, а приложение разбито.
  • данные были зачитаны, а приложение закрыло сокет
  • данные были зачитаны, а приложение называется shutdown(WR) (почти такое же, как закрыто)
  • FIN не был отправлен удаленно еще
  • FIN был отправлен удаленно, но еще не получен.
  • FIN был отправлен и потерян
  • FIN получен по вашему желанию

Очевидно, что ваша ОС может отличить довольно многие из этих состояний, но не все из них. Я не могу придумать API, который был бы таким подробным...

Некоторые системы позволяют запрашивать оставшееся пространство буфера отправки. Возможно, если вы это сделали, и сокет уже был выключен, вы бы поняли ошибку?

Хорошая новость заключается только в том, что сокет закрыт, не означает, что вы не можете его допросить. После завершения работы я могу получить все TCP_INFO, state=7 (закрыто). В некоторых случаях сообщать state=8 (закрыть wait).

http://lxr.free-electrons.com/source/net/ipv4/tcp.c#L1961 содержит все подробные сведения о конечной машине Linux TCP.

Ответ 3

TL; DR:

Не полагайтесь на состояние сокета для этого; он может вырезать вас во многих случаях с ошибками. Вам необходимо испечь устройство подтверждения/получения в свой протокол связи. Первый символ в каждой строке, используемой для status/ack, очень хорошо работает для текстовых протоколов.


Во многих, но не во всех системах Unix-like/POSIXy можно использовать TIOCOUTQ (также SIOCOUTQ) ioctl, чтобы определить, сколько данных осталось в исходящем буфере.

Для сокетов TCP, даже если другой конец отключил свою сторону записи (и, следовательно, не отправит больше данных с этой целью), все передачи будут подтверждены. Данные в исходящем буфере удаляются только после получения подтверждения от ящика получателя. Таким образом, когда в исходящем буфере больше нет данных, мы знаем, что ядро ​​на другом конце получило данные.

К сожалению, это не означает, что приложение получило и обработало данные. Это же ограничение распространяется на все методы, которые полагаются на состояние сокета; это также является причиной, по которой в принципе подтверждение получения/принятия окончательной строки состояния должно происходить из другого приложения и не может быть автоматически обнаружено.

Это, в свою очередь, означает, что ни один из них не может закрыть свои отправляющие стороны до самого окончательного сообщения о получении/подтверждении. Вы не можете полагаться на TCP - или любые другие протоколы - автоматическое управление состояниями сокетов. Вы должны испечь критические квитанции/подтверждения в сам протокол потока.

В случае OP поток протокола кажется простым текстовым текстом. Это довольно полезно и легко разобрать. Одним из надежных способов "расширения" такого протокола является резервирование первого символа каждой строки для кода состояния (или, альтернативно, резервирование определенных односимвольных строк в качестве подтверждений).

Для больших бинарных протоколов в полете (т.е. протоколов, где отправитель и получатель на самом деле не синхронизированы), полезно маркировать каждый кадр данных с увеличением (циклическим) целым числом и иногда реагировать на другой конец, с обновлением, позволяющим отправителю узнать, какие кадры были полностью обработаны и какие из них получены, и должны ли скоро появляться дополнительные кадры/не очень скоро. Это очень полезно для сетевых устройств, которые потребляют много данных, с поставщиком данных, желающим быть обновленным по ходу и желаемой скорости передачи данных (думаю, 3D-принтеры, машины с ЧПУ и т.д., Где содержимое данных изменяет максимально допустимую скорость передачи данных динамически).

Ответ 4

Хорошо, поэтому я вспоминаю, что вытаскиваю волосы, пытаясь решить эту проблему еще в конце 90-х. Я наконец нашел неясный документ, который заявил, что вызов чтения в отключенном сокете вернет 0. Я использую этот факт по сей день.

Ответ 5

Вероятно, вам лучше использовать ZeroMQ. Это отправит целое сообщение или вообще не будет. Если вы установите его, отправьте длину буфера на 1 (самое короткое из них будет доступно), вы можете проверить, заполнен ли буфер отправки. Если нет, сообщение было успешно передано, возможно. ZeroMQ также очень приятен, если у вас ненадежное или прерывистое сетевое подключение как часть вашей системы.

Это все еще не совсем удовлетворительно. Возможно, вам даже лучше реализовать свой собственный механизм подтверждения отправки поверх ZeroMQ. Таким образом, у вас есть абсолютное доказательство того, что сообщение получено. У вас нет доказательств того, что сообщение не получено (что-то может пойти не так, как между испуском и получением ack, и вы не можете решить проблему с двумя генералами). Но это лучшее, что может быть достигнуто. Тогда вы должны реализовать архитектуру Communicating Sequential Processes поверх модели актера ZeroMQ, которая сама реализована поверх потоков TCP. В конечном счете это немного медленнее, но ваше приложение имеет больше уверенности в знании того, что произошло.