При закрытии сокета срабатывает коллбэк. C#
7972
19
Здравствуйте.
У меня есть вопрос по сокетам в .NET (старая история:улыб:
В общем, я создаю на сервере сокет, начинаю им "слушать". Когда клиент цепляется к этому сокету - срабатывает коллбэк, указанный в BeginAccept; мы достаем с помощью EndAccept рабочий сокет и обмениваемся данными с клиентом.
Теперь, если серверу вдруг нужно закрыть слушающий сокет (неважно уже, подсоединен клиент или нет), то при вызове Close() снова срабатывает коллбэк, однако, сокет уже разрушен, и я получаю ObjectDisposedException при попытке EndAccept, что не удивительно.
Вопрос. Как закрывать нормально сокет?
Shutdown и Disconnect падают с ошибкой "10057 WSAENOTCONN". Это тоже меня не удивляет :). Ведь сокет только слушает и выдает другие сокеты для обмена данными с клиентами, а сам никуда не коннектился. Конечно, можно отлавливать эти исключения или при входе в коллбэк проверять значение некоего маркера и выходить из него если маркер говорит об отключенном сокете.
В общем, посоветуйте, как правильно разделаться с закрытием?
Viper
Я завожу свое свойство disconnected, при вызове клоуза выставляю это свойство как флаг, чтобы не обращаться к этому сокету
Дима553
Значит, все-таки маркером обойтись. Я так и поступал, но думал, может все-таки есть "красивый" способ этого избежать.
Viper
Кстати, про птичек.
Читал на днях книгу по системному программированию под Win32, сетевое программирование в том числе. Автор забугорный, описывает функции WinAPI с точки зрения юниксоида, но это так, к слову. Так вот, там утверждается, что прослушиваемый сокет закрывать (который listen(Socket,...)) низзя до завершения работы программы, и во всех примерах этот сокет закрывается при WSACleanup(). Я пробовал под дебаггером Visual C++ 2005 вручную закрыть прослушивающий сокет и отловить на нем событие FD_CLOSE (при асинхронной работе, на оконных сообщениях), управление туда не передавалось ни про shutdown(), ни при closesocket()
ASGS
Странно. Если я вызываю "слушателю" Close(), то (неважно как обойдя все эти колбэки) сокет действительно закрывается и можно создать новый объект и снова заставить его "слушать". Например привязать это к нажатию кнопок "Start server" и "Stop server". И в сетевом экране пропадает "прослушка" порта. Т.е. вроде как сокет действительно разрушается.
ASGS
[offtopic]
... Автор забугорный, описывает функции WinAPI с точки зрения юниксоида, но это так, к слову. ...
А вы думаете откуда у мелкомягких появился стек протоколов TCP/IP?
[/offtopic]
Viper
Shutdown и Disconnect падают с ошибкой "10057 WSAENOTCONN"
не уверен, что это так пишется в C#, но в дельфях была конструкция:
try /* попробовать */
закрыть/открыть что-то - сделать критичную системную операцию
except /* исключение */
а вот тут мы ловим ошибку, и начинаем анализировать, почему нормально операция не произвелась, возможно определяем другой способ освобождения ресурсов, открытия/закрытия устройств, прочей чешуе, памятую что объектная модель на уровнях абстракции типа "сокет" не может преугадать все ответы ОС и оборудования.
end; /* except */
Кстати, обработка исключений - то, чем программисты-одиночки-любители часто пренебрегают, и очень зря.

