Помогите написать программу мониторинга сетевой активности компьютера на C#

Мне нужно написать программу для мониторинга сетевой активности


Ответы (1 шт):

Автор решения: return

Вы используете неверный метод считывания данных, т. е. polling (ссылка). Тут лучше подойдёт interrupting.

public async Task<UserInfo> GetRemoteInfoAsync()
{
    // Обращение к серверу
}

Тут:

  • UserInfo - инфа об юзере (в которой, конечно, содержится наш UserStatus)

  • GetRemoteInfoAsync - метод извлечения этой инфы с удалённого сервера

Исходя и этого нам нужны:

  • Формат клиент-сервер сообщений

  • Классы для клиента/сервера

Пахнет low-level работёнкой с сокетами (если вы действительно хотите понять, как это работает).

Реализация

Формат сообщений

Мне в голову пришли вот такие команды:

  • HELLO - команда создаёт эксклюзивный сокет для вашего клиента и возвращает его адрес вместе с версией сервера. Пример ответа: v1.0 56437

  • QUIT - освобождает ваш ранее созданный сокет и завершает сеанс. Пример ответа: Good bye!

  • INFO - возвращает инфу. Здесь ответ - сериализованный объект UserInfo

Хэлперы

public static class IpServices
{
    IPAddress localIpAddress;

    public IPAddress LocalIpAddress
    {
        get
        {
            localIpAddress ??= GetLocalIpAddress();

            return localIpAddress;
        }
    }

    static IPAddress GetLocalIpAddress()
    {
        var props = IPGlobalProperties.GetIPGlobalProperties();

        return Dns.GetHostAddresses(props.HostName)[0];
    }
    public static FreePortEnumerable EnumerateFreePorts()
    {
        var props = IPGlobalProperties.GetIPGlobalProperties();

        return new FreePortEnumerable(props);   
    }
    public static int GetNextFreePort()
    {
        var freePortSeq = EnumerateFreePorts();
        using var freePortIter = freePortSeq.GetEnumerator();

        return freePortIter.MoveNext() ? freePortIter.Current : -1;
    }

    public readonly struct FreePortEnumerable : IEnumerable<int>
    {
        readonly IPEndPoint[] activeEndPoints;

        internal FreePortEnumerable(IPGlobalProperties properties)
        {
            if (properties == null) throw new ArgumentNullException("properites");

            activeEndPoints = props.GetActiveTcpListeners();
        }

        public Enumerator GetEnumerator() => new Enumerator(this);
        IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        public struct Enumerator : IEnumerator<int>
        {
            readonly IEnumerator<IPEndPoint> activeEndPointIter;

            public int Current { get; private set; }

            internal Enumerator(FreePortEnumerable seq)
            {
                activeEndPointIter = seq.activeEndPoints.GetEnumerator();
                Current = 1024;
            }

            public void Dispose() { }
            public bool MoveNext()
            {
                while (!activeEndPointIter.MoveNext() ||
                    activeEndPointIter.Current != Current) { }

                if (Current = ushort.MaxValue) return false;

                Current++;

                return true;
            }
            public void Reset()
            {
                activeEndPointIter.Reset();
                Current = 1024;
            }
        }
    }
}
public sealed class MyListenerPool
{
    readonly Dictionary<int, MyListener> listeners; // чтоб GC не коллектил

    public MyServer Server { get; }
    public MyListener DefaultListener { get; }

    internal MyListenerPool(MyServer server)
    {
        Server = server ?? throw new ArgumentNullException("server");
        var defaultListenerPort = IpServices.GetNextFreePort();

        if (defaultListenerPort == -1)
            throw new SocketException((int)SocketError.TooManyOpenSockets);

        DefautListener = new MyListener(new IPAddress(IpServices.LocalIpAddress, defaultListenerPort), true);
    }

    public bool TryRent(out MyListener listener)
    {
#pragma warning disable CS86025
        listener = null;
#pragma warning restore CS86025

        var listenerPort = ipServices.GetNextFreePort();

        if (listenerPort == -1)
            return false;
        else
        {
            listener = new MyListener(new IPAddress(IpServices.LocalIpAddress, listenerPort));
            listener.RegisterListenCallback(Server.HandleRequest);

            lock (syncRoot)
            {
                listeners.Add(listener.LocalEndPoint.Port, listener);
            }

            return true;
        }
    }
    public MyListener Rent()
    {
        while (!TryRent(out var listener)) { }

        return listener;
    }
}
public sealed class MyListener : IDisposable
{
    readonly object syncRoot = new object();

    public Socket Socket { get; }
    public IPEndPoint LocaEndPoint => Socket.LocalEndPoint as IPEndPoint;
    public Socket? ClientSocket { get; private set; }
    public IPEndPoint RemoteEndPoint
    {
        get
        {
            if (!IsConnected) throw new InvalidOperationException("Disconnected listeners have not remote endpoint");

            return ClientSocket.RemoteEndPoint;
        }
    }
    public bool IsConnected => ClientSocket != null;

