1

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

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

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

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

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

Подякували: Chemist-i1

3

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

0xDADA11C7 написав:

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

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

4

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

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

5

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

offtopic

З поверненням.

6

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

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

З поверненням.

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

7

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

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

З поверненням.

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

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

дякую

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");
            }
        }

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

12

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

koala написав:

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

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

13 Востаннє редагувалося koala (05.04.2014 19: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 байт, наприклад?

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

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

15

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

FakiNyan написав:

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

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

16

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

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

17

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

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

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

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

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

18

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

0xDADA11C7 написав:

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

Що?

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 тисяч з копійками... І після цього вже вилазить повідомлення про помилку. Що тут не так? Чому в звичайному режимі роботи сервер працює нормально, а при "отладці" всілякі помилки вилазять?

20

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

FakiNyan написав:

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

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

FakiNyan написав:

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

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

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