Думая похожая конструкция должна быть в C#
Mad_Dollar
Спасибо, вы все правильно сказали, в C# действительно есть обработка исключений try/catch и я ей пользуюсь :). И, конечно же, у меня работа с сокетом заключена в try, но отлавливаю я SocketException. Конечно, я могу после поставить и catch(ObjectDisposedException ex) и обрабатывать это исключение, но мой вопрос заключался в том, чтобы исключить срабатывание колбэка и, как следствие, обращения в колбэке к уничтоженному сокету. Обычно, после отлова всех ожидаемых исключений, я ставлю catch(Exception ex) и записываю его в лог (эта конструкция перехватывает абсолютно все исключения). Просто мне казалось, что возможно есть способ закрыть сокет таким образом, чтобы он не генерил колбэк.
Viper
Т.е. вроде как сокет действительно разрушается.
Только что попробовал прилепить кнопку и жестоко заткнуть слушающий сокет. Действительно, закрывается и сообщение о закрытии не поступает в обработчик. Вопрос только, насколько эта операция корректна и не будет ли проблем типа "утечки ресурсов" при долгой работе программы
Mad_Dollar
А вы думаете откуда у мелкомягких появился стек протоколов TCP/IP?
А я вроде и не утверждал, что это именно они его придумали:улыб: В книге честно написано UNIX -> Bercley (вроде так пишется, если нет, извиняюсь :o) sockets -> Windows sockets
ASGS
сообщение о закрытии не поступает в обработчик. Вопрос только, насколько эта операция корректна и не будет ли проблем типа "утечки ресурсов" при долгой работе программы
Это как? Т.е. коллбэк у вас не запускается при закрытии? Или вы BeginAccept не делали?
Про "утечку ресурсов", в .NET есть сборщик мусора, может он и не "само совершенство", но благодаря ему, по идее, не должно возникать утечек.
Viper
На С++
Пишу портмаппер.
Упрощенно так:
SOCKET lstsock;
lstsock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
bind(lstsock,...);
listen(lstsock,SOMAXCON);
WSAAsyncSelect(lstsock,hMainWnd,WM_ASYNC_CLIENTEVENT,FD_ACCEPT|FD_READ|FD_CLOSE);
в обработчике сообщения WM_ASYNC_CLIENTEVENT, когда FD_ACCEPT:
SOCKET newsock = accept(lstsock,NULL,NULL);
При этом newsock наследует параметры lstsock, в том числе обработчик сообщения WM_ASYNC_CLIENTEVENT.
Так вот, в этот самый обработчик управление прекрасно заходит, когда к программе "стучатся" со стороны в слущающий сокет, во второй, в третий раз и т.д. Заходит, когда закрывается newsock, программно через shutdown() или со стороны. Естественно, еще когда данные приходят. А вот когда closesocket(lstsock) или shutdown(lstsock) - управление туда не передается :шок:
ASGS
Ну так по идее это правильно. Делаем Shutdown - объект не уничтожается. Close - уничтожается. Заходить вроде бы не должен. А у меня - заходит :(. А если я "слушателю" делаю сначала Shutdown - он падает (выше написано). Поэтому приходится либо отлавливать в обработчике исключение, связанное с обращением к уничтоженному объекту, либо пользоваться флагом.
Viper
Видимо, тонкости C#, возможно, даже связанные с тем самым "уборщиком мусора". Я бы посоветовал сходить на www.rsdn.ru, там много квалифицированного народа общается.
ASGS
Я видел подобный вопрос на форуме www.gotdotnet.ru, но внятно топикстартеру никто ответить не смог. Попробую покопать на RDSN, хотя это скорее вопрос эстетики кода, чем реальной проблемы:улыб:
Viper
Я бы пользовался try/except со флагами...
Уборка мусора, чем так славятся компиляторы "высокого уровня" решает тривиальные задачи из условий общности эксепшнов стандартных объектов.
С точки зрения обычной ОСи открытие сокета и привязка к нему процедуры обработки - естественное логичное событие, причин для сохранения саллбэк-методов после уничтожения (закрытия сокетов) в С дот Нет да и во многих языках нету, но алгоритмизм Си дот Нет позволяет иметь с точки зрения ОСи закрытый сокет и не освобожденный код/память. Научите изучать Perl, там после привязки процедуры к сокету нет варианта его отвязать без закрытия сокета, что у вас и получается. Вывод - не все мягкое что не твердое. Если я на Perl'е открываю сокет и привязываюсь (bind) к нему - отвязатся я могу лишь после его закрытия, а если отвязался закрыв сам сокет - мой код, привязанный к обработке событий сокета никогда больше не выполнится. Почему так происходит в дот-нете - есть только предположение, что сей продукт был собарн в очень большой степени абстракции.
В большинстве случаев операция "открытия сокета" и его привязки интерпретируется компиляторами более высокого уровня как одна операция. Если вы закрыли сокет, то - сборщик мусора еще не получил извещения что код не нужен (потому что это неизвестно по умолчанию - если есть код, то никто кроме автора не решит нужен он или нет), либо в целях сборки мусора собирал код и обрашение с памятью произошла неотработка "возврата указателя", перехваченного осборщиком.
Но это сложно, из простого - используйте флаг и обработку исключений, из сложного не используйте дот-нет /* имхо, не пинать =)) */
в общем много и сумбурно, попрошу не пинать и воспринимать это как поток сознания, который в печатной форме не могу объяснить =))
Mad_Dollar
Спасибо вам, в очередной раз:улыб:В принципе, я так и делал, но, повторюсь, думал, что есть корректный способ закрывания, без лишнего "дергания" колбэка.
Оффтоп: в .NET сборщик мусора работает тогда, когда считает нужным :). Конечно, можно явно вызвать сборку мусора, но даже тогда я не могу быть уверенным, что мусор будет убран. Но, тем не менее, несмотря на это, .NET весьма и весьма крут:улыб:
Viper
=)) оффтопик:

