Установка KeepAlive опции для Windows сокетов

312
09 июля 2017, 11:04

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'а при отключении удалённого компьютера от сети я дождаться не могу (в течении заданной секунды, да и вообще никогда). Подскажите, в чём причина?

Answer 1

Решение состоит в отказе от нативной прослойки в виде 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);
    }
READ ALSO
Недопустимое имя столбца

Недопустимое имя столбца

Переопределил DbContext и указал модель по которой буду работь с БД:

324
Имя &ldquo;Console&rdquo; не существует в текущем контексте. Visual Studio 2017 c#

Имя “Console” не существует в текущем контексте. Visual Studio 2017 c#

Перешел на Windows 10Поставил Visual Studio 2017 года

501
Сравнение DateTime через LINQ запрос

Сравнение DateTime через LINQ запрос

Нужно перебрать коллекцию, и вытащить элемент, дата которого равна заданнойПишу LINQ запрос:

352