Быстрая пересылка через Socket
От: impure_soul Россия  
Дата: 10.10.07 07:26
Оценка:
Доброго времени суток.
Вопрос в следующем. Есть сервер, основная задача которого — пересылка данных между двумя клиентами (т.е. он просто принимает с одного сокета и отправляет на другой).
Данные от клиента идут "пакетами" по 65536 байт или меньше (как получится). Если интервал между отправкой двух пакетов от одного клиента больше 100 мс, то всё работает нормально. Если задержку не ставить, то получающий клиент не может десериализовать сообщение т.к. вместо отправленых, например, 33500 байт получает 65536 или вообще что-то другое (например 1024 байта). Не могу понять где и почему портится пакет. После проверки оказалось что при быстрой отправке уже сервер получает пакет непрвильного размера, хотя на клиенте перед выполнением метода Send размер был верным. Возможно уже не вижу очевидных вещей. Подскажите в чём проблема?

Вот частичный код сервера:

public void Start()
{
   SetupServerSocket(); // тут настройка сокета (IP, Port, ...)
   _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
}

// класс, используемый для возврата через IAsyncResult
private class ConnectionInfo
        {
            private readonly object on_lock = new object();
            public Int32 ClientID;
            private Socket _Socket;
            public Socket Socket
            {
                get { lock (on_lock) {return _Socket; } }
                set { lock (on_lock) { _Socket = value; } }
            }
            private byte[] _Buffer;
            public byte[] Buffer
            {
                get { lock (on_lock) { return _Buffer; } }
                set { lock (on_lock) { _Buffer = value; } }
            }
        }

private void AcceptCallback(IAsyncResult result)
        {
            
            ConnectionInfo connection = new ConnectionInfo();
            try
            {
                // Завершение операции Accept
                Socket s = (Socket)result.AsyncState;
                connection.Socket = s.EndAccept(result);
                connection.Buffer = new byte[65536];
                lock (_connections) _connections.Add(connection);
                connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length,
                            SocketFlags.None, new AsyncCallback(ReceiveCallback), connection);
              
                _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), result.AsyncState);
            }
            catch (SocketException exc)
            {
                CloseConnection(false, connection);
                Log("Описание ошибки: "+exc.SocketErrorCode);
            }
        }

        private void ReceiveCallback(IAsyncResult result)
        {
            ConnectionInfo connection = (ConnectionInfo)result.AsyncState;
            try
            {
                int bytesRead = connection.Socket.EndReceive(result); // << при "быстрой отправке" 
                                                                      //тут  bytesRead не совпадает
                                                                      //отправленным числом байт
                byte[] _tmp_buf = connection.Buffer;
                connection.Buffer = new byte[65536];

                connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length,
                              SocketFlags.None,new AsyncCallback(ReceiveCallback), connection);

                if (0 != bytesRead)
                {
                    MyMessageType MyMessage = null;
                    BinaryFormatter serializer = new BinaryFormatter();
                    #region Serialization
                    try
                    {
                        MemoryStream stream = new MemoryStream(_tmp_buf);
                            MyMessage = (MyMessageType)serializer.Deserialize(stream);
                            stream.Close();
                    }
                    catch
                    {
                        
                        return;
                    }
                    #endregion
                    #region Send
                    foreach (ConnectionInfo conn in _connections) // Перебераем всех  
                                                                  //подключённых клиентов
                    {
                    if (conn.ClientID == MyMessage.UsersTo.ID)
                    {
                        try
                        {
                         conn.Socket.Send(_tmp_buf, bytesRead, SocketFlags.None);
                         break;
                         }
                         catch { isOnline = false; }
                    }
                
                }
            }
            catch (SocketException exc)
            {
                CloseConnection(true,connection);
                Log("Описание ошибки: " + exc.SocketErrorCode);
            } 
        }



