C#:
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void SetupKeepAliveOpt(IntPtr sock);
static void ProcessIncomingConnection(Socket socket) {
Console.WriteLine("Incoming connection!");
while (true) {
byte[] package = new byte[1024];
socket.Receive(package);
List<byte> packageList = new List<byte>(package);
while (packageList.Count > 0 && packageList.Last() == 0) packageList.RemoveAt(packageList.Count - 1);
package = packageList.ToArray();
if (package.Length == 0) break;
Console.WriteLine("\nReceived: " + Encoding.ASCII.GetString(package) + "\nSize: " + package.Length);
}
Console.WriteLine("End");
}
static void Main(string[] args) {
string remoteIp = "192.168.1.2";
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (Console.ReadKey().Key == ConsoleKey.D1) {
socket.Bind(new IPEndPoint(IPAddress.Any, 19000));
socket.Listen(10);
while (true) ProcessIncomingConnection(socket.Accept());
}
else {
socket.Connect(new IPEndPoint(IPAddress.Parse(remoteIp), 19000));
SetupKeepAliveOpt(socket.Handle);
while(true) {
Console.Write("Send: ");
string data = Console.ReadLine();
if (data.Length == 0) break;
socket.Send(Encoding.ASCII.GetBytes(data));
}
}
socket.Close();
Console.WriteLine("CLOSED");
Console.ReadKey();
}
C++ (dll):
// SetSockOpt.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "windows.h"
#include "Ws2def.h"
#include "Winsock2.h"
#include "Mstcpip.h"
#include "iostream"
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
typedef void(*LPSOCKCLOSEPROC)(DWORD sock);
DWORD WINAPI ErrorWaiting(LPVOID lpParameter)
{
DWORD dw = WaitForSingleObject(*(HANDLE*)lpParameter, INFINITE);
MessageBox(NULL, L"Event!", NULL, NULL);
return 0;
}
extern "C" __declspec(dllexport) void SetupKeepAliveOpt(DWORD sock)
{
SOCKET socket = (SOCKET)sock;
struct tcp_keepalive tcpkeepalive;
tcpkeepalive.onoff = TRUE;
tcpkeepalive.keepalivetime = 200;
tcpkeepalive.keepaliveinterval = 1000;
//
// Send socket IO code.
//
LPDWORD lpcbBytesReturned;
INT nSize;
WSAIoctl(socket,
SIO_KEEPALIVE_VALS,
(LPVOID)&tcpkeepalive,
sizeof(tcpkeepalive),
NULL, 0,
reinterpret_cast<DWORD*>(&nSize),
NULL, NULL);
//
// Setup socket close event.
//
HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, false, L"CloseEvent");
WSAEventSelect(socket, hEvent, FD_CLOSE);
DWORD dwThreadId;
CreateThread(NULL, 0, ErrorWaiting, &hEvent, 0, &dwThreadId);
}
Данный код работает без ошибок, но уведомления в виде msgbox'а при отключении удалённого компьютера от сети я дождаться не могу (в течении заданной секунды, да и вообще никогда). Подскажите, в чём причина?
Решение состоит в отказе от нативной прослойки в виде dll, т.к. всё достаточно хорошо реализуется средствами самого C# :)
socket.SetSocketOption результата не даст, т.к. KeepAlive опция включена по-умолчанию. Интервал между пробами составляет порядка 2 часов.
Чтобы понизить это смешно огромное время нужно настроить опцию. Причём на обеих сторонах соединения (желательно одинаково).
Настройка выполняется через socket.IOControl. Как сделать это есть ответ здесь. Так же могу предложить хорошую статью по этой теме (там же есть рабочий пример на C++): https://rsdn.org/article/net/keep_alive.xml
Теперь, когда после подключения опция настроена, мы можем спокойно работать. Если удалённая сторона внезапно отключится, мы по истечению заданного нами таймаута на попытках операций с сокетом (Receive, Send) будем получать исключения.
Я бы так же отметил, что лично заметил одну странность. Интервалы в keepalive структуре задаются в десятках мс или что-то около этого. Хотя везде пишут, что там просто миллисекунды. Имхо, но если в C#, где мы устанавливаем опцию через socket.IOControl написать keepaliveinterval = 500, то после того как удалённая система будет отключена от сети, исключение на моменте отправки будет начинать вылезать спустя минимум 4700 мс (а обычно 5000 +- 80-100 мс).
static void SetKeepAliveOption(Socket socket) {
int size = Marshal.SizeOf(new uint());
byte[] optInValue = new byte[size * 3];
BitConverter.GetBytes((uint)1).CopyTo(optInValue, 0);
BitConverter.GetBytes((uint)20).CopyTo(optInValue, size);
BitConverter.GetBytes((uint)500).CopyTo(optInValue, size * 2);
socket.IOControl(IOControlCode.KeepAliveValues, optInValue, null);
}
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости