Могу ли я дать совет по базовому программированию асинхронных сокетов на C#?

Я разрабатываю (или, во всяком случае, пытаюсь) программу, которая использует асинхронный сокет, предположительно, для передачи строк на сервер и клиент в любое время.

Эта программа требует, чтобы к серверу было подключено не более одного клиента. Я попробовал программирование сокетов, но обнаружил, что оно блокирует программу до тех пор, пока одна из них не получит что-то.

Поскольку у меня есть только базовое понимание программирования асинхронных сокетов, я выбрал самый простой, который смог найти, или, по крайней мере, самый простой, который смог понять.

Вот мой код для сервера:

    public Socket g_server_conn;
    public byte[] g_bmsg;
    public bool check = false;  

    private void net_As_Accept(IAsyncResult iar)
    {
        Socket server_conn = (Socket)iar.AsyncState;
        g_server_conn = server_conn.EndAccept(iar);
        g_bmsg = new byte[1024];
        check = true;
        g_server_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_server_conn);
    }

    private void net_As_Send(IAsyncResult iar)
    {
        Socket server_conn = (Socket)iar.AsyncState;
        server_conn.EndSend(iar);
    }

    private void net_As_Receive(IAsyncResult iar)
    {
        try
        {
            Socket server_conn = (Socket)iar.AsyncState;
            server_conn.EndReceive(iar);
            if (g_bmsg.Length != 0)
            {
                net_Data_Receive(Encoding.ASCII.GetString(g_bmsg, 0, g_bmsg.Length));
                check = false;
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "GG");
        }
    }

    public void net_Data_Send(string msg2snd) // Function for sending through socket
    {
        MessageBox.Show(msg2snd);
        byte[] byData = System.Text.Encoding.ASCII.GetBytes(msg2snd);
        g_server_conn.BeginSend(byData, 0, byData.Length, SocketFlags.None, new AsyncCallback(net_As_Send), g_server_conn);
        g_server_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_server_conn);
    }

    private void net_Data_Receive(string txt)
    {
        if (lblBuffer.InvokeRequired)
            lblBuffer.Invoke(new MethodInvoker(delegate { net_Data_Receive(txt); }));
        else
            lblBuffer.Text = txt;

        if (txt.StartsWith("&"))
        {
            // Do something              
        }
    }

И вот мой код для клиента:

    private void net_As_Connect(IAsyncResult iar)
    {
        try
        {
                Socket client_conn = (Socket)iar.AsyncState;
                client_conn.EndConnect(iar);
                g_bmsg = new byte[1024];
                check = true;
                string toSendData = "&" + net_Name;
                net_Data_Send(toSendData);
                g_client_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_client_conn);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "GG");
        }
    }

    private void net_As_Send(IAsyncResult iar)
    {
        Socket client_conn = (Socket)iar.AsyncState;
        client_conn.EndSend(iar);
    }

    private void net_As_Receive(IAsyncResult iar)
    {
        if (g_bmsg.Length != 0)
        {
            net_Data_Receive(Encoding.ASCII.GetString(g_bmsg, 0, g_bmsg.Length));
            check = false;
        }
    }

    public void net_Data_Send(string msg2snd)
    {
        byte[] byData = System.Text.Encoding.ASCII.GetBytes(msg2snd);

        g_client_conn.BeginSend(byData, 0, byData.Length, SocketFlags.None, new AsyncCallback(net_As_Send), g_client_conn);
        g_client_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_client_conn);
    }

    private void net_Data_Receive(string txt)
    {
        if (lblBuffer.InvokeRequired)
            lblBuffer.Invoke(new MethodInvoker(delegate { net_Data_Receive(txt); }));
        else
            lblBuffer.Text = txt;

        if (txt.StartsWith("&"))
        {
            // Do Something              
        }
        else if (txt.StartsWith("$"))
        {
            // Do something Else
        }

    }

Теперь клиент может нормально подключиться к серверу. Клиент может даже отправить строку, содержащую имя пользователя, на Сервер, которая затем будет отображаться на Сервере. Затем Сервер отправляет имя своего пользователя Клиенту, которое клиент получает и отображает. Все, что отправляется, сохраняется в метке (lblBuffer).

Но потом, скажем, у меня есть следующий код:

    private void btnSendData_Click(object sender, EventArgs e)
    {
        string posMov = "Stuff to send";
        net_Data_Send(posMov);
    }

