При закрытии сокета срабатывает коллбэк. C#
8197
19
Viper
activist
Здравствуйте.
У меня есть вопрос по сокетам в .NET (старая история
В общем, я создаю на сервере сокет, начинаю им "слушать". Когда клиент цепляется к этому сокету - срабатывает коллбэк, указанный в BeginAccept; мы достаем с помощью EndAccept рабочий сокет и обмениваемся данными с клиентом.
Теперь, если серверу вдруг нужно закрыть слушающий сокет (неважно уже, подсоединен клиент или нет), то при вызове Close() снова срабатывает коллбэк, однако, сокет уже разрушен, и я получаю ObjectDisposedException при попытке EndAccept, что не удивительно.
Вопрос. Как закрывать нормально сокет?
Shutdown и Disconnect падают с ошибкой "10057 WSAENOTCONN". Это тоже меня не удивляет :). Ведь сокет только слушает и выдает другие сокеты для обмена данными с клиентами, а сам никуда не коннектился. Конечно, можно отлавливать эти исключения или при входе в коллбэк проверять значение некоего маркера и выходить из него если маркер говорит об отключенном сокете.
В общем, посоветуйте, как правильно разделаться с закрытием?
У меня есть вопрос по сокетам в .NET (старая история
В общем, я создаю на сервере сокет, начинаю им "слушать". Когда клиент цепляется к этому сокету - срабатывает коллбэк, указанный в BeginAccept; мы достаем с помощью EndAccept рабочий сокет и обмениваемся данными с клиентом.
Теперь, если серверу вдруг нужно закрыть слушающий сокет (неважно уже, подсоединен клиент или нет), то при вызове Close() снова срабатывает коллбэк, однако, сокет уже разрушен, и я получаю ObjectDisposedException при попытке EndAccept, что не удивительно.
Вопрос. Как закрывать нормально сокет?
Shutdown и Disconnect падают с ошибкой "10057 WSAENOTCONN". Это тоже меня не удивляет :). Ведь сокет только слушает и выдает другие сокеты для обмена данными с клиентами, а сам никуда не коннектился. Конечно, можно отлавливать эти исключения или при входе в коллбэк проверять значение некоего маркера и выходить из него если маркер говорит об отключенном сокете.
В общем, посоветуйте, как правильно разделаться с закрытием?
Я завожу свое свойство disconnected, при вызове клоуза выставляю это свойство как флаг, чтобы не обращаться к этому сокету
Значит, все-таки маркером обойтись. Я так и поступал, но думал, может все-таки есть "красивый" способ этого избежать.
Кстати, про птичек.
Читал на днях книгу по системному программированию под Win32, сетевое программирование в том числе. Автор забугорный, описывает функции WinAPI с точки зрения юниксоида, но это так, к слову. Так вот, там утверждается, что прослушиваемый сокет закрывать (который listen(Socket,...)) низзя до завершения работы программы, и во всех примерах этот сокет закрывается при WSACleanup(). Я пробовал под дебаггером Visual C++ 2005 вручную закрыть прослушивающий сокет и отловить на нем событие FD_CLOSE (при асинхронной работе, на оконных сообщениях), управление туда не передавалось ни про shutdown(), ни при closesocket()
Читал на днях книгу по системному программированию под Win32, сетевое программирование в том числе. Автор забугорный, описывает функции WinAPI с точки зрения юниксоида, но это так, к слову. Так вот, там утверждается, что прослушиваемый сокет закрывать (который listen(Socket,...)) низзя до завершения работы программы, и во всех примерах этот сокет закрывается при WSACleanup(). Я пробовал под дебаггером Visual C++ 2005 вручную закрыть прослушивающий сокет и отловить на нем событие FD_CLOSE (при асинхронной работе, на оконных сообщениях), управление туда не передавалось ни про shutdown(), ни при closesocket()
Странно. Если я вызываю "слушателю" Close(), то (неважно как обойдя все эти колбэки) сокет действительно закрывается и можно создать новый объект и снова заставить его "слушать". Например привязать это к нажатию кнопок "Start server" и "Stop server". И в сетевом экране пропадает "прослушка" порта. Т.е. вроде как сокет действительно разрушается.
Mad_Dollar
guru
[offtopic]
[/offtopic]
... Автор забугорный, описывает функции WinAPI с точки зрения юниксоида, но это так, к слову. ...А вы думаете откуда у мелкомягких появился стек протоколов TCP/IP?
[/offtopic]
Mad_Dollar
guru
Shutdown и Disconnect падают с ошибкой "10057 WSAENOTCONN"не уверен, что это так пишется в C#, но в дельфях была конструкция:
try /* попробовать */
закрыть/открыть что-то - сделать критичную системную операцию
except /* исключение */
а вот тут мы ловим ошибку, и начинаем анализировать, почему нормально операция не произвелась, возможно определяем другой способ освобождения ресурсов, открытия/закрытия устройств, прочей чешуе, памятую что объектная модель на уровнях абстракции типа "сокет" не может преугадать все ответы ОС и оборудования.
end; /* except */
Кстати, обработка исключений - то, чем программисты-одиночки-любители часто пренебрегают, и очень зря.
Думая похожая конструкция должна быть в C#
Сейчас читают
Результаты ЕГЭ налицо
10351
52
КОФЕЙНЯ (часть 2)
208191
1000
красота и материнство (часть 36)
172989
1000
Viper
activist
Спасибо, вы все правильно сказали, в C# действительно есть обработка исключений try/catch и я ей пользуюсь :). И, конечно же, у меня работа с сокетом заключена в try, но отлавливаю я SocketException. Конечно, я могу после поставить и catch(ObjectDisposedException ex) и обрабатывать это исключение, но мой вопрос заключался в том, чтобы исключить срабатывание колбэка и, как следствие, обращения в колбэке к уничтоженному сокету. Обычно, после отлова всех ожидаемых исключений, я ставлю catch(Exception ex) и записываю его в лог (эта конструкция перехватывает абсолютно все исключения). Просто мне казалось, что возможно есть способ закрыть сокет таким образом, чтобы он не генерил колбэк.
Т.е. вроде как сокет действительно разрушается.Только что попробовал прилепить кнопку и жестоко заткнуть слушающий сокет. Действительно, закрывается и сообщение о закрытии не поступает в обработчик. Вопрос только, насколько эта операция корректна и не будет ли проблем типа "утечки ресурсов" при долгой работе программы
ASGS
guru
А вы думаете откуда у мелкомягких появился стек протоколов TCP/IP?А я вроде и не утверждал, что это именно они его придумали В книге честно написано UNIX -> Bercley (вроде так пишется, если нет, извиняюсь :o) sockets -> Windows sockets
сообщение о закрытии не поступает в обработчик. Вопрос только, насколько эта операция корректна и не будет ли проблем типа "утечки ресурсов" при долгой работе программыЭто как? Т.е. коллбэк у вас не запускается при закрытии? Или вы BeginAccept не делали?
Про "утечку ресурсов", в .NET есть сборщик мусора, может он и не "само совершенство", но благодаря ему, по идее, не должно возникать утечек.
На С++
Пишу портмаппер.
Упрощенно так:
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) - управление туда не передается
Пишу портмаппер.
Упрощенно так:
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) - управление туда не передается
Ну так по идее это правильно. Делаем Shutdown - объект не уничтожается. Close - уничтожается. Заходить вроде бы не должен. А у меня - заходит :(. А если я "слушателю" делаю сначала Shutdown - он падает (выше написано). Поэтому приходится либо отлавливать в обработчике исключение, связанное с обращением к уничтоженному объекту, либо пользоваться флагом.
Видимо, тонкости C#, возможно, даже связанные с тем самым "уборщиком мусора". Я бы посоветовал сходить на www.rsdn.ru, там много квалифицированного народа общается.
Я видел подобный вопрос на форуме www.gotdotnet.ru, но внятно топикстартеру никто ответить не смог. Попробую покопать на RDSN, хотя это скорее вопрос эстетики кода, чем реальной проблемы
Mad_Dollar
guru
Я бы пользовался try/except со флагами...
Уборка мусора, чем так славятся компиляторы "высокого уровня" решает тривиальные задачи из условий общности эксепшнов стандартных объектов.
С точки зрения обычной ОСи открытие сокета и привязка к нему процедуры обработки - естественное логичное событие, причин для сохранения саллбэк-методов после уничтожения (закрытия сокетов) в С дот Нет да и во многих языках нету, но алгоритмизм Си дот Нет позволяет иметь с точки зрения ОСи закрытый сокет и не освобожденный код/память. Научите изучать Perl, там после привязки процедуры к сокету нет варианта его отвязать без закрытия сокета, что у вас и получается. Вывод - не все мягкое что не твердое. Если я на Perl'е открываю сокет и привязываюсь (bind) к нему - отвязатся я могу лишь после его закрытия, а если отвязался закрыв сам сокет - мой код, привязанный к обработке событий сокета никогда больше не выполнится. Почему так происходит в дот-нете - есть только предположение, что сей продукт был собарн в очень большой степени абстракции.
В большинстве случаев операция "открытия сокета" и его привязки интерпретируется компиляторами более высокого уровня как одна операция. Если вы закрыли сокет, то - сборщик мусора еще не получил извещения что код не нужен (потому что это неизвестно по умолчанию - если есть код, то никто кроме автора не решит нужен он или нет), либо в целях сборки мусора собирал код и обрашение с памятью произошла неотработка "возврата указателя", перехваченного осборщиком.
Но это сложно, из простого - используйте флаг и обработку исключений, из сложного не используйте дот-нет /* имхо, не пинать =)) */
в общем много и сумбурно, попрошу не пинать и воспринимать это как поток сознания, который в печатной форме не могу объяснить =))
Уборка мусора, чем так славятся компиляторы "высокого уровня" решает тривиальные задачи из условий общности эксепшнов стандартных объектов.
С точки зрения обычной ОСи открытие сокета и привязка к нему процедуры обработки - естественное логичное событие, причин для сохранения саллбэк-методов после уничтожения (закрытия сокетов) в С дот Нет да и во многих языках нету, но алгоритмизм Си дот Нет позволяет иметь с точки зрения ОСи закрытый сокет и не освобожденный код/память. Научите изучать Perl, там после привязки процедуры к сокету нет варианта его отвязать без закрытия сокета, что у вас и получается. Вывод - не все мягкое что не твердое. Если я на Perl'е открываю сокет и привязываюсь (bind) к нему - отвязатся я могу лишь после его закрытия, а если отвязался закрыв сам сокет - мой код, привязанный к обработке событий сокета никогда больше не выполнится. Почему так происходит в дот-нете - есть только предположение, что сей продукт был собарн в очень большой степени абстракции.
В большинстве случаев операция "открытия сокета" и его привязки интерпретируется компиляторами более высокого уровня как одна операция. Если вы закрыли сокет, то - сборщик мусора еще не получил извещения что код не нужен (потому что это неизвестно по умолчанию - если есть код, то никто кроме автора не решит нужен он или нет), либо в целях сборки мусора собирал код и обрашение с памятью произошла неотработка "возврата указателя", перехваченного осборщиком.
Но это сложно, из простого - используйте флаг и обработку исключений, из сложного не используйте дот-нет /* имхо, не пинать =)) */
в общем много и сумбурно, попрошу не пинать и воспринимать это как поток сознания, который в печатной форме не могу объяснить =))
Viper
activist
Спасибо вам, в очередной разВ принципе, я так и делал, но, повторюсь, думал, что есть корректный способ закрывания, без лишнего "дергания" колбэка.
Оффтоп: в .NET сборщик мусора работает тогда, когда считает нужным :). Конечно, можно явно вызвать сборку мусора, но даже тогда я не могу быть уверенным, что мусор будет убран. Но, тем не менее, несмотря на это, .NET весьма и весьма крут
Оффтоп: в .NET сборщик мусора работает тогда, когда считает нужным :). Конечно, можно явно вызвать сборку мусора, но даже тогда я не могу быть уверенным, что мусор будет убран. Но, тем не менее, несмотря на это, .NET весьма и весьма крут
Mad_Dollar
guru
=)) оффтопик:
php программисы настолько суровы, что их уверенность в выполнении кода в одной придуманной автором локали (цп1251, кои8р, 866-я) не подразумевает выполнения в мускуле set names в принципе и явный вызов деструкторов объектов в частности =))
Не скажу что я опытный кодописатель, но практика показывает, что для тривиальных задач "сборщики" высокого уровня работают процентов на 80, позволяя экономить процентов 200 времени написания кода, но в нетривиальных ситуациях нужно смотреть лично, "чтобы код не тёк" =))
php программисы настолько суровы, что их уверенность в выполнении кода в одной придуманной автором локали (цп1251, кои8р, 866-я) не подразумевает выполнения в мускуле set names в принципе и явный вызов деструкторов объектов в частности =))
Не скажу что я опытный кодописатель, но практика показывает, что для тривиальных задач "сборщики" высокого уровня работают процентов на 80, позволяя экономить процентов 200 времени написания кода, но в нетривиальных ситуациях нужно смотреть лично, "чтобы код не тёк" =))
А почему это при вызове 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();
}
}
Прошу прощения за плохую читабельность - видимо парсер обрезает табы
приведу код, который использовался для теста. Это простейшая форма с тремя кнопками 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();
}
}
Прошу прощения за плохую читабельность - видимо парсер обрезает табы