Частичный код клиента:

        void OnBeginReceive(IAsyncResult result)
        {
            ClientConnectionInfo conn = (ClientConnectionInfo)result.AsyncState;
            try
            {
                int bytesRead = client.EndReceive(result);
                byte[] _tmp_buf = new byte[bytesRead];
                Array.Copy(conn.Buffer, _tmp_buf, bytesRead);
                conn.Buffer = new byte[MAX_PACKET_LEN];
                client.BeginReceive(conn.Buffer, 0, MAX_PACKET_LEN, SocketFlags.None, 
                                            new AsyncCallback(OnBeginReceive), conn);

                if (0 != bytesRead)
                {
                    MyMessageType MyMessage = null;
                    BinaryFormatter serializer = new BinaryFormatter();
                    try
                    {
                        MemoryStream stream = new MemoryStream(_tmp_buf);
                            MyMessage = (MyMessageType)serializer.Deserialize(stream);
                            stream.Close();
                    }
                    catch
                    {
                        // Вот сюда вылетает получатель, при быстрой отправке.
                        MessageBox.Show("Не удалось десериализовать пришедшее сообщение.");
                        return;
                    }

                    GenerateMessageEvent(SRMessageDesserializeObj);
                 }
            }
            catch (Exception ex){ MessageBox.Show("Критическая ошибка "+'\n'+ex.Message);  }
        }

//Метод отправки данных с клиента

        public void DoWork(string path, int id, string name)
        {
            int MAX_DATA_LEN = 30000;
            string _path = path;
            FileStream stream = new FileStream(_path, System.IO.FileMode.Open,
                        System.IO.FileAccess.ReadWrite);
            BinaryReader r = new BinaryReader(stream);
            int segmentCount = (int)stream.Length / MAX_DATA_LEN + 1;
            int sourceIndex = 0;

            for (int i = 0; i < segmentCount; i++)
            {
                MyMessageType _message = new MyMessageType();
              
                int bytesToCopy = (int)stream.Length - sourceIndex;
                if (bytesToCopy > MAX_DATA_LEN) bytesToCopy = MAX_DATA_LEN;

                _message.DataContainer = r.ReadBytes(bytesToCopy);
                sourceIndex += bytesToCopy;

                MemoryStream mstream = new MemoryStream();
                BinaryFormatter serializer = new BinaryFormatter();
                serializer.Serialize(mstream, _message);
                byte[] buf = mstream.ToArray();
                try
                {
                    client.Send(buf, 0, buf.Length, SocketFlags.None);
                }
                catch { }
                Thread.Sleep(100); // << Вот та самая задержка. Если её убрать,
                                  // то возникает описанная проблема
            }
            stream.Close();
        }


Буду очень признтелен за помошь. Задачу очень нузно решить
Re: Быстрая пересылка через Socket
От: stump http://stump-workshop.blogspot.com/
Дата: 10.10.07 10:12
Оценка: 1 (1)
Здравствуйте, impure_soul, Вы писали:

_>Доброго времени суток.

_>Вопрос в следующем. Есть сервер, основная задача которого — пересылка данных между двумя клиентами (т.е. он просто принимает с одного сокета и отправляет на другой).
_>Данные от клиента идут "пакетами" по 65536 байт или меньше (как получится). Если интервал между отправкой двух пакетов от одного клиента больше 100 мс, то всё работает нормально. Если задержку не ставить, то получающий клиент не может десериализовать сообщение т.к. вместо отправленых, например, 33500 байт получает 65536 или вообще что-то другое (например 1024 байта). Не могу понять где и почему портится пакет. После проверки оказалось что при быстрой отправке уже сервер получает пакет непрвильного размера, хотя на клиенте перед выполнением метода Send размер был верным. Возможно уже не вижу очевидных вещей. Подскажите в чём проблема?

_>Вот частичный код сервера:


skipped[]

_>Буду очень признтелен за помошь. Задачу очень нузно решить


Данные при передаче буфферизуются. Поэтому Socket.Receive (или EndReceive) прчитает столько байт, сколько у него на данный момент есть в буфере. Данные, переданные с клиента одним Socket.Send могут приниматься на другой стороне несколькими Socket.Receive. Об этом надо помнить.
Если ты передаешь в один сокет не сплошной поток байт, а логически отдельные части, тебе следует предпринять специальные меры, чтобы правильно разделить принимаемый поток на другой стороне.
Например сначала можно посылать размер передаваемых данных (Int32 — 4 байта), а потом сами данные. На другой стороне ты сначала читаешь 4 байта, преобразуешь их в Int32 и видишь размер идущих следом данных. Выделяешь соотвтетствующий буфер и повторяешь Receive пока на прочитаешь их все.
Понедельник начинается в субботу
Re[2]: Быстрая пересылка через Socket
От: impure_soul Россия  
Дата: 10.10.07 13:46
Оценка:
Здравствуйте, stump, Вы писали:


S>Данные при передаче буфферизуются. Поэтому Socket.Receive (или EndReceive) прчитает столько байт, сколько у него на данный момент есть в буфере. Данные, переданные с клиента одним Socket.Send могут приниматься на другой стороне несколькими Socket.Receive. Об этом надо помнить.