Клиент ничего не получает. Помещение окна сообщения в функцию net_Data_Send(msg2snd) показывает, что сервер действительно отправляет сообщение. На самом деле, добавление окна сообщения в эту функцию заставляет ее работать (клиент ее получает) по причинам, которые я не знаю. Поскольку я не пытался отправить сообщение от клиента на сервер (кроме имени при подключении клиента), я предполагаю, что у клиента будет такая же проблема с отправкой на сервер.

Кроме того, когда он отправляет второе сообщение (поместив окно сообщения в функцию net_Data_Send), перезаписываются только части метки (lblBuffer). Итак, если меня зовут «Анон Э. Муз», и сервер отправляет это, когда клиент подключается, и я пытаюсь отправить, скажем, «0,0» (через нажатие кнопки), метка на клиенте будет читать «0,0 п Э. Мус».

Что я сделал не так? Могу ли я помочь в этом, пожалуйста?

Возможно, у меня проблема с net_Data_Receive и net_Data_Send?


person zack_falcon    schedule 03.11.2011    source источник
comment
Проблема с перезаписью заключается в том, что вы конвертируете содержимое буфера из байтов ascii в строку, g_bmsg.Length вернет длину массива байтов, а не текущую длину данных, которые были помещены в него с помощью принять звонок. Вам нужно сохранить, сколько фактических байтов прочитано, и использовать это   -  person Matt    schedule 03.11.2011


Ответы (2)


Я думаю, вам нужно снова вызвать BeginReceive на вашем клиенте, похоже, вы вызываете его только один раз, поэтому после того, как он получил имя сервера, он больше не прослушивает данные с сервера.

private void net_As_Receive(IAsyncResult iar)
{
    var bytesRead = g_client_conn.EndReceive(iar);


    if (bytesRead != 0)
    {
        net_Data_Receive(Encoding.ASCII.GetString(g_bmsg, 0, bytesRead));
        check = false;
    }

    g_client_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_client_conn);
}

также, как я упоминал в своем комментарии, используйте значение bytesRead, чтобы определить, какой объем буфера вам нужно использовать.

Вам нужно будет решить, являются ли данные, которые вы получили от сокета, полным объемом, или вам нужно прочитать больше данных, чтобы составить текущее сообщение с другой стороны.

person Matt    schedule 03.11.2011
comment
Спасибо. Теперь он может получать сообщения. Хотя я все еще работаю над перезаписью лейбла. Кстати, в моем С# нет "var". Я думаю, что это доступно только в С# 3.0. Может ли изменение этого на «int» что-нибудь испортить? - person zack_falcon; 03.11.2011
comment
нет, используйте int :) извините, так привыкли использовать var, теперь это вторая природа! - person Matt; 03.11.2011

BeginReceive не просто вызывает свой обратный вызов всякий раз, когда приходит новый пакет (строка в вашем случае). Фактически. BeginReceive или любой метод необработанных сокетов работает в потоковом режиме, а не на основе пакетов. См. http://msdn.microsoft.com/en-us/library/bew39x2a.aspx для примера.

Что вам нужно сделать, так это в вашем методе обратного вызова 'net_As_Receive' (название ужасно imo), вам нужно сначала сделать вызов socket.EndRecieve(IAsyncResult), который, в свою очередь, возвращает общее количество байтов, доступных в настоящее время. После этого вы должны принять решение, получать больше данных или нет.

Например:

private StringBuilder packetBuilder;
{ 
    if (packetBuilder == null)
        packetBuilder = new StringBuilder();

    // finalyze the receive
    int length = g_server_conn.EndReceive(iar);

    if (length != 0) 
    { 
        // get the total bytes received. Note that the length property is of that of the number of bytes received rather than that of the buffer
        packetBuilder.Append(Encoding.ASCII.GetString(g_bmsg, 0, length));

        net_Data_Receive(packetBuilder.ToString()); 
        check = false; 
    } 

    // receive the next part
    g_server_conn.BeginReceive(g_bmsg, 0, g_bmsg.Length, SocketFlags.None, new AsyncCallback(net_As_Receive), g_server_conn);  
} 

Обратите внимание, что этот пример не заботится о пакетах. Это будет работать, если вам повезет, но есть хорошее изменение: будет показана часть строки или будут объединены 2 разные строки. Хорошая реализация будет искать конец строки и показывать только эту часть, буферизуя остальную часть до тех пор, пока не будет найден новый конец строки. Вы также можете использовать StreamReader, чтобы сделать свою жизнь намного проще.

person Polity    schedule 03.11.2011