php программисы настолько суровы, что их уверенность в выполнении кода в одной придуманной автором локали (цп1251, кои8р, 866-я) не подразумевает выполнения в мускуле set names в принципе и явный вызов деструкторов объектов в частности =))
Не скажу что я опытный кодописатель, но практика показывает, что для тривиальных задач "сборщики" высокого уровня работают процентов на 80, позволяя экономить процентов 200 времени написания кода, но в нетривиальных ситуациях нужно смотреть лично, "чтобы код не тёк" =))
Viper
А почему это при вызове Close срабатывает калбэк, на BeginAccept ? Может быть сразу по месту Close надо позаботиться, чтобы не вызывать BeginAccept ? Скорее всего вы закрыли сокет в каком то потоке, но в этот момент произошел вызов BeginAccept в другом потоке и ему был передан уже дохлый сокет.
Житель
Нет, к сожалению:улыб:Колбэк действительно вызывается, причем не только в случае с BeginAccept, но и с BeginReceive, например.
приведу код, который использовался для теста. Это простейшая форма с тремя кнопками button1, button2, button3. Первая инициирует прослушивание порта (создаю "слушающий" сокет", вторая - подсоединяет новый сокет к этому порту (метод Connect), третья закрывает "слушателя".
public partial class Form1 : Form
{

Socket acceptor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket connector = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket sender;

public Form1()
{
InitializeComponent();
acceptor.Bind(new IPEndPoint(IPAddress.Loopback, 12000));
acceptor.Listen(1);
}

private void button1_Click(object sender, EventArgs e)
{
//Начинаем слушать
acceptor.BeginAccept(new AsyncCallback(OnAccepted), null);
}

private void button2_Click(object sender, EventArgs e)
{
connector.Connect(new IPEndPoint(IPAddress.Loopback, 12000));
}

private void OnAccepted(IAsyncResult result)
{
//Соединение установлено, сокет для обмена данными создан
sender = acceptor.EndAccept(result);//Вот здесь возникает исключение, т.к. уже был вызван Close().
//BeginAccept не вызывался больше ни в одном потоке, т.к. их нет:улыб:


sender.Close();
//Сокет для обмена данными уничтожен, продолжаем слушать дальше
acceptor.BeginAccept(new AsyncCallback(OnAccepted), null);
}


void Button3Click(object sender, EventArgs e)
{
//Закрываем слушателя
acceptor.Close();
}
}
Прошу прощения за плохую читабельность - видимо парсер обрезает табы