21 Востаннє редагувалося FakiNyan (06.04.2014 11:54:28)

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

Torbins написав:

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

Тіпа так?
http://не-дійсний-домен/7XZYk.png

"гарно я намалював?"

ПОХВАЛІТЬ МЕНЕ

Torbins написав:

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

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

бага це тіпу баг? Окремі повідомлення, це тіпо, щоб вони приходили в такому стані, як і надсилались? ну щоб не змішувалися?

22

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

Усе так. Намальовано гарно і правильно.

Подякували: FakiNyan1

23

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

Torbins написав:

Усе так. Намальовано гарно і правильно.

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

http://s1.ipicture.ru/uploads/20140406/SnT9QkZb.gif

24

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

окей, ну в мене є ідея, як осідлати той малюнок, буду малювати блок-схему

Подякували: koala1

25

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

атставіть, мені було дуже ліньки оту блок схему писати і оте все інше, тому, для початку я вирішив перевірити, через що стається помилка на сервері, ну, зробив я це просто, взяв останні 147 байт відправлені клієнтом і отримані 147 байт сервером і перевів ото в 16-ткову форму, ну чи яка вона там, власне я вивів кожен байт як число і просто перевірив чи дорівнюють відправлені 147 байт отриманим 147 байтам, і так, вони ідентичні, тобто помилка не в тому, что вони якось не так приходять, а в тому, что я їх невірно розбираю на сервері. от тут і треба шукати правди..

Подякували: 0xDADA11C7, koala2

26

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

Ну то локалізуйте код сервера у модуль (або просто повидирайте цікаві частини) і викладайте сюди, щоб ми могли подивиться

27

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

0xDADA11C7 написав:

Ну то локалізуйте код сервера у модуль (або просто повидирайте цікаві частини) і викладайте сюди, щоб ми могли подивиться

я майже докопався до суті. завтра спробую сам розібратися, якщо не зможу, то зкину

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

власне я вже зкидував той шматок коду сюди, але ви не дивіться на нього!

28

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

Наскільки мені відомо (поправте мене якщо я помиляюся) асинхронні сокети - суто віндузовське збочення, спричинене відсутністю подоби POSIX функції fork()

29

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

0xDADA11C7 написав:

Наскільки мені відомо (поправте мене якщо я помиляюся) асинхронні сокети - суто віндузовське збочення, спричинене відсутністю подоби POSIX функції fork()

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

30 Востаннє редагувалося 0xDADA11C7 (08.04.2014 11:38:49)

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

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

31

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

0xDADA11C7 написав:

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

НЄЄЄ,  я пишу ММО РПГ, мені тре щоб все було дуже швидко, а не так, що клієнт відправив запит який потребує звернення до бд, і поки сервер працює з бд і не відправив відповіді, то клієнт не може відправити позицію персонажа, ну ващє вже

32

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

хм, розбір даних наче вірний, але вилазить помилка при десереалізації

33

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

ні, хлопці... щось тут не так, темні демони повного місяця поставили проклятий бар'єр між мною та знанням, мені потрібна ваша допомога, лише наша дружба врятує цей світ від зла!
Коротше кажучи, я написав програмку-емулятор клієнта та сервера. Я хтів земулювати той випадок, коли клієнт вже відправив декілька повідомлень, а сервер ще не може їх прийняти. В мене ця затримка 10 секунд. Працює так - ви запускаєте програму і починаєте писати повідомлення, вони відправляються і через десять секунд сервер їх прийме, і знаєте, що я помітив? Помилка вилазить лише на третій ітерації розбору прийнятого масиву байтів, так само, як і в моєму проекті. І ще одне, якщо відправляти повідомлення, котрі складаються лише з чисел (без пробілів, здається), то все окей, але якщо це текстові повідомлення, то помилка... Ось проект, він досить простий, клас ServerEmu - це сервер, ClientEmu - клієнт, ClientData - клас-повідомлення, котрий в собі буде містити повідомлення, котре ви напишете після запуску програми. http://rghost.ru/53898046

34

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

От так завжди... я не якийсь геній і не дуже добре розуміюсь на математиці, не знаю, чи це просто не моє, чи я просто тупенький, але є лише одна правда - БОРІТЕСЯ - ПОБОРЕТЕ!!!! і так! я переміг! Навіть не знадобилась допомога друзів.

35

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

гаразд, поясню. Помилка була в тому, що я зчитував дані починаючи не з того індексу. А  чому перші дві ітерації все працювало? в мене є змінна startIndex, котра містить індекс початку даних, котрі містять довжину повідомлення, і є змінна fullLength котра містить довжину всього пакету, тобто це довжина повідомлення + 2 байти, котрі містять довжину. Так от, як індекс зчитування нового повідомлення я підставляв fullLength+2, а тепер дивіться. http://не-дійсний-домен/81grl.png
Очевидно, якщо всі пакети мають однакову довжину, то це буде працювати, також, завдяки десереалізації за допомогою ProtoBuf - пакети, котрі містять дані нединамічних розмірів, (int має фіксований розмір в 4 байти, а string немає фіксованого розміру) теж будуть нормально оброблятися, тому що навіть якщо ви передаєте клас, котрий містить єдине поле типу string, і якщо в цьому полі записані дані, котрі можна привести до типу int, то ProtoBuf зробить це під час серіалізації, наприклад,

string s = "111";

І все, що мені треба було зробити, так це замінити fullLength+2 на startIndex+2

Подякували: 0xDADA11C7, koala2

36

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

Можете показати фрагмент коду, який це робить?

37

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

koala написав:

