асинхронная функция с множественным ожиданием, вызывающим проблему - PullRequest
0 голосов
/ 03 ноября 2019

В моем клиентском приложении каждый клиент получает два Socket, один для получения и другой для отправки и подключения к серверу следующим образом:

async void Connect2Server(object obj)
{
    recvArgs = new SocketAsyncEventArgs();
    sendArgs = new SocketAsyncEventArgs();
    recvArgs.AcceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    sendArgs.AcceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    await recvArgs.AcceptSocket.ConnectAsync(host, port);
    await recvArgs.AcceptSocket.SendAsync(Info<SockInfo>.Pack(new SockInfo()
    {
        Name = ClientName,
        RecvArg = true
    }), SocketFlags.None);

    await sendArgs.AcceptSocket.ConnectAsync(host, port);
    await sendArgs.AcceptSocket.SendAsync(Info<SockInfo>.Pack(new SockInfo()
    {
        Name = ClientName,
        RecvArg = false
    }), SocketFlags.None);

    SetBuffer(recvArgs, bufferSize);
    recvArgs.Completed += Receive;
    recvArgs.AcceptSocket.ReceiveAsync(recvArgs);
}

и в AcceptAsync обратном вызове сервера. Ясделав это:

void Accepted(object sender, SocketAsyncEventArgs e)
{
    e.Completed -= Accepted;
    e.Completed += CheckInfo;
    SetBuffer(e, infoBufferLength);
    e.AcceptSocket.ReceiveAsync(e);
    Accept();
}

серверная CheckInfo функция проверяет SockInfo, отправленный клиентом. Если RecvArg = true сервер назначает SocketAsyncEventArgs в качестве SendArgs, в противном случае RecvArgs, например, так:

void CheckInfo(object sender, SocketAsyncEventArgs e)
{
    lock (Clients)
    {
        e.Completed -= CheckInfo;
        var info = Info<SockInfo>.Unpack(e.Buffer);
        Client c = Clients.FirstOrDefault(x => x.Name == info.Name);
        if (c == null)
        {
            c = new Client();
            c.Name = info.Name;
            Clients.Add(c);
        }

        if (info.RecvArg)
        {
            c.SendArgs = e;
            c.SendArgs.SetBuffer(null);
            c.SendArgs.Completed += Sent;
        }
        else
        {
            c.RecvArgs = e;
            SetBuffer(c.RecvArgs, bufferLength);
            c.RecvArgs.Completed += Received;
            c.RecvArgs.AcceptSocket.ReceiveAsync(c.RecvArgs);
        }
    }
}

Clients является ObservableCollection из Client:

class Client
{
    public string Name { get; set; }
    public SocketAsyncEventArgs SendArgs { get; set; }
    public SocketAsyncEventArgs RecvArgs { get; set; }
}

и SockInfo - это структура со следующими свойствами:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SockInfo
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string Name;
    public bool RecvArg;       
}

Если я установлю 3 точки останова: первую и вторую в Connect2Server функции клиентского приложения, 2-ю и 4-ю await и третью в сервереCheckInfo и запустите оба приложения в режиме отладки, CheckInfo дважды ударит, и обе функции c.SendArgs и c.RecvArgs в CheckInfo получат SocketAsyncEventArgs. Однако если я удаляю первые две точки останова, CheckInfo поражается только один раз почти всегда, а c.RecvArgs остается null!

Почему и как я могу убедиться, что c.RecvArgs всегда получает свое SocketAsyncEventArgs?
Другой вопрос: нужно ли иметь lock на Clients в CheckInfo?


EDIT

Twoдля этого вопроса могут иметь отношение другие функции (1) StartServer, где сервер начинает прослушивание и передает управление (2) Accept, что создает новый SocketAsyncEventArgs при каждом вызове. Вот эти два:

void StartServer(object obj)
{
    server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    server.Bind(new IPEndPoint(0, port));
    server.Listen(0);
    Accept();
}

void Accept()
{
    var arg = new SocketAsyncEventArgs();
    arg.Completed += Accepted;
    try { server.AcceptAsync(arg); }
    catch (Exception)
    {
        arg.Dispose();
        StopServer(null);
    }
}

1 Ответ

0 голосов
/ 03 ноября 2019

Я не знаю, почему я должен вызывать эти callbacks вручную, и я не уверен, является ли это решением. Я хотел бы внести некоторые изменения в мои Accept и Accepted функции, вот те, с изменениями:

void Accept()
{
    var arg = new SocketAsyncEventArgs();
    arg.Completed += Accepted;
    try 
    { 
        bool done = server.AcceptAsync(arg);
        if (!done) Accepted(null, arg);
    }
    catch (Exception)
    {
        arg.Dispose();
        StopServer(null);
    }
}

void Accepted(object sender, SocketAsyncEventArgs e)
{
    e.Completed -= Accepted;
    e.Completed += CheckInfo;
    SetBuffer(e, infoBufferLength);
    bool done = e.AcceptSocket.ReceiveAsync(e);
    if (!done) CheckInfo(null, e);
    Accept();
}

, и пока что он отлично работает с этими done и if (!done) ...!

...