Каким образом данные переданные одним Send может принять сразу несколько Receive?

S>Если ты передаешь в один сокет не сплошной поток байт, а логически отдельные части, тебе следует предпринять специальные меры, чтобы правильно разделить принимаемый поток на другой стороне.
S>Например сначала можно посылать размер передаваемых данных (Int32 — 4 байта), а потом сами данные. На другой стороне ты сначала читаешь 4 байта, преобразуешь их в Int32 и видишь размер идущих следом данных. Выделяешь соотвтетствующий буфер и повторяешь Receive пока на прочитаешь их все.


Тоесть даже если я пересылаю данные "пакетами" то мне всеравно нужно сначало посылать размер пакета а потом всё остальное?
Я не привёл в коде описание пакета и полный код его обработки т.к. много текста, но сам пакет (класс MyMessageType) внутри себя содержит заголовок и часть данных. Потом я его сериализую и отправляю единым потоком. Весь MyMessageType гарантированно помещается в 65К.

    public class MyMessageType
    {
        MyMessageTypeHeader header;

        MyMessageTypeData data;

        public MyMessageTypeHeader Header
        {
            get { return header; }
            set { header = value; }
        }

        public MyMessageTypeData Data
        {
            get { return data; }
            set { data = value; }
        }
       ...
    }

    public class MyMessageTypeHeader
    {
        byte[] UID;

        User userFrom;

        User usersTo;
        
        Int64 part;
        Int64 partsCount;

        MessageType messageType;
        
        DateTime timeStamp = DateTime.Now;

        Dictionary<string, string> parameters;
       ...
    }

    public class MyMessageTypeData
    {
        Dictionary<string, string> parameters;

        public Dictionary<string, string> Parameters
        {
            get { return parameters; }
            set { parameters = value; }
        }

        byte[] dataContainer;

        public byte[] DataContainer
        {
            get { return dataContainer; }
            set { dataContainer = value; }
        }
    }


Я предполагал что сформированный мной пакет и будет для сервера некоторым потоком, а о существовании всех прочих частей (пакетов) он вообще знать не должен. Тоесть он должен принять за один раз не более 65536 байт, bytesRead = client.EndReceive(result) — вернёт мне количество реально полученных байт, далее сервер их перенаправит получателю. Собирать части данных воедино будет уже получатель в соответствии с некоторым алгоритмом (если будет необходимость). А тем временем сервер, перенаправив пакет, должен быть готов принять следующий (возможно даже не имеющий отношения к предыдущему).
Может для этого надо сокету на сервере как-то сказать что буфер прошлый нужно очистить?
Или я чего-то не понял?
Re[3]: Быстрая пересылка через Socket
От: stump http://stump-workshop.blogspot.com/
Дата: 10.10.07 14:22
Оценка: 3 (1)
Здравствуйте, impure_soul, Вы писали:

_>Здравствуйте, stump, Вы писали:



_>

S>>Данные при передаче буфферизуются. Поэтому Socket.Receive (или EndReceive) прчитает столько байт, сколько у него на данный момент есть в буфере. Данные, переданные с клиента одним Socket.Send могут приниматься на другой стороне несколькими Socket.Receive. Об этом надо помнить.


_>Каким образом данные переданные одним Send может принять сразу несколько Receive?

Очень просто. Ты передаешь в Send буфер размером 64К. Сокет начинает передавать эти данные, а другой сокет начинает принимать. Принимающий сокет складывает все принятое в буфер. В это время ты вызываешь Receive и сокет отдает тебе все что есть у него в буфере (допустим 12К). Он не ждет пока придут все 64К потому, что все происходит асинхронно. Он даже не знает что ему должны передать 64К.

_>

S>>Если ты передаешь в один сокет не сплошной поток байт, а логически отдельные части, тебе следует предпринять специальные меры, чтобы правильно разделить принимаемый поток на другой стороне.
S>>Например сначала можно посылать размер передаваемых данных (Int32 — 4 байта), а потом сами данные. На другой стороне ты сначала читаешь 4 байта, преобразуешь их в Int32 и видишь размер идущих следом данных. Выделяешь соотвтетствующий буфер и повторяешь Receive пока на прочитаешь их все.


_>Тоесть даже если я пересылаю данные "пакетами" то мне всеравно нужно сначало посылать размер пакета а потом всё остальное?