Можете показати фрагмент коду, який це робить?

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, startIndex + 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;

                            SystemMessage("length: " + dataLength + "  full length: " + fullLength + "  start index: " + startIndex);

                        } while (startIndex < 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);
            }
            catch (Exception e)
            {
                SystemMessage(e.Message + " | ReceiveCallback");
            }
        }
Прихований текст

а у Вас репутації 666 хД

38

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

Зараз ви мені репутацію трохи виправите...
Не хочу детально лізти в код, але:
0. Для синхронізації в реальному часі, як не дивно, UDP кращий.
1. Немає перевірки, чи достатньо даних прийшло. Якщо сума всіх dataLength більша за nBytes, буде кепсько.
2. Проголошуйте змінні разом з ініціалізацією в тому блоці, в якому вони будуть використовуватися:

 do
{
  int dataLength = BitConverter.ToUInt16(client.bufferReceive, startIndex);

3. fulllength в такому вигляді взагалі не потрібен - він завжди більший dataLength на 2 і ніде більше не використовується.
4. Ніколи не використовуйте "магічні числа":

Buffer.BlockCopy( client.bufferReceive, startIndex + sizeof( UInt16 ), arr, 0, dataLength );

5. Зверніть увагу на класи NetworkStream і TcpClient.
6. Не забувайте ставити else у взаємовиключних гілках - навіть якщо зараз вони взаємовиключаються умовами:

else if (nBytes == 0)

7. Дуже не бажано перехоплювати всі виключні ситуації. Якщо ви очікуєте чогось - це правильно його перехопити і обробити; але якщо станеться дійсно неочікувана ситуація - хай з нею розбирається те, що її очікувало, а не ваша заглушка.

Подякували: FakiNyan1

39

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

koala написав:

Зараз ви мені репутацію трохи виправите...
Не хочу детально лізти в код, але:
0. Для синхронізації в реальному часі, як не дивно, UDP кращий.
1. Немає перевірки, чи достатньо даних прийшло. Якщо сума всіх dataLength більша за nBytes, буде кепсько.
2. Проголошуйте змінні разом з ініціалізацією в тому блоці, в якому вони будуть використовуватися:

 do
{
  int dataLength = BitConverter.ToUInt16(client.bufferReceive, startIndex);

3. fulllength в такому вигляді взагалі не потрібен - він завжди більший dataLength на 2 і ніде більше не використовується.
4. Ніколи не використовуйте "магічні числа":

Buffer.BlockCopy( client.bufferReceive, startIndex + sizeof( UInt16 ), arr, 0, dataLength );

5. Зверніть увагу на класи NetworkStream і TcpClient.
6. Не забувайте ставити else у взаємовиключних гілках - навіть якщо зараз вони взаємовиключаються умовами:

else if (nBytes == 0)

7. Дуже не бажано перехоплювати всі виключні ситуації. Якщо ви очікуєте чогось - це правильно його перехопити і обробити; але якщо станеться дійсно неочікувана ситуація - хай з нею розбирається те, що її очікувало, а не ваша заглушка.

0) Ну так я хочу отак зробити. Всі дані, котрі треба передавати, я поділяю на дві групи - дані про позицію/поворот персонажа і всі інші дані (використання вмінь, атака, повідомлення в чат і т.д.). Так от першу групу я хочу передавати по UDP, тому що там треба якнайшвидше передавати нову позицію, аби персонажи рухались біль-менш синхронізовано. А ще ж UDP використовують в голосових чатиках, тобто він забезпечує швидкість, але іноді дані можуть пошкоджуватись, тому я пошкодженні дані буду просто відкидати, і так, як частота відправки позиції буде великою, то це не дуже погано буде впливати на рухи персонажів. Я ще читав, що краще взагалі використовувати UDP і навішати на нього всі потрібні прибамбаси, котрі є в TCP, ну щоб було швидко і надійно. Але я трохи запутався з тим UDP - не знаю, що сервер має додавати в коллекцію з клієнтами, там же сокетів немає, якийсь EndPoint але один... ну таке.
1) Ну про це мені пан Torbins, здається, писав. Але поки я не зіштовхнувся з цією проблемою - дуже важко заставити себе написати так, як треба.
2) А чо так?
3) Ну це да, я просто спочатку пишу так, аби було зрозуміло, ну як в голові уявляю, так і роблю, а потім вже оптимізую, якщо є повторювані частини коду, то виношу їх в метод і все інше, це я потім зроблю.
4) Та це я теж знаю, сам не полюбляю отих маг. чисел, потім виправлю все.
5) А навіщо? От про NetworkStream не знаю, але TcpClient це ж просто клас, котрий працює з сокетами TCP, просто вже там якісь готові штуки в нього є. А я просто працюю з TCP сокетами, ну типу роблю те саме, що TcpClient, але по-своєму
6) Це теж оптимізація ага?
7) Окєй.

40

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

0,1,3,4,7. Це не вимоги, це поради. Не треба нічого пояснювати, просто врахуйте. Зрештою, програма ваша.
2. Бо:
- змінна знищується, щойно стає непотрібною, і не буде використана вдруге;
- не буде двох присвоювань, а отже, буде менше плутанини (колись ви подивитеся в цей код, побачите, що dataLength = 0, а потім будете довго шукати, чому воно працює не так).
5. Бо код, протестований тисячами програмістів, кращий за власний велосипед (якщо, звісно, ваша задача не написати саме велосипед, тоді вибачте). А про NetworkStream почитайте.
6. Якщо колись щось зміниться і перший блок почне змінювати nBytes (наприклад, зменшувати на кількість прочитаних байтів), то другий все одно не почне виконуватися і програма буде працювати, як і раніше.