C # Почему "Flush" не заставляет байты пропускать сетевой поток? - PullRequest
9 голосов
/ 15 февраля 2011

У меня есть проект, в котором я пытаюсь отправить сериализованный объект на сервер, а затем дождаться возвращения сообщения «ОК» или «ОШИБКА».

Кажется, у меня похожеепроблема с плакатом: TcpClient проблема отправки / закрытия

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

private void button4_Click(object sender, EventArgs e)
{
    RequestPacket req = new RequestPacket();

    /// ... Fill out request packet ...

    /// Connect to the SERVER to send the message...
    TcpClient Client = new TcpClient("localhost", 10287);
    using (NetworkStream ns = Client.GetStream())
    {
        XmlSerializer xml = new XmlSerializer(typeof(RequestPacket));
        xml.Serialize(ns, req);

        /// NOTE: This doesn't seem to do anything.... 
        ///       The server doesn't get the object I just serialized.
        ///       However, if I use ns.Close() it does... 
        ///          but then I can't get the response.
        ns.Flush();

        // Get the response. It should be "OK".
        ResponsePacket resp;

        XmlSerializer xml2 = new XmlSerializer(typeof(ResponsePacket));
        resp = (ResponsePacket)xml2.Deserialize(ns);


        /// ... EVALUATE RESPONSE ...
    }

    Client.Close()
}

ОБНОВЛЕНИЕ: В ответ на один комментарий, я не думаю, что клиент может быть виноват.Это просто ожидание объекта, и объект никогда не появится, пока я не закрою сокет ... однако, если я ошибаюсь, я БЛАГОСЛОВЕННО поеду ворону публично.=) Вот клиент:

    static void Main(string[] args)
    {
        // Read the port from the command line, use 10287 for default
        CMD cmd = new CMD(args);
        int port = 10287;

        if (cmd.ContainsKey("p")) port = Convert.ToInt32(cmd["p"]);

        TcpListener l = new TcpListener(port);
        l.Start();

        while (true)
        {
            // Wait for a socket connection.
            TcpClient c = l.AcceptTcpClient();

            Thread T = new Thread(ProcessSocket);

            T.Start(c);
        }
    }


    static void ProcessSocket(object c)
    {
        TcpClient C = (TcpClient)c;

        try
        {
            RequestPacket rp;
            //// Handle the request here.
            using (NetworkStream ns = C.GetStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(RequestPacket));
                rp = (RequestPacket)xml.Deserialize(ns);
            }

            ProcessPacket(rp);
        }
        catch
        {
            // not much to do except ignore it and go on.
        }
    }

Да ... это так просто.

Ответы [ 4 ]

4 голосов
/ 15 февраля 2011

О-о, ты можешь винить Алгоритм Нейгла . Это не имеет ничего общего с C #, хотя это поведение по умолчанию для стека TCP / IP. Включите параметр сокета NoDelay , используя метод SetSocketOption . Но будьте осторожны, отключение алгоритма Nagle снизит пропускную способность.

Я также не уверен насчет того потока, который вы используете поверх сокета, так как я вообще не являюсь разработчиком C #, но попробуйте отбросить его экземпляр, чтобы он точно писал: -)

3 голосов
/ 17 февраля 2011

Короткая версия, по-видимому, при использовании XmlSerializer (или любого другого большого двоичного объекта) для выталкивания данных в NetworkStream просто удерживает строку открытой в течение неопределенного времени в ожидании записи дополнительной информации.Он сбрасывает соединение только после того, как вы его закроете.Это создает ситуацию, когда этот метод отлично подходит для отправки, но не получения.Или наоборот.Это становится односторонней связью и бесполезной для продолжения обратной и обратной связи по одному и тому же соединению.

Отчасти это дерьмо, что мне приходилось обходить что-то, что выглядело так элегантно на поверхности, но бросалоВозвращаясь к своим старым дням C, я сначала отправил пакет «количество байтов», а затем сам пакет.Это позволяет мне ПРОЧИТАТЬ на другом конце точное число байтов, поэтому я никогда не попаду в блокирующий шаблон.

Чтобы упростить свою жизнь, я создал класс, который содержит некоторые статические методы для отправки и получения.Этот класс может отправлять ЛЮБОЙ XML-сериализуемый класс по сети, поэтому он делает то, что мне нужно.

Если у кого-нибудь есть более элегантное решение, я буду открыт для его прослушивания.

public class PacketTransit
{
    public static void SendPacket(TcpClient C, object Packet)
    {
        MemoryStream ms = new MemoryStream();
        XmlSerializer xml = new XmlSerializer(Packet.GetType());
        xml.Serialize(ms, Packet);
        ms.Position = 0;
        byte[] b = ms.GetBuffer();
        ms.Dispose();

        byte [] sizePacket = BitConverter.GetBytes(b.Length);
        // Send the 4-byte size packet first.
        C.Client.Send(sizePacket, sizePacket.Length, SocketFlags.None);
        C.Client.Send(b, b.Length, SocketFlags.None);
    }

    /// The string is the XML file that needs to be converted.
    public static string ReceivePacket(TcpClient C, Type PacketType)
    {
        byte [] FirstTen = new byte[1024];
        int size = 0;
        byte[] sizePacket = BitConverter.GetBytes(size);

        // Get the size packet
        int sp = C.Client.Receive(sizePacket, sizePacket.Length, SocketFlags.None);
        if (sp <= 0) return "";

        size = BitConverter.ToInt32(sizePacket, 0);

        // read until "size" is met
        StringBuilder sb = new StringBuilder();
        while (size > 0)
        {
            byte[] b = new byte[1024];
            int x = size;
            if (x > 1024) x = 1024;
            int r = C.Client.Receive(b, x, SocketFlags.None);
            size -= r;
            sb.Append(UTF8Encoding.UTF8.GetString(b));
        }

        return sb.ToString();
    }

    /// The XML data that needs to be converted back to the appropriate type.
    public static object Decode(string PacketData, Type PacketType)
    {
        MemoryStream ms = new MemoryStream(UTF8Encoding.UTF8.GetBytes(PacketData));
        XmlSerializer xml = new XmlSerializer(PacketType);
        object obj = xml.Deserialize(ms);
        ms.Dispose();

        return obj;
    }

    public static RequestPacket GetRequestPacket(TcpClient C)
    {
        string str = ReceivePacket(C, typeof(RequestPacket));

        if (str == "") return new RequestPacket();

        RequestPacket req = (RequestPacket) Decode(str, typeof(RequestPacket));

        return req;
    }

    public static ResponsePacket GetResponsePacket(TcpClient C)
    {
        string str = ReceivePacket(C, typeof(ResponsePacket));

        if (str == "") return new ResponsePacket();

        ResponsePacket res = (ResponsePacket)Decode(str, typeof(ResponsePacket));

        return res;
    }
}

Чтобы использовать этот класс, мне просто нужно вызвать PacketTransit.SendPacket(myTcpClient, SomePacket), чтобы отправить любой данный XML-Serializable объект.Затем я могу использовать PacketTransit.GetResponsePacket или PacketTransit.GetRequestPacket, чтобы получить его на другом конце.

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

0 голосов
/ 14 марта 2013

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

0 голосов
/ 15 февраля 2011

вы должны использовать StreamWriter / Reader, связанный с вашим сетевым потоком. .Flush ничего не делает с NetworkStream, смотрите здесь:

http://www.c -sharpcorner.com / UploadFile / dottys / SocketProgDTRP11222005023030AM /SocketProgDTRP.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...