_>Я не привёл в коде описание пакета и полный код его обработки т.к. много текста, но сам пакет (класс MyMessageType) внутри себя содержит заголовок и часть данных. Потом я его сериализую и отправляю единым потоком. Весь MyMessageType гарантированно помещается в 65К.

Если бы ты для передачи каждого своего сообщения открывал отдельное соединение (сокет) то это не понадобилось бы. Принимал бы все пока не закроется соединение.



_>Я предполагал что сформированный мной пакет и будет для сервера некоторым потоком, а о существовании всех прочих частей (пакетов) он вообще знать не должен. Тоесть он должен принять за один раз не более 65536 байт, bytesRead = client.EndReceive(result) — вернёт мне количество реально полученных байт, далее сервер их перенаправит получателю. Собирать части данных воедино будет уже получатель в соответствии с некоторым алгоритмом (если будет необходимость). А тем временем сервер, перенаправив пакет, должен быть готов принять следующий (возможно даже не имеющий отношения к предыдущему).

_>Может для этого надо сокету на сервере как-то сказать что буфер прошлый нужно очистить?
_>Или я чего-то не понял?

Как я уже сказал выше, сокет не знает где начинаются, а где заканчиваются передаваемые тобой сообщения. Не следует думать что данные переданные тобой в буфере в метод Send являются для сокета како либо логической посылкой. Он просто сливает их дальше вниз по стеку OSI.
Понедельник начинается в субботу
Re[3]: Быстрая пересылка через Socket
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.10.07 14:59
Оценка:
Здравствуйте, impure_soul, Вы писали:

_>Я предполагал что сформированный мной пакет и будет для сервера некоторым потоком, а о существовании всех прочих частей (пакетов) он вообще знать не должен. Тоесть он должен принять за один раз не более 65536 байт, bytesRead = client.EndReceive(result) — вернёт мне количество реально полученных байт, далее сервер их перенаправит получателю. Собирать части данных воедино будет уже получатель в соответствии с некоторым алгоритмом (если будет необходимость). А тем временем сервер, перенаправив пакет, должен быть готов принять следующий (возможно даже не имеющий отношения к предыдущему).

_>Может для этого надо сокету на сервере как-то сказать что буфер прошлый нужно очистить?
_>Или я чего-то не понял?

В TCP канале вообще нету "пакетов", ВСЕ данные передаются оддним потоком. То есть ты передаешь 4 раза по 5 килобайт, а прочитано оно может быть один раз — 20 килобайт или 20 раз по килобайту. Поэтому именно програмист должен заботиться о целостности "пакетов". Это можно сделать двумя способами: передавать размер пакета или использовать символ-ограничитель



А вообще стоит почитать Йона Снейдера "Эффективное программирование TCP/IP"
Re[4]: Быстрая пересылка через Socket
От: impure_soul Россия  
Дата: 10.10.07 15:01
Оценка:
Здравствуйте, stump, Вы писали:

S>Очень просто. Ты передаешь в Send буфер размером 64К. Сокет начинает передавать эти данные, а другой сокет начинает принимать. Принимающий сокет складывает все принятое в буфер. В это время ты вызываешь Receive и сокет отдает тебе все что есть у него в буфере (допустим 12К). Он не ждет пока придут все 64К потому, что все происходит асинхронно. Он даже не знает что ему должны передать 64К.


Отсюда следует, что я впринципе не могу корректно передавать поток данных асинхронно!
Иначе как я смогу получить все отправленные данные в асинхронной модели?
Получается что я не должен вызвыать BeginReceive до тех пор пока не буду уверен что клиент закончил отправку данных?
Тогда в чём смысл асинхронной модели если все методы BeginXXX EndXXX только позволят читать в другом потоке и всё.
В таком случае как вообще пользоваться методами BeginReceive и EndReceive если мне нужно получить все данные отправленные пользователем. Насколько я понял написанное у Рихтера на каждый метод BeginReceive обязательно должен быть вызван EndReceive, который вернёт сами прочитанные данные и их количество. Тоесть само получение данных возможно только после вызова EndReceive. Тогда в какой момент мне нужно прочитать первые четыре байта размера и начинать читать до конца??? И почему тогда после устаноки задержки

 for (int i = 0; i < segmentCount; i++)
            {
                MyMessageType _message = new MyMessageType();
              
                int bytesToCopy = (int)stream.Length - sourceIndex;
                if (bytesToCopy > MAX_DATA_LEN) bytesToCopy = MAX_DATA_LEN;

                _message.DataContainer = r.ReadBytes(bytesToCopy);
                sourceIndex += bytesToCopy;

                MemoryStream mstream = new MemoryStream();
                BinaryFormatter serializer = new BinaryFormatter();
                serializer.Serialize(mstream, _message);
                byte[] buf = mstream.ToArray();
                try
                {
                    client.Send(buf, 0, buf.Length, SocketFlags.None);
                }
                catch { }
                Thread.Sleep(100); // << Вот та самая задержка. Если её убрать,
                                  // то возникает описанная проблема
            }