    internal MyListener(IPEndPoint localEndPoint, bool isDefault = false)
    {
        if (localEndPoint == null) throw new ArgumentNullException("endPoint");

        Socket = new Socket(SocketType.Stream, ProtocolType.Ip);
        Socket.Bind(localEndPoint);
        Socket.Listen(isDefault ? 4096: 8);
    }

    public void Accept()
    {
        ClientSocket = Socket.Accept();
    }
    public bool Disconnect()
    {
        if (!IsConnected) return false;

        ClientSocket.Close();

        return true;
    }
    public bool TryReceiveRequest(out MyRequest request)
    {
#pragma warning disable CS86025
        request = null;
#pragma warning restore CS86025

        var dataSegmentOwners = new ValueArrayBuilder<IMemoryOwner>(stackalloc IMemoryOwner[8]);
        var dataBuilder = new ValueSequenceBuilder();
        Span<byte> buffer = stackalloc byte[1024];

        try
        {
            while (true)
            {
                var receivedBytes = Socket.Receive(buffer);

                if (receivedBytes = 0) break;

                var dataSegmentOwner = MemoryPool<byte>.Shared.Rent(receivedBytes);
                var dataSegment = dataSegmentOwner.Memory;

                dataSegmentOwners.Add(dataSegmentOwner);
                dataBuilder.Append(dataSegment);
            }

            var data = dataBuilder.Build();
            using var stream = SequenceStream(data);

            request = new MyRequestSerializer().Deserialize(stream, out request);
        }
        finally
        {
            for (var i = 0; i < dataSegmentOwners.Length; i++)
                dataSegmentOwners[i].Dispose();
        }

        return true;
    }
    public MyRequest ReceiveRequest()
    {
        while (!TryReceiveMessage(out var request)) { }

        return request;
    }
    public Task ListenAsync(CancellationToken cancellationToken = default) => new Task(() =>
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            if (!TryReceiveRequest(out var request)) continue;

            lock (syncRoot)
            {
                foreach (var _callback in GetListenCallbacks())
                    _callback(request);
            }
        }
    });
    public void SendResponse(MyResponse response)
    {

    }
    public ListenCallbackRegistation RegisterListenCallback(Action<MyRequest> callback)
    {
        // ...
    }

    public struct ListenCallbackRegistration : IDisposable
    {
        // ...
    }
}
public sealed class MyRequest
{
    public MyRequestCommandCode CommandCode { get; }
    public ReadOnlyCollection<ReadOnlyMemory<char>> Args { get; }

    // ...
}
public ref struct ValueSequenceBuilder<T>
{
    // ...
}

Ссылка1, ссылка2

public struct MyRequestSerializer
{
    // Метод void Serialize(Stream stream, MyMessage message);
    // Метод MyMessage Deserialize(Stream stream);
}
public sealed class SequenceStream : Stream
{
    // Реализация через ReadOnlySequence
}
public sealed class MyResponse
{
    // Ну, думаю вы поняли)
}

Асинхронные методы, а также методы диспозинга скипнуты. ValueXXX типы - облегчённые аналоги их не like-value типов.

Класс сервера

public sealed class MyServer
{
    readonly Dictionary<ReadOnlyMemory<char>, UserInfo> users;
    readonly object syncRoot = new object();

    public MyListenerPool ListenerPool { get; }
    public MyListener DefaultListener { get; }

    public MyServer()
    {
        ListenerPool = new MyListenerPool(this);
        users = new Dictionary<ReadOnlyMemory<char>, UserInfo>();
    }

    public void HandleRequest(MyListener listener, MyRequest request)
    {
        if (request == null) throw new ArgumentNullException("request");

        switch (request.CommandCode)
        {
            MyRequestCommandCode.Hello:
                var task = new Task(l =>
                {
                    const string responseFormat = "";

                    var _listener = (MyListener)l;                    

                    var newListener = ListenerPool.Rent();
                    newListener.ListenAsync();

                    var response = new MyResponse(ValueString.Format(responseFormat, newListener.LocalEndPoint.Port));
                    _listener.SendResponse(response);
                }, listener);
                task.Start();

                break;
            MyRequestCommandCode.Quit:
                listener.Disconnect();

                break;
            MyRequestCommandCode.Info:
                UserInfo info;

                lock (syncRoot)
                {
                    info = users[request.Args[0]];
                }

                var task = new Task(l =>
                {
                    // response sending (я уже устал)
                });

                break;
        }
    }
}

Класс клиента

Тут ещё проще чем с сервером. Я его не буду писать, т. к. вам нужно просто прочитать про сокеты.


ВАЖНО: Это всё ещё псевдо-код! Он всё ещё нуждается в дальнейшей доработке. Я постарался за 1 присест записать весь код. Внимательно проделайте его review (просто держите у себя в голове, что я тупой, если найдёте ошибку).

→ Ссылка