все данные приходят нормально?

Какая именно операция мне портит данные в буфере, Send на клиенте — перезаписывая ещё непрочитанные данные или Receive на сервере — читая то что ещё не пришло до конца?? Или я просто читаю на сервере конец прошлого и начало нового пакета как единое целое???

Подскажите пожалйста.
Re[5]: Быстрая пересылка через Socket
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.10.07 15:26
Оценка: 3 (1)
Здравствуйте, impure_soul, Вы писали:

Или я просто читаю на сервере конец прошлого и начало нового пакета как единое целое???
Именно
Re[6]: Быстрая пересылка через Socket
От: impure_soul Россия  
Дата: 10.10.07 19:48
Оценка:
Спасибо всем. Со всем разобрался.
Re[5]: Быстрая пересылка через Socket
От: Аноним  
Дата: 10.10.07 21:23
Оценка:
Здрасте. Есть несколько замечаний про ассинхроную работу TCP/IP. Дето все в том что не зависимо от того какими блоками вы засылаете в сокет байты, это еще не является настоящими пакетами которые будут пересылаться через сеть, настоящие пакеты будут ограниченны физической средой пересылки. В чаще всего это EtherNet, да да эта та самая карточка которая стоит у вас в компьютере. Так вот размер EtherNet-пакета в 90% случаев равен 1500 байт, и это число является характеристикой физической сети обозначаемой абревиатурой MTU. Согласно спецификации (где-то, когда-то я читал TCP/IP будет перепаковывать более длинные пакеты в более короткие. Когда это может произойти ? Например в вашем случае, когда вы засписываете 65к, а оно поедет по 1500 байт, или при гейтировании одной сетики в другую — из локального EtherNet-а в какой-нибудь PPP. Причем согласно той же спецификации TCP/IP не позаботится собрать короткие пакеты в более длинные, даже когда у него появится возможность — например ври возврате с PPP на EtherNet. Еще одно Но — в реальной жизни размер пересылаемого блока меньше даже чем 1500 байт — не забывайте что у каждого пакета есть заголовок а иногда даже несколько — потому что мы использум стек протоколов TCP/IP. На самой вершине ваша программа — она говорит протоколу TCP — вот тебе блок отсылай, TCP добавляет свой заголовок и говорит уже протоколу IP вот тебе данные — посылай. IP не разбираясь, считает что все что он получил является для него одним блоком данных добавляет свой заголовок и отдает его EtherNet-у, тот в свою очередь добавляет свой заголовко и уже посылает конкретно. Вообщем реальный пакет это 3 заголовка + все что осталось от 1500 байт. Когда это чудо приходит на принимающий сокет, то TCP/IP хочет как можно быстрее доложиться тебе что пришли данные, и поэтому ты их можешь тут же считать, но ровно столько сколько есть в буффере сокета, то есть тебе самому надо реализовать буффер, наполнять его и по мере необходимости обрабатывать. Путем проб я для себя определил что наибольшая скорость передачи у меня достигается при размере внутренего буффера в 64240 байт — не знаю что это за магическое число — может это целое число тех данных что отстались от 1500 байт EtherNet-пакета ?
Re[6]: Быстрая пересылка через Socket
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 11.10.07 03:05
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Путем проб я для себя определил что наибольшая скорость передачи у меня достигается при размере внутренего буффера в 64240 байт — не знаю что это за магическое число — может это целое число тех данных что отстались от 1500 байт EtherNet-пакета ?


А как определялась эта самая скорость?
Re[6]: Быстрая пересылка через Socket
От: impure_soul Россия  
Дата: 12.10.07 09:52
Оценка:
Здравствуйте, Аноним, Вы писали:

А> ... TCP/IP хочет как можно быстрее доложиться тебе что пришли данные, и поэтому ты их можешь тут же считать, но ровно столько сколько есть в буффере сокета, то есть тебе самому надо реализовать буффер, наполнять его и по мере необходимости обрабатывать


А можно по подробнее как именно это проделать пользуясь асинхронной моделью? Может примерчик есть?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.