Страницы

Поиск по вопросам

Показаны сообщения с ярлыком tcp-ip. Показать все сообщения
Показаны сообщения с ярлыком tcp-ip. Показать все сообщения

суббота, 11 апреля 2020 г.

Возникновение сигнала SIGPIPE (ошибка EPIPE) при обращении из браузера Android устройства

#cpp #android #сокет #http #tcp_ip

                    
Написал демон на С++ (Линукс). Он слушает запросы от устройства на Android. 

Причем сделал универсально: в браузере на Android в адресной строке указывается IP-адрес/сайт
(на котором слушает демон), и сам демон посылает в ответ файл, который сохраняется
в браузере.

Использую неблокирующие сокеты, TCP и т.д.

Спустя некоторое время после начала отправки файла приходит ошибка EPIPE (errno =
32 - Broken pipe). После этой ошибки я закрываю сокет.

Не знаю как будет работать на других Android телефонах, но мой делает повторный запрос
и скачивает файл со второго раза без ошибки EPIPE. Боюсь, что на других телефонах файл
просто не скачается.

Если я проделываю то же самое со своего компьютера, то никаких ошибок EPIPE не возникает.

Хотелось бы разобраться почему так происходит.
    


Ответы

Ответ 1



Ошибка EPIPE возвращается обычно в том случае, если данный сокет никем не читается, тоесть нет ни одного процесса, имеющего открытый на чтение дескриптор, связанный с этим сокетом. Возможна ситуация, что Ваш клиент на Андроид по каким-то причинам закрыл дескриптор на чтение, связанный с этим сокетом, тогда, по идее, Вы должны были бы в демоне получить сигнал SIGPIPE и как-то обработать эту ситуацию. Если же вы игнорируете SIGPIPE, то вызов write() обязан вернуть вам EPIPE.

Ответ 2



Разобрался... Потратил кучу времени, чтобы выяснить - просто так на моём китайском телефоне (хотя вроде хуавей хвалят) работает браузер... в нете попадается информация, что стандартный загрузчик на Андроиде (особенно ниже 3.х) с ошибками скачивает файлы, и предлагается установить альтернативный браузер со встроенным менеджером загрузок, может Вам тоже этот подход попробовать? – margosh

Ответ 3



Эта ошибка возникает на всех линуксах, при работе с сокетами. Лечится вызовом signal(SIGPIPE, SIG_IGN);

понедельник, 9 марта 2020 г.

Как работать с TCP в Swift?

#ios #swift #tcp_ip


У меня есть TCP сервер, который слушает входящие команды и общается с БД. Как мне
слать к нему запросы из ios приложения? Работаю со swift 2.

Другими словами, нужно реализовать следующее:
Пользователь вводит логин и пароль, пытается войти в свой аккаунт. В это время посылается
запрос на сервер - "Проверь, есть ли такой юзер в БД". Он это делает, и шлет ответ
обратно на ios приложение. 

Никак не могу понять, как мне такое реализовать. Насколько я понимаю, мне нужно работать
с потоками, но ничего путного про это на ios я не нашел.
    


Ответы

Ответ 1



Если я Вас правильно понял, то потоки Вам вовсе не нужны. Тут надо использовать блоки. Скажем Вы отправляете запрос и включаете индикатор загрузки, а когда приходит ответ Вы прекращаете загрузку и в зависимости от результата с сервера используете success или же failure блок. Советую использовать AFNetworking. Что-то на подобии let manager = AFHTTPRequestOperationManager() manager.GET( "http://myServerUrl.com", parameters: ["email":"myemail@gmail.com", "password":"1234Password"], success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in //TODO - make login action }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in // TODO - show error message } )

Ответ 2



На сколько я понимаю вам необходимо выполнить HTTP запрос авторизации пользователя к вашему серверу и получить какой то ответ. Для работы с сетью на Swift вам подойдет Alamofire. Самый простой пример выполнения GET запроса: Alamofire.request(.GET, "https://yourserver.org/get", parameters: ["name": "user", "pass": "12345"]) .responseJSON { response in print(response.result) // result of response serialization } Более подробные примеры запросов приведены в описании либы.

среда, 4 марта 2020 г.

Гарантированнная доставка сообщения по протоколу TCP

#c_sharp #tcp #tcp_ip #протоколы


Всем привет. Уже несколько дней ломаю голову над одной проблемой. Как то раз нашёл
пример многопользовательского клиент-серверного консольного TCP чата. И если я не ошибаюсь,
то протокол TCP не гарантирует доставку сообщения. И в случае если сообщение не будет
доставлено полностью или доставлено вообще, то надо предпринять какие-то меры. Данная
программа если я не ошибаюсь, не предпринимает никаких действий если сообщения не будет
доставлено полностью.
Вопрос: Какие меры надо предпринять чтобы обеспечить гарантированную доставку сообщения
или же, что надо делать если сообщения не будет доставлено полностью? Или может быть
программа принимает какие либо меры, если сообщение не будет доставлено полностью?
Если не трудно, то покажите,что надо добавить в программу.
Программа имеет 2 проекта Клиент и Сервер.
Код Клиента:
Класс Program:

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ChatClient
{
    class Program
    {
        static string userName; 
        private const string host = "192.168.0.107"; 
        private const int port = 20113; 
        static TcpClient client; 
        static NetworkStream stream; // Создаём объект NetworkStream, через него
можно отправлять сообщения серверу или наоборот получать

        static void Main(string[] args)
        {
            Console.Write("Введите свое имя: ");
            userName = Console.ReadLine();
            client = new TcpClient();
            try
            {
                client.Connect(host, port); //подключение клиента
                stream = client.GetStream(); // возвращает объект NetworkStream

                string message = userName;
                byte[] data = Encoding.Unicode.GetBytes(message); //Присваиваем массиву
data перекодированное сообщение
                stream.Write(data, 0, data.Length); // Передаем массив data

                // запускаем новый поток для получения данных
                Thread receiveThread = new Thread(new ThreadStart(ReceiveMessage));
                receiveThread.Start(); //старт потока
                Console.WriteLine("Добро пожаловать, {0}", userName);
                SendMessage();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                Disconnect();
            }
        }
        // отправка сообщений
        static void SendMessage()
        {
           label1: Console.WriteLine("\nВведите сообщение: ");
            while (true)
            {
                string message = Console.ReadLine();
                byte[] data = Encoding.Unicode.GetBytes(message);
                if (message != "exit")
                {
                    stream.Write(data, 0, data.Length);
                }
                else
                {
                    message = "exit";
                label2: Console.WriteLine("Вы действительно хотите выйти из чата
Y / N:");
                    switch (Console.ReadKey().Key)
                    {
                        case ConsoleKey.Y:
                            stream.Write(data, 0, data.Length);
                            Disconnect();
                            break;
                        case ConsoleKey.N:
                            goto label1;
                        default:
                            Console.WriteLine("Введите Y / N\n");
                            goto label2;
                    }    
                }
            }
        }
        // получение сообщений
        static void ReceiveMessage()
        {
            while (true)
            {
                try
                {
                    byte[] data = new byte[64]; // буфер для получаемых данных
                    StringBuilder builder = new StringBuilder();
                    int bytes = 0;
                    do
                    {
                        bytes = stream.Read(data, 0, data.Length);
                        builder.Append(Encoding.Unicode.GetString(data, 0, bytes));
                    }
                    while (stream.DataAvailable);

                    string message = builder.ToString();
                    Console.WriteLine(message);//вывод сообщения
                }
                catch
                {
                    Console.WriteLine("Подключение прервано!"); //соединение было
прервано
                    Console.ReadLine();
                    Disconnect();
                }
            }
        }
        static void Disconnect()
        {
            Console.WriteLine("disconect");
            if (stream != null)
                stream.Close();//отключение потока
            if (client != null)
                client.Close();//отключение клиента
            Environment.Exit(0); //завершение процесса  
        }
    }
}


Код Сервера:
Класс ServerObject:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;

namespace ChatServer
{
    public class ServerObject
    {
            static TcpListener tcpListener; // сервер для прослушивания
            List clients = new List(); // все подключения
            protected internal void AddConnection(ClientObject clientObject)
            {
                clients.Add(clientObject);
            }
            protected internal void RemoveConnection(string id)
            {
                // получаем по id закрытое подключение
                ClientObject client = clients.FirstOrDefault(c => c.Id == id);
                // и удаляем его из списка подключений
                if (client != null)
                    clients.Remove(client);
            }
            // прослушивание входящих подключений
            protected internal void Listen()
            {
                try
                {
                    tcpListener = new TcpListener(IPAddress.Any, 8888);
                    tcpListener.Start();
                    Console.WriteLine("Сервер запущен. Ожидание подключений...");
                    while (true)
                    {
                        TcpClient tcpClient = tcpListener.AcceptTcpClient(); //Приём
ожидающего запроса на подключение
                        ClientObject clientObject = new ClientObject(tcpClient, this);
                        Thread clientThread = new Thread(new ThreadStart(clientObject.Process));
                        clientThread.Start();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Disconnect();
                }
            }
            // трансляция сообщения подключенным клиентам
            protected internal void BroadcastMessage(string message, string id)
            {
                byte[] data = Encoding.Unicode.GetBytes(message);
                for (int i = 0; i < clients.Count; i++)
                {
                    if (clients[i].Id != id) // если id клиента не равно id отправляющего
                    {
                        clients[i].Stream.Write(data, 0, data.Length); //передача данных
                    }
                }
            }
            // отключение всех клиентов
            protected internal void Disconnect()
            {
                tcpListener.Stop(); //остановка сервера

                for (int i = 0; i < clients.Count; i++)
                {
                    clients[i].Close(); //отключение клиента
                }
                Environment.Exit(0); //завершение процесса
            }
        }
    }


Класс ClientObject:

using System;
using System.Net.Sockets;
using System.Text;

namespace ChatServer
{
    public class ClientObject
    {
        protected internal string Id { get; private set; }
        protected internal NetworkStream Stream { get; private set; }
        string userName;
        TcpClient client;
        ServerObject server; // объект сервера

        public ClientObject(TcpClient tcpClient, ServerObject serverObject)
        {
            Id = Guid.NewGuid().ToString();
            client = tcpClient;
            server = serverObject;
            serverObject.AddConnection(this);
        }

        public void Process()
        {
            try
            {
                Stream = client.GetStream();
                // получаем имя пользователя
                string message = GetMessage();
                userName = message;
                string s = new String('*', 6);
                message = userName + " вошел в чат";
                // посылаем сообщение о входе в чат всем подключенным пользователям
                server.BroadcastMessage(message, this.Id);
                Console.WriteLine(message);
                // в бесконечном цикле получаем сообщения от клиента
                while (true) 
                {
                    try
                    {
                        message = GetMessage();
                    if (message == "exit")
                        {
                            message = String.Format($"{s}{userName}: покинул чат{s}");
                            Console.Write(message);
                            server.BroadcastMessage(message, this.Id);
                            break;

                        }
                    else
                        {
                            message = String.Format($"{userName}: {message}");
                            Console.WriteLine(message);
                            server.BroadcastMessage(message, this.Id);
                        }
                    }
                    catch
                    {
                        message = String.Format($"{s}{userName}: покинул чат{s}");
                        Console.Write(message);
                        server.BroadcastMessage(message, this.Id);
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                // в случае выхода из цикла закрываем ресурсы
                server.RemoveConnection(this.Id);
                Close();
            }
        }

        // чтение входящего сообщения и преобразование в строку
        private string GetMessage()
        {
            byte[] data = new byte[64]; // буфер для получаемых данных
            StringBuilder builder = new StringBuilder();
            int bytes = 0;
            do
            {
                bytes = Stream.Read(data, 0, data.Length);
                builder.Append(Encoding.Unicode.GetString(data, 0, bytes));
            }
            while (Stream.DataAvailable);

            return builder.ToString();
        }

        // закрытие подключения
        protected internal void Close()
        {
            if (Stream != null)
                Stream.Close();
            if (client != null)
                client.Close();
        }
    }
}


Класс Program:

using System;
using System.Threading;

namespace ChatServer
{
    class Program
    {
            static ServerObject server; // сервер
            static Thread listenThread; // потока для прослушивания
            static void Main(string[] args)
            {
                try
                {
                    server = new ServerObject();
                    listenThread = new Thread(new ThreadStart(server.Listen));
                    listenThread.Start(); //старт потока
                }
                catch (Exception ex)
                {
                    server.Disconnect();
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }


Пытался сделать так как посоветовал PashaPash, но всё без результатно.
Как мне посоветовал PashaPash я добавил признак окончания сообщения '/'

do
{
}
while (bytes==(byte)'/');



Если я правильно понял данные должны считываться как-то с помощью  StreamReader
Но как накапливать данные в MemoryStream не очень понятно?
    


Ответы

Ответ 1



TCP гарантирует доставку. Проблема в вашем коде в том, что он предполагает, что то, что передано в один вызов Write, будет вычитано одним вызовом Read на другой стороне. А это не так. Write не "отправляет пакет". Он просто пишет данные в сокет. А Read не "читает пакет". Он вычитывает из буфера сокета то, что успело дойти. Если вы сделали два вызова Write, подождали, и сделали один Read - вычитаются данные обоих вызовов. Если вы сделали вызов Write, и данные не успели дойти - Read вычитает только начало (начало строки, в вашем случае). Обработка в цикле с builder.Append - ненадежна, т.к. ваш код читает из локального буфера, очень быстро (быстрее, чем данные идут по сети!), и достигает состояния !Stream.DataAvailable где-то на середине сообщения. Кроме того, он считает, что каждый результат Read может быть преобразован в Unicode строку. И никак не учитывает, что может получить фрагмент с половиной символа в конце или начале. Надежные способы, на выбор: Передавать перед каждым сообщением его длину. Не читать напрямую из Stream - обернуть его в BinaryReader, и читать длину (reader.ReadInt32()), потом - ровно столько байт, сколько нужно (reader.GetBytes(messageLength) будет ждать, пока не придет нужное количество байт). Или использовать binaryWriter.Write(message) / binaryReader.ReadString(), который автоматически делают то же самое за вам. Ввести признак окончания сообщения, например символ переноса строки. Читать до тех пор, пока в прочитанном фрагмент нет соответствующего байта (а не по Stream.DataAvailable). И при этом накапливать вычитанное в MemoryStream, в виде байт чтобы избежать проблем с половиной юникодового символа. В конце преобразовывать в строчку все сообщение целиком. Пример на коде из вопроса (с минимальными изменениями): Клиент - просто заменяете работу со stream на работу с reader / writer: using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading; namespace ChatClient { class Program { static string userName; private const string host = "127.0.0.1"; private const int port = 8888; static TcpClient client; static BinaryReader reader; static BinaryWriter writer; static void Main(string[] args) { Console.Write("Введите свое имя: "); userName = Console.ReadLine(); client = new TcpClient(); try { client.Connect(host, port); //подключение клиента var stream = client.GetStream(); // возвращает объект NetworkStream reader = new BinaryReader(stream, Encoding.Unicode, true); writer = new BinaryWriter(stream, Encoding.Unicode, true); writer.Write(userName); // запускаем новый поток для получения данных Thread receiveThread = new Thread(new ThreadStart(ReceiveMessage)); receiveThread.Start(); //старт потока Console.WriteLine("Добро пожаловать, {0}", userName); SendMessage(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Disconnect(); } } // отправка сообщений static void SendMessage() { label1: Console.WriteLine("\nВведите сообщение: "); while (true) { string message = Console.ReadLine(); if (message != "exit") { writer.Write(message); } else { message = "exit"; label2: Console.WriteLine("Вы действительно хотите выйти из чата Y / N:"); switch (Console.ReadKey().Key) { case ConsoleKey.Y: writer.Write(message); Disconnect(); break; case ConsoleKey.N: goto label1; default: Console.WriteLine("Введите Y / N\n"); goto label2; } } } } // получение сообщений static void ReceiveMessage() { while (true) { try { string message = reader.ReadString(); Console.WriteLine(message);//вывод сообщения } catch { Console.WriteLine("Подключение прервано!"); //соединение было прервано Console.ReadLine(); Disconnect(); } } } static void Disconnect() { Console.WriteLine("disconect"); if (client != null) client.Close();//отключение клиента Environment.Exit(0); //завершение процесса } } } Сервер - то же самое, с указанием той же кодировки: using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Net; using System.Text; using System.Threading; namespace ChatServer { public class ServerObject { static TcpListener tcpListener; // сервер для прослушивания List clients = new List(); // все подключения protected internal void AddConnection(ClientObject clientObject) { clients.Add(clientObject); } protected internal void RemoveConnection(string id) { // получаем по id закрытое подключение ClientObject client = clients.FirstOrDefault(c => c.Id == id); // и удаляем его из списка подключений if (client != null) clients.Remove(client); } // прослушивание входящих подключений protected internal void Listen() { try { tcpListener = new TcpListener(IPAddress.Any, 8888); tcpListener.Start(); Console.WriteLine("Сервер запущен. Ожидание подключений..."); while (true) { TcpClient tcpClient = tcpListener.AcceptTcpClient(); //Приём ожидающего запроса на подключение ClientObject clientObject = new ClientObject(tcpClient, this); Thread clientThread = new Thread(new ThreadStart(clientObject.Process)); clientThread.Start(); } } catch (Exception ex) { Console.WriteLine(ex.Message); Disconnect(); } } // трансляция сообщения подключенным клиентам protected internal void BroadcastMessage(string message, string id) { for (int i = 0; i < clients.Count; i++) { if (clients[i].Id != id) // если id клиента не равно id отправляющего { clients[i].SendMessage(message); //передача данных } } } // отключение всех клиентов protected internal void Disconnect() { tcpListener.Stop(); //остановка сервера for (int i = 0; i < clients.Count; i++) { clients[i].Close(); //отключение клиента } Environment.Exit(0); //завершение процесса } } } using System; using System.IO; using System.Net.Sockets; using System.Text; namespace ChatServer { public class ClientObject { protected internal string Id { get; private set; } string userName; TcpClient client; ServerObject server; // объект сервера BinaryWriter writer; public ClientObject(TcpClient tcpClient, ServerObject serverObject) { Id = Guid.NewGuid().ToString(); client = tcpClient; server = serverObject; serverObject.AddConnection(this); } public void Process() { try { var stream = client.GetStream(); this.writer = new BinaryWriter(stream, Encoding.Unicode, false); var reader = new BinaryReader(stream, Encoding.Unicode, false); // получаем имя пользователя string message = reader.ReadString(); userName = message; string s = new String('*', 6); message = userName + " вошел в чат"; // посылаем сообщение о входе в чат всем подключенным пользователям server.BroadcastMessage(message, this.Id); Console.WriteLine(message); // в бесконечном цикле получаем сообщения от клиента while (true) { try { message = reader.ReadString(); if (message == "exit") { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break; } else { message = String.Format($"{userName}: {message}"); Console.WriteLine(message); server.BroadcastMessage(message, this.Id); } } catch { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break; } } } catch (Exception e) { Console.WriteLine(e.Message); } finally { // в случае выхода из цикла закрываем ресурсы server.RemoveConnection(this.Id); Close(); } } internal void SendMessage(string message) { this.writer.Write(message); } public void Close() { if (client != null) client.Dispose(); } } }

пятница, 14 февраля 2020 г.

Что за IP вида 10.658941?

#сеть #ip #tcp_ip


ping 10.658941


Даёт такой IP:


  PING 10.658941 (10.10.13.253) 56(84) bytes of data.


Объясните, пожалуйста, что значит .658941, и почему именно в таком формате? И для
чего вообще это нужно?
    


Ответы

Ответ 1



ip - это 32-х разрядное число. На 4 байта его делят, грубо говоря, для разделения уровня сетей, а выводят с разделением точками - для удобства восприятия. 10.13.253 = 10*256*256 + 13*256 + 253 = 658941 Можно вообще весь адрес записать как 168431101 или 0x0A0A0DFD

среда, 5 февраля 2020 г.

Какой смысл в числах SYN и ACK в протоколе TCP?

#tcp_ip #tcp


Привет. Не понимаю, что значит SYN и ACK при установке соединения по протоколу TCP.
Кучу видео пересмотрел и не понял. Кто разобрался в этом?  Какой смысл в этих SYN и
ACK (зачем их вообще придумали)? Какими-то непонятными числами компьютеры обмениваются,
какие-то приращения SYN на единицу... Нужен какой-то просто пример, понятный для того,
кто вообще в сетях не разбирается.
    


Ответы

Ответ 1



Придумали их с той важной целью, что пакеты, передющиеся по TCP, могут прийти не в той же последовательности что были отправлены и не в том же составе. Нужен механизм, который позволит собрать набор полученных пакетов в правильной последовательности. И заодно проверить все ли пакеты присутствуют или кто-то на пол пути сошёл с дистанции и потерялся. Эту задачу и решают при помощи номеров очереди и номеров подтверждений. Номера очереди (номера последовательности) - просто нумеруют отправляемые пакеты. Это число увеличивается в зависимости от длины поля данных. Каждый октет данных (т. е. каждый байт) одного пакета имеет свой номер очереди. Номер очереди первого октета данных и передаётся в заголовке TCP пакета, он же и считается номером очереди для пакета. Номера подтверждений - сообщают другой стороне номер очереди который ожидается получить от неё следующим. Они говорят, что пакеты со всеми предыдущими номерами очередей (но не включая этот) уже были получены. Первоначальный номер очереди высылается клиентом при установлении соединения вместе с флагом SYN. Сервер в ответ высылает номер подтверждения (полученный номер очереди + 1) и свой номер очереди (в целом любой, но при использовании механизма SYN coockie построенному по определённому алгоритму). Сервер в данный момент сообщает клиенту что ожидает от него пакет, который будет иметь номер очереди равный отправленному номеру подтверждения. От этого номера клиент в дальнейшем и отталкивается. Далее всё происходит таким образом - одна сторона (сторона А) отправляет другой (стороне Б) пакеты, пронумерованные номерами очередей. Вторая сторона принимает их и сообщает номер очереди, которая она ожидает получить от А со следующим пакетом. Это говорит о том, что сторона Б получила все пакеты, у которых номер очереди был ниже переданного номера подтверждения (но не равен ему) и что сторона Б ожидает, что в следующей партии переданных данных нумерация начнётся как раз с этого номера. На всякий случай ещё раз - поле TCP Номер очереди (Порядковый номер) означает просто номер пакета, нужен для того, что бы пакеты правильно собрать и обнаружить пропажу (или дубликат). Поле Номер подтверждения служит для информирования второй стороны о том какие пакеты были от неё уже получены (с какими порядковыми номерами) и содержит число, которое ожидается увидеть в поле Номер очереди следующего полученного пакета от этого же источника. P. S. SYN и ACK это всё же флаги, а не числа. Они говорят о том, что задействованы соответствующие поля заголовка (флаги TCP)

Как работает протокол TCP/IP?

#сеть #tcp_ip


Привет.

Вопрос по сетям (стек протоколов TCP/IP). Если человек вводит url-адрес в строку
адреса в браузере и жмет энтэр, то:


Браузер смотрит, а есть ли на компе в таблице соответствия доменных имен логическим
адресам данное доменное имя. Например, его нет в таблице и отправляется DNS-пакет-запрос
на DNS-сервер подсети... потом приходит ответ.
Айпи адрес получателя теперь известен и начинается установка соединения по протоколу
TCP - трехкратное рукопожатие.


...

Мне не понятно, ГДЕ все эти правила записаны (где эта последовательность действий
задана) - "посмотри, есть ли соответствие "доменное имя - айпи" на компе, если нет,
отправь, днс-запрос серверу днс, жди ответ, потом устанавливай соединение по протоколу
айпи, котом пакеты лови (хттп и тсп-пакеты) и посылай подтверждение о получении, закрывай
соединение"? Это какой-то протокол? Везде пишут про ОПИСАННУЮ ВЫШЕ последовательность
действий, но мне не понятно КАКАЯ ПРОГРАММА ЭТО ВСЕ ДЕЛАЕТ И ГДЕ ЭТО "ЗАШИТО".
    


Ответы

Ответ 1



Работа с сетью - это одна из частей многоуровневой системы ввода/вывода в операционной системе. Если вникать глубже, когда вы инициализируете запрос к устройству, этот запрос обслуживает диспетчер ввода-вывода, который передаст его куда нужно, и вернет в процесс данные, которые нужны от устройства. Во всех современных сетях используется так называемый стек протоколов для наслоения различных протоколов друг на друга. На каждом уровне решаются разные вопросы. Например, на самом нижнем уровне протоколы определяют, как сообщить в потоке битов, где начинаются и где заканчиваются пакеты. На более высоком уровне протоколы занимаются прокладыванием маршрутов для пакетов по сложным сетям от источника к месту назначения. И на еще более высоком уровне они обеспечивают надлежащую доставку всех пакетов в многопакетном сообщении в нужном порядке. Пользовательский процесс генерирует сообщение и выдает системный вызов для его отправки по предварительно установленному TCP-соединению. Стек протоколов, находящийся в ядре, добавляет в начало сообщения TCP-заголовок, а затем IP-заголовок. Затем сообщение поступает к драйверу сети Ethernet, который добавляет Ethernet-заголовок, направляющий пакет к маршрутизатору в сети Ethernet. После чего этот маршрутизатор внедряет пакет в Интернет. Чтобы установить соединение с удаленным хостом (или хотя бы отправить ему дейтаграмму), необходимо знать его IP-адрес. Поскольку оперировать списками с 32-битными адресами людям неудобно, была изобретена система под названием DNS (Domain Name System — система доменных имен), представляющая собой базу данных, которая отображает ASCII-имена хостов на их IP-адреса. Браузер запрашивает у DNS IP-адрес, соответствующий имени DNS в ответ выдает IP Браузер устанавливает TCP-соединение с портом 80 на хосте с IP-адресом Затем он отправляет запрос на файл index.html Сервер отправляет файл index.html Отображение файла TCP-соединение разрывается. Естественно, установление соединения, запрос IP по имени происходит через API операционной системы, операционная система пересылает ваш запрос DNS службе, которая сканирует файл hosts, и если там есть запись - выдает ее, иначе обращается к серверу или собственному кешу. Рекомендую Вам почитать "Современные операционные системы" Э. Таненбаум., книга большая, но знаний от нее получите по архитектуре и работе операционной системы массу.

понедельник, 30 декабря 2019 г.

Узнать внешний ip-адрес своего компьютера

#c_sharp #tcp_ip #ip_address


Как определить внешний Ip адрес своего компьютера на языке c#?
    


Ответы

Ответ 1



Полагаю, что тривиальное решение с обращением к внешнему сервису не требуется? Выполните трассировку на любой доступный публичный адрес. Первый не-приватный адрес в цепи - это почти наверняка адрес дефолтного шлюза внешнего маршрутизатора. Для определения его (самого маршрутизатора) адреса нужно оттрассировать все адреса подсети найденного дефолтного шлюза - при трассировке до внешнего адреса получите на 1 хоп меньше, чем до остальных адресов подсети. PS1. Поскольку гарантированного способа определить маску подсети дефолтного шлюза имхо нет, придётся делать какие-то допущения. Впрочем, провайдеры редко выделяют под вывод клиентов широкий пул. PS2. Провайдер запросто может учинить бяку и перекосячить трассировку (узлы могут не отвечать или нештатно менять TTL). А если у него OSPF с несколькими равноценными маршрутами, трасссировка может вообще превратиться в ходячий цирк. PS3. Провайдер может иметь несколько внешних каналов, и твой внешний адрес будет зависеть от чего угодно - начиная с адреса узла назначения и кончая ценой на рис в Антарктиде... PS4. И это ещё только для случая, когда выход в мир - прямой. А не приведи господи у тебя прокся какая-нибудь...

Ответ 2



Я пару раз пользовался вот таким образам: string pubIp = new System.Net.WebClient().DownloadString("https://api.ipify.org"); тут много примеров https://stackoverflow.com/questions/3253701/get-public-external-ip-address

четверг, 19 декабря 2019 г.

C++: кросплатформенный TCP клиент-сервер

#cpp #tcp_ip #клиент_сервер


Гуглил на тему tcp клиент-сервера, но выдает либо через WinAPI, либо через socket-ы
Linux.
Неужели за столько лет не появилось нормального кроссплатформенного решения в виде
приложения к стандартной библиотеке?
Прошу прояснить ситуацию
    


Ответы

Ответ 1



Добрый день! Нет, сети в стандарте С++, по-моему, до сих пор не появились. Поэтому ищем дополнительные, сторонние библиотеки. Используйте boost::asio или что-то подобное

Ответ 2



Если сервер не высоконагруженный, то ничего искать не нужно. Все, что касается сети и под Win и под Linux будет будет с бОльшего вполне кроссплатформенно. Ну да, разные header файлы будут. Ну и под Win придется библиотеку подключить дополнительно. Но это меньшее из зол (имеется ввиду чем тянуть еще буст и т.п.). Если же нужен сильный сервер, тогда, на мой взгляд, лучше вообще его писать под ту ось, на которой он и будет работать, а не придумывать себе сказку, что "сейчас я сделаю кроссплатформенный сервер, нужный всем и на всех осях и на все случаи жизни, и этот сервер будет держать 100500 соединений". Что касается клиента, то здесь тем более нет проблем. Но это все относительно вопроса ( а там только про сеть упоминалось ).

Ответ 3



Посмотри в сторону Qt. Они сейчас выросли в разы. Я бы делал на нем. Так же можно использовать Node.js , он прекрасно работает и под вин и под линукс. Php тоже самое, есть интепретатор под виндоус. Код менять не придется.

Ответ 4



Я щупал Qt и asio. Asio (в standalone-версии) - самый лёгкий вариант в плане размера бинарников и скорости исполнения, Qt почти не отстаёт по скорости, но библиотеки намного тяжелее (Qt5Core - 5+Мб, Qt5Network - 1.5Мб, тогда как бинарники клиента и сервера через asio были намного меньше 100 кб), но лучше документирован. Для asio почти вся документация идёт в варианте boost::asio, чтобы разобраться со standalone-версией пришлось денёк покопаться. Если решение в основном планируется на STL, я бы использовал asio. Если же в проекте Qt уже используется, QTcpServer и QTcpSocket сильно проект не утяжелят.

среда, 11 декабря 2019 г.

Java. Вопрос по архитектуре сервера для игры

#java #сокет #tcp_ip #io


Думаю над архитектурой сервера для игры и встал вопрос над тем, как обрабатывать
подключения клиентов. Протокол - tcp/ip.

Стоит выбор между многопоточной архитектурой (1 клиент - 1 поток) и асинхронным вводом/выводом
(когда чтение не блокирует поток, если данных нет, а возвращает 0 и поток может обрабатывать
других клиентов, то есть 1 поток - много клиентов).

Игра - mmorpg, где потенциально люди могут стоять в городе и не передавать почти
никакого ввода на сервер, но так же бывают динамичные моменты где важен быстрый ответ
сервера. 



Потоки при остановке не используют CPU циклы, более отзывчивые, однако держат оперативную
память, чего бы не хотелось в слабоактивных местах, где от игроков почти не идет ввода. 



Асинхронный подход использует меньше памяти, менее уязвим к слабоактивным клиентам,
но менее эффективен для активных клиентов.

Выбор очень сложный, поэтому хотел бы спросить совета у более опытных, которые имели
опыт с данными подходами, спасибо.
    


Ответы

Ответ 1



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

суббота, 7 декабря 2019 г.

Qt, Linux, С++. Кто может мешать связи по TCP/IP [решено]?

#cpp #linux #qt #tcp_ip


Написал небольшую библиотечку для синхронизации данных между программами. Есть очередь
сообщений. Есть сервер, цепляющийся к очереди, открывающий порт и отвечающий на запросы
типа "дай все сообщения", "дай сообщения с такого-то времени", "дай сообщения за последние
n мс" и так далее. Есть клиент, цепляющийся к аналогичной очереди, и периодически опрашивающий
сервера с указанными адресами/портами, занося новые сообщения в очередь. Подключается,
опрашивает и отключается. Написал несколько программ, обменивающихся через такие очереди.
Под Windows всё работает в любых версиях от W7 до W10, проверял c версиями Qt 4.7.1,
4.8.6, 5.5.1. Под Linux (Ubuntu 14.04, Qt 5.5.1) тоже работает практически всё. Проблема
в слове "практически".

Итак, есть программы A, B, C. B опрашивает несколько источников (A) и формирует выходную
очередь, которую опрашивают несколько клиентов (С). Доставка от A к B или от A к C
работает всегда. А вот от B к C иногда, если С запущена позже, чем B, связь не налаживается
- идут timeout'ы и порча пакетов. При этом, если B и C находятся на одном узле, всё
работает. Пробовал для отладки поднимать на ноутбуке виртуальные машины Virtualbox
и запускать B и C в разных виртуалках или одну в виртуалке, другую на хосте - всё работает,
даже если сетевые задержки сильно увеличивать. Проблемы появляются только при работе
между разными физическими компами. Попробовал пересобирать B под Qt 4.8.6 - проблема
появляется реже, но всё равно иногда остаётся. Если запускать B в виндовой версии,
через Wine - всё тоже работает. Сетевые экраны отключены. Порты (9200-9300), используемые
для сервера, никем не заняты, после запуска программ-серверов (A, B) сканер показывает,
что они (порты) открыты.

Сейчас собираю на работе тестовый стенд (несколько узлов с Linux) для того, чтобы
запустить B под отладчиком. Но в целом ситуация вызывает у меня глубочайшее недоумение,
так как:


один и тот же код работает между A и B, между A и C, но не между B и C
один и тот же код работает между разными виртуальными машинами, но не между разными
физическими
... в сборке для `Windows`, но не в сборке для `Linux`.
... если C запущена раньше, чем B, но не наоборот.


Код проверен Valgrind'ом и cppcheck'ом. Натыкался ли кто-нибудь на похожие "грабли",
можете ли предположить хоть одну причину такого странного поведения?

Update: Всё страньше и страньше. Собрал тестовый стенд - и не могу воспроизвести
проблему. Те же самые A, B и C цепляются друг к другу во всех возможных комбинациях.
Чувствую, придётся запасной switch на объект везти.

Update2: Удалось воспроизвести проблему на стенде. Цепляю к одному B несколько C,
через некоторое время часть клиентов (C) перестаёт цепляться. Что удивительно: когда
клиент цепляться перестал, я пробую с его машины пинговать машину, на которой развёрнут
сервер (B) - и она не пингуется. И начинает пинговаться только после того, как машина
B в свою очередь начнёт пинговать клиентскую (С). У меня появилось подозрение, что
Ubuntu воспринимает эту кучу запросов, приходящую с клиента, как DOS-атаку, и блокирует
хост клиента. Осталось найти, где в настройках системы это прописано, и отключить.

Update3: Ситуация действительно очень похожа на защиту от DDOS-атаки. И такая версия
объясняет, почему связь между A и B работает, а между B и C - нет. К одному источнику
данных (A) цепляется максимум один промежуточный обработчик (B), а вот к нему может
цепляться несколько клиентов C. И именно обращения на один порт с разных узлов, видимо,
интерпретируются как DDOS-атака. При этом первый подключившийся клиент остаётся работоспособным,
а вот последующие обрубаются так, что даже ping'и от них не проходят.

Update4: найден workaround. Я запустил на машине, на которой запущен B, команду ping
до хостов, на которых запущены C. После этого отрубаться эти хосты перестали. Вместе
с тем, понять, что же вызывает такую реакцию системы, пока не получается. ufw отключён.

Update5: проблема решена. Сервер не должен рвать соединение, надо дождаться, пока
соединение разорвёт клиент (см. Update к комментарию).
    


Ответы

Ответ 1



Причины возникновения проблемы нашлись. Их было две. Одна - в тестовой системе, там серьёзные неполадки с сетью, и даже pingи теряются процентов на 30. Вторая и главная причина - в моём коде, сервер неправильно закрывал соединение. Обработав входящее соединение и сделав socket->write(), он висел на socket->waitForBytesWritten() (таким образом, до завершения отправки поток сервера был заблокирован), а после этого выполнял socket->close(). Видимо, такое принудительное закрытие сокета и не нравилось ОС. Когда я убрал waitForBytesWritten() и вместо close() сделал disconnectFromHost(), всё заработало. Update: Заработало не всё. На сервере проблема осталась, workaround из Update4 помогает, пока конфигурация остаётся стационарной. Когда мы добавляем мобильный клиент, подключающийся из разных мест, с разных IP, добавить его в пингующий скрипт уже не получается, и он довольно скоро отваливается. На клиентах, даже стационарных, иногда появляются сообщения об обрыве и восстановлении связи. Решение проблемы нашёл только недавно. Итак: когда сервер стал закрывать сокет более осторожно, через disconnectFromHost, проблема стала менее острой. Однако правильное решение оказалось таким: сервер вообще не должен закрывать сокет самостоятельно! Он должен реагировать на сигналы disconnected и error, по которым вызывает deleteLater(). Когда сервер сам решает закрыть сокет, завершив отправку данных, возникает гонка сигналов. Сигнал об отключении с сервера может прилететь раньше, чем ответ сервера клиенту, и тогда мы на клиенте получаем ситуацию "сервер отключился - сервер подключился". А когда до сервера долетает запрос на отключение от клиента, а сокета уже нет, видимо, возникает аварийная ситуация, и накопление таких ситуаций приводит к блокировке IP клиента (под Linux. Windows такие запросы, похоже, просто игнорирует). Итог: из сервера просто удалил disconnectFromHost(), и старый клиент работает (завершается неделя тестирования 2 клиента на 1 сервер) без единой потери связи. Timeoutы уменьшил с 400-600 мс до 10мс, и всё равно разрывов нет. Главный вывод: сервер не должен закрывать соединение сам, он должен реагировать на сигналы disconnected и error, возникающие вследствие действий клиента и среды передачи данных.

пятница, 5 июля 2019 г.

Возникновение сигнала SIGPIPE (ошибка EPIPE) при обращении из браузера Android устройства

Написал демон на С++ (Линукс). Он слушает запросы от устройства на Android.
Причем сделал универсально: в браузере на Android в адресной строке указывается IP-адрес/сайт (на котором слушает демон), и сам демон посылает в ответ файл, который сохраняется в браузере.
Использую неблокирующие сокеты, TCP и т.д.
Спустя некоторое время после начала отправки файла приходит ошибка EPIPE (errno = 32 - Broken pipe). После этой ошибки я закрываю сокет.
Не знаю как будет работать на других Android телефонах, но мой делает повторный запрос и скачивает файл со второго раза без ошибки EPIPE. Боюсь, что на других телефонах файл просто не скачается.
Если я проделываю то же самое со своего компьютера, то никаких ошибок EPIPE не возникает.
Хотелось бы разобраться почему так происходит.


Ответ

Разобрался... Потратил кучу времени, чтобы выяснить - просто так на моём китайском телефоне (хотя вроде хуавей хвалят) работает браузер...
в нете попадается информация, что стандартный загрузчик на Андроиде (особенно ниже 3.х) с ошибками скачивает файлы, и предлагается установить альтернативный браузер со встроенным менеджером загрузок, может Вам тоже этот подход попробовать? – margosh

четверг, 30 мая 2019 г.

Как работать с TCP в Swift?

У меня есть TCP сервер, который слушает входящие команды и общается с БД. Как мне слать к нему запросы из ios приложения? Работаю со swift 2
Другими словами, нужно реализовать следующее: Пользователь вводит логин и пароль, пытается войти в свой аккаунт. В это время посылается запрос на сервер - "Проверь, есть ли такой юзер в БД". Он это делает, и шлет ответ обратно на ios приложение.
Никак не могу понять, как мне такое реализовать. Насколько я понимаю, мне нужно работать с потоками, но ничего путного про это на ios я не нашел.


Ответ

Если я Вас правильно понял, то потоки Вам вовсе не нужны. Тут надо использовать блоки. Скажем Вы отправляете запрос и включаете индикатор загрузки, а когда приходит ответ Вы прекращаете загрузку и в зависимости от результата с сервера используете success или же failure блок. Советую использовать AFNetworking
Что-то на подобии
let manager = AFHTTPRequestOperationManager() manager.GET( "http://myServerUrl.com", parameters: ["email":"myemail@gmail.com", "password":"1234Password"], success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in //TODO - make login action }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in // TODO - show error message } )

понедельник, 20 мая 2019 г.

Гарантированнная доставка сообщения по протоколу TCP

Всем привет. Уже несколько дней ломаю голову над одной проблемой. Как то раз нашёл пример многопользовательского клиент-серверного консольного TCP чата. И если я не ошибаюсь, то протокол TCP не гарантирует доставку сообщения. И в случае если сообщение не будет доставлено полностью или доставлено вообще, то надо предпринять какие-то меры. Данная программа если я не ошибаюсь, не предпринимает никаких действий если сообщения не будет доставлено полностью. Вопрос: Какие меры надо предпринять чтобы обеспечить гарантированную доставку сообщения или же, что надо делать если сообщения не будет доставлено полностью? Или может быть программа принимает какие либо меры, если сообщение не будет доставлено полностью? Если не трудно, то покажите,что надо добавить в программу. Программа имеет 2 проекта Клиент и Сервер. Код Клиента: Класс Program:
using System; using System.Net.Sockets; using System.Text; using System.Threading;
namespace ChatClient { class Program { static string userName; private const string host = "192.168.0.107"; private const int port = 20113; static TcpClient client; static NetworkStream stream; // Создаём объект NetworkStream, через него можно отправлять сообщения серверу или наоборот получать
static void Main(string[] args) { Console.Write("Введите свое имя: "); userName = Console.ReadLine(); client = new TcpClient(); try { client.Connect(host, port); //подключение клиента stream = client.GetStream(); // возвращает объект NetworkStream
string message = userName; byte[] data = Encoding.Unicode.GetBytes(message); //Присваиваем массиву data перекодированное сообщение stream.Write(data, 0, data.Length); // Передаем массив data
// запускаем новый поток для получения данных Thread receiveThread = new Thread(new ThreadStart(ReceiveMessage)); receiveThread.Start(); //старт потока Console.WriteLine("Добро пожаловать, {0}", userName); SendMessage(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Disconnect(); } } // отправка сообщений static void SendMessage() { label1: Console.WriteLine("
Введите сообщение: "); while (true) { string message = Console.ReadLine(); byte[] data = Encoding.Unicode.GetBytes(message); if (message != "exit") { stream.Write(data, 0, data.Length); } else { message = "exit"; label2: Console.WriteLine("Вы действительно хотите выйти из чата Y / N:"); switch (Console.ReadKey().Key) { case ConsoleKey.Y: stream.Write(data, 0, data.Length); Disconnect(); break; case ConsoleKey.N: goto label1; default: Console.WriteLine("Введите Y / N
"); goto label2; } } } } // получение сообщений static void ReceiveMessage() { while (true) { try { byte[] data = new byte[64]; // буфер для получаемых данных StringBuilder builder = new StringBuilder(); int bytes = 0; do { bytes = stream.Read(data, 0, data.Length); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); } while (stream.DataAvailable);
string message = builder.ToString(); Console.WriteLine(message);//вывод сообщения } catch { Console.WriteLine("Подключение прервано!"); //соединение было прервано Console.ReadLine(); Disconnect(); } } } static void Disconnect() { Console.WriteLine("disconect"); if (stream != null) stream.Close();//отключение потока if (client != null) client.Close();//отключение клиента Environment.Exit(0); //завершение процесса } } }
Код Сервера: Класс ServerObject:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Net; using System.Text; using System.Threading;
namespace ChatServer { public class ServerObject { static TcpListener tcpListener; // сервер для прослушивания List clients = new List(); // все подключения protected internal void AddConnection(ClientObject clientObject) { clients.Add(clientObject); } protected internal void RemoveConnection(string id) { // получаем по id закрытое подключение ClientObject client = clients.FirstOrDefault(c => c.Id == id); // и удаляем его из списка подключений if (client != null) clients.Remove(client); } // прослушивание входящих подключений protected internal void Listen() { try { tcpListener = new TcpListener(IPAddress.Any, 8888); tcpListener.Start(); Console.WriteLine("Сервер запущен. Ожидание подключений..."); while (true) { TcpClient tcpClient = tcpListener.AcceptTcpClient(); //Приём ожидающего запроса на подключение ClientObject clientObject = new ClientObject(tcpClient, this); Thread clientThread = new Thread(new ThreadStart(clientObject.Process)); clientThread.Start(); } } catch (Exception ex) { Console.WriteLine(ex.Message); Disconnect(); } } // трансляция сообщения подключенным клиентам protected internal void BroadcastMessage(string message, string id) { byte[] data = Encoding.Unicode.GetBytes(message); for (int i = 0; i < clients.Count; i++) { if (clients[i].Id != id) // если id клиента не равно id отправляющего { clients[i].Stream.Write(data, 0, data.Length); //передача данных } } } // отключение всех клиентов protected internal void Disconnect() { tcpListener.Stop(); //остановка сервера
for (int i = 0; i < clients.Count; i++) { clients[i].Close(); //отключение клиента } Environment.Exit(0); //завершение процесса } } }
Класс ClientObject:
using System; using System.Net.Sockets; using System.Text;
namespace ChatServer { public class ClientObject { protected internal string Id { get; private set; } protected internal NetworkStream Stream { get; private set; } string userName; TcpClient client; ServerObject server; // объект сервера
public ClientObject(TcpClient tcpClient, ServerObject serverObject) { Id = Guid.NewGuid().ToString(); client = tcpClient; server = serverObject; serverObject.AddConnection(this); }
public void Process() { try { Stream = client.GetStream(); // получаем имя пользователя string message = GetMessage(); userName = message; string s = new String('*', 6); message = userName + " вошел в чат"; // посылаем сообщение о входе в чат всем подключенным пользователям server.BroadcastMessage(message, this.Id); Console.WriteLine(message); // в бесконечном цикле получаем сообщения от клиента while (true) { try { message = GetMessage(); if (message == "exit") { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break;
} else { message = String.Format($"{userName}: {message}"); Console.WriteLine(message); server.BroadcastMessage(message, this.Id); } } catch { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break; } } } catch (Exception e) { Console.WriteLine(e.Message); } finally { // в случае выхода из цикла закрываем ресурсы server.RemoveConnection(this.Id); Close(); } }
// чтение входящего сообщения и преобразование в строку private string GetMessage() { byte[] data = new byte[64]; // буфер для получаемых данных StringBuilder builder = new StringBuilder(); int bytes = 0; do { bytes = Stream.Read(data, 0, data.Length); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); } while (Stream.DataAvailable);
return builder.ToString(); }
// закрытие подключения protected internal void Close() { if (Stream != null) Stream.Close(); if (client != null) client.Close(); } } }
Класс Program:
using System; using System.Threading;
namespace ChatServer { class Program { static ServerObject server; // сервер static Thread listenThread; // потока для прослушивания static void Main(string[] args) { try { server = new ServerObject(); listenThread = new Thread(new ThreadStart(server.Listen)); listenThread.Start(); //старт потока } catch (Exception ex) { server.Disconnect(); Console.WriteLine(ex.Message); } } } }
Пытался сделать так как посоветовал PashaPash, но всё без результатно. Как мне посоветовал PashaPash я добавил признак окончания сообщения '/'
do { } while (bytes==(byte)'/');
Если я правильно понял данные должны считываться как-то с помощью StreamReader Но как накапливать данные в MemoryStream не очень понятно?


Ответ

TCP гарантирует доставку.
Проблема в вашем коде в том, что он предполагает, что то, что передано в один вызов Write, будет вычитано одним вызовом Read на другой стороне. А это не так.
Write не "отправляет пакет". Он просто пишет данные в сокет.
А Read не "читает пакет". Он вычитывает из буфера сокета то, что успело дойти.
Если вы сделали два вызова Write, подождали, и сделали один Read - вычитаются данные обоих вызовов.
Если вы сделали вызов Write, и данные не успели дойти - Read вычитает только начало (начало строки, в вашем случае).
Обработка в цикле с builder.Append - ненадежна, т.к. ваш код читает из локального буфера, очень быстро (быстрее, чем данные идут по сети!), и достигает состояния !Stream.DataAvailable где-то на середине сообщения. Кроме того, он считает, что каждый результат Read может быть преобразован в Unicode строку. И никак не учитывает, что может получить фрагмент с половиной символа в конце или начале.
Надежные способы, на выбор:
Передавать перед каждым сообщением его длину. Не читать напрямую из Stream - обернуть его в BinaryReader, и читать длину (reader.ReadInt32()), потом - ровно столько байт, сколько нужно (reader.GetBytes(messageLength) будет ждать, пока не придет нужное количество байт). Или использовать binaryWriter.Write(message) / binaryReader.ReadString(), который автоматически делают то же самое за вам. Ввести признак окончания сообщения, например символ переноса строки. Читать до тех пор, пока в прочитанном фрагмент нет соответствующего байта (а не по Stream.DataAvailable). И при этом накапливать вычитанное в MemoryStream, в виде байт чтобы избежать проблем с половиной юникодового символа. В конце преобразовывать в строчку все сообщение целиком.
Пример на коде из вопроса (с минимальными изменениями):
Клиент - просто заменяете работу со stream на работу с reader / writer:
using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading;
namespace ChatClient { class Program { static string userName; private const string host = "127.0.0.1"; private const int port = 8888; static TcpClient client; static BinaryReader reader; static BinaryWriter writer;
static void Main(string[] args) { Console.Write("Введите свое имя: "); userName = Console.ReadLine(); client = new TcpClient(); try { client.Connect(host, port); //подключение клиента var stream = client.GetStream(); // возвращает объект NetworkStream reader = new BinaryReader(stream, Encoding.Unicode, true); writer = new BinaryWriter(stream, Encoding.Unicode, true);
writer.Write(userName);
// запускаем новый поток для получения данных Thread receiveThread = new Thread(new ThreadStart(ReceiveMessage)); receiveThread.Start(); //старт потока Console.WriteLine("Добро пожаловать, {0}", userName); SendMessage(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Disconnect(); } }
// отправка сообщений static void SendMessage() { label1: Console.WriteLine("
Введите сообщение: "); while (true) { string message = Console.ReadLine(); if (message != "exit") { writer.Write(message); } else { message = "exit"; label2: Console.WriteLine("Вы действительно хотите выйти из чата Y / N:"); switch (Console.ReadKey().Key) { case ConsoleKey.Y: writer.Write(message); Disconnect(); break; case ConsoleKey.N: goto label1; default: Console.WriteLine("Введите Y / N
"); goto label2; } } } } // получение сообщений static void ReceiveMessage() { while (true) { try { string message = reader.ReadString(); Console.WriteLine(message);//вывод сообщения } catch { Console.WriteLine("Подключение прервано!"); //соединение было прервано Console.ReadLine(); Disconnect(); } } } static void Disconnect() { Console.WriteLine("disconect"); if (client != null) client.Close();//отключение клиента Environment.Exit(0); //завершение процесса } } }
Сервер - то же самое, с указанием той же кодировки:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Net; using System.Text; using System.Threading;
namespace ChatServer { public class ServerObject { static TcpListener tcpListener; // сервер для прослушивания List clients = new List(); // все подключения protected internal void AddConnection(ClientObject clientObject) { clients.Add(clientObject); } protected internal void RemoveConnection(string id) { // получаем по id закрытое подключение ClientObject client = clients.FirstOrDefault(c => c.Id == id); // и удаляем его из списка подключений if (client != null) clients.Remove(client); } // прослушивание входящих подключений protected internal void Listen() { try { tcpListener = new TcpListener(IPAddress.Any, 8888); tcpListener.Start(); Console.WriteLine("Сервер запущен. Ожидание подключений..."); while (true) { TcpClient tcpClient = tcpListener.AcceptTcpClient(); //Приём ожидающего запроса на подключение ClientObject clientObject = new ClientObject(tcpClient, this); Thread clientThread = new Thread(new ThreadStart(clientObject.Process)); clientThread.Start(); } } catch (Exception ex) { Console.WriteLine(ex.Message); Disconnect(); } } // трансляция сообщения подключенным клиентам protected internal void BroadcastMessage(string message, string id) { for (int i = 0; i < clients.Count; i++) { if (clients[i].Id != id) // если id клиента не равно id отправляющего { clients[i].SendMessage(message); //передача данных } } } // отключение всех клиентов protected internal void Disconnect() { tcpListener.Stop(); //остановка сервера
for (int i = 0; i < clients.Count; i++) { clients[i].Close(); //отключение клиента } Environment.Exit(0); //завершение процесса } } }
using System; using System.IO; using System.Net.Sockets; using System.Text;
namespace ChatServer { public class ClientObject { protected internal string Id { get; private set; } string userName; TcpClient client; ServerObject server; // объект сервера BinaryWriter writer;
public ClientObject(TcpClient tcpClient, ServerObject serverObject) { Id = Guid.NewGuid().ToString(); client = tcpClient; server = serverObject; serverObject.AddConnection(this); }
public void Process() { try { var stream = client.GetStream();
this.writer = new BinaryWriter(stream, Encoding.Unicode, false); var reader = new BinaryReader(stream, Encoding.Unicode, false); // получаем имя пользователя string message = reader.ReadString();
userName = message; string s = new String('*', 6); message = userName + " вошел в чат"; // посылаем сообщение о входе в чат всем подключенным пользователям server.BroadcastMessage(message, this.Id); Console.WriteLine(message); // в бесконечном цикле получаем сообщения от клиента while (true) { try { message = reader.ReadString(); if (message == "exit") { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break;
} else { message = String.Format($"{userName}: {message}"); Console.WriteLine(message); server.BroadcastMessage(message, this.Id); } } catch { message = String.Format($"{s}{userName}: покинул чат{s}"); Console.Write(message); server.BroadcastMessage(message, this.Id); break; } } } catch (Exception e) { Console.WriteLine(e.Message); } finally { // в случае выхода из цикла закрываем ресурсы server.RemoveConnection(this.Id); Close(); } }
internal void SendMessage(string message) { this.writer.Write(message); }
public void Close() { if (client != null) client.Dispose(); } } }

суббота, 20 апреля 2019 г.

Какой смысл в числах SYN и ACK в протоколе TCP?

Привет. Не понимаю, что значит SYN и ACK при установке соединения по протоколу TCP. Кучу видео пересмотрел и не понял. Кто разобрался в этом? Какой смысл в этих SYN и ACK (зачем их вообще придумали)? Какими-то непонятными числами компьютеры обмениваются, какие-то приращения SYN на единицу... Нужен какой-то просто пример, понятный для того, кто вообще в сетях не разбирается.


Ответ

Придумали их с той важной целью, что пакеты, передющиеся по TCP, могут прийти не в той же последовательности что были отправлены и не в том же составе. Нужен механизм, который позволит собрать набор полученных пакетов в правильной последовательности. И заодно проверить все ли пакеты присутствуют или кто-то на пол пути сошёл с дистанции и потерялся.
Эту задачу и решают при помощи номеров очереди и номеров подтверждений. Номера очереди (номера последовательности) - просто нумеруют отправляемые пакеты. Это число увеличивается в зависимости от длины поля данных. Каждый октет данных (т. е. каждый байт) одного пакета имеет свой номер очереди. Номер очереди первого октета данных и передаётся в заголовке TCP пакета, он же и считается номером очереди для пакета. Номера подтверждений - сообщают другой стороне номер очереди который ожидается получить от неё следующим. Они говорят, что пакеты со всеми предыдущими номерами очередей (но не включая этот) уже были получены.
Первоначальный номер очереди высылается клиентом при установлении соединения вместе с флагом SYN. Сервер в ответ высылает номер подтверждения (полученный номер очереди + 1) и свой номер очереди (в целом любой, но при использовании механизма SYN coockie построенному по определённому алгоритму). Сервер в данный момент сообщает клиенту что ожидает от него пакет, который будет иметь номер очереди равный отправленному номеру подтверждения. От этого номера клиент в дальнейшем и отталкивается.
Далее всё происходит таким образом - одна сторона (сторона А) отправляет другой (стороне Б) пакеты, пронумерованные номерами очередей. Вторая сторона принимает их и сообщает номер очереди, которая она ожидает получить от А со следующим пакетом. Это говорит о том, что сторона Б получила все пакеты, у которых номер очереди был ниже переданного номера подтверждения (но не равен ему) и что сторона Б ожидает, что в следующей партии переданных данных нумерация начнётся как раз с этого номера.
На всякий случай ещё раз - поле TCP Номер очереди (Порядковый номер) означает просто номер пакета, нужен для того, что бы пакеты правильно собрать и обнаружить пропажу (или дубликат). Поле Номер подтверждения служит для информирования второй стороны о том какие пакеты были от неё уже получены (с какими порядковыми номерами) и содержит число, которое ожидается увидеть в поле Номер очереди следующего полученного пакета от этого же источника.
P. S. SYN и ACK это всё же флаги, а не числа. Они говорят о том, что задействованы соответствующие поля заголовка (флаги TCP)

среда, 5 декабря 2018 г.

Java. Вопрос по архитектуре сервера для игры

Думаю над архитектурой сервера для игры и встал вопрос над тем, как обрабатывать подключения клиентов. Протокол - tcp/ip
Стоит выбор между многопоточной архитектурой (1 клиент - 1 поток) и асинхронным вводом/выводом (когда чтение не блокирует поток, если данных нет, а возвращает 0 и поток может обрабатывать других клиентов, то есть 1 поток - много клиентов).
Игра - mmorpg, где потенциально люди могут стоять в городе и не передавать почти никакого ввода на сервер, но так же бывают динамичные моменты где важен быстрый ответ сервера.

Потоки при остановке не используют CPU циклы, более отзывчивые, однако держат оперативную память, чего бы не хотелось в слабоактивных местах, где от игроков почти не идет ввода.

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


Ответ

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

четверг, 11 октября 2018 г.

Qt, Linux, С++. Кто может мешать связи по TCP/IP [решено]?

Написал небольшую библиотечку для синхронизации данных между программами. Есть очередь сообщений. Есть сервер, цепляющийся к очереди, открывающий порт и отвечающий на запросы типа "дай все сообщения", "дай сообщения с такого-то времени", "дай сообщения за последние n мс" и так далее. Есть клиент, цепляющийся к аналогичной очереди, и периодически опрашивающий сервера с указанными адресами/портами, занося новые сообщения в очередь. Подключается, опрашивает и отключается. Написал несколько программ, обменивающихся через такие очереди. Под Windows всё работает в любых версиях от W7 до W10, проверял c версиями Qt 4.7.1, 4.8.6, 5.5.1. Под Linux (Ubuntu 14.04, Qt 5.5.1) тоже работает практически всё. Проблема в слове "практически".
Итак, есть программы A, B, C. B опрашивает несколько источников (A) и формирует выходную очередь, которую опрашивают несколько клиентов (С). Доставка от A к B или от A к C работает всегда. А вот от B к C иногда, если С запущена позже, чем B, связь не налаживается - идут timeout'ы и порча пакетов. При этом, если B и C находятся на одном узле, всё работает. Пробовал для отладки поднимать на ноутбуке виртуальные машины Virtualbox и запускать B и C в разных виртуалках или одну в виртуалке, другую на хосте - всё работает, даже если сетевые задержки сильно увеличивать. Проблемы появляются только при работе между разными физическими компами. Попробовал пересобирать B под Qt 4.8.6 - проблема появляется реже, но всё равно иногда остаётся. Если запускать B в виндовой версии, через Wine - всё тоже работает. Сетевые экраны отключены. Порты (9200-9300), используемые для сервера, никем не заняты, после запуска программ-серверов (A, B) сканер показывает, что они (порты) открыты.
Сейчас собираю на работе тестовый стенд (несколько узлов с Linux) для того, чтобы запустить B под отладчиком. Но в целом ситуация вызывает у меня глубочайшее недоумение, так как:
один и тот же код работает между A и B, между A и C, но не между B и C один и тот же код работает между разными виртуальными машинами, но не между разными физическими ... в сборке для `Windows`, но не в сборке для `Linux`. ... если C запущена раньше, чем B, но не наоборот.
Код проверен Valgrind'ом и cppcheck'ом. Натыкался ли кто-нибудь на похожие "грабли", можете ли предположить хоть одну причину такого странного поведения?
Update: Всё страньше и страньше. Собрал тестовый стенд - и не могу воспроизвести проблему. Те же самые A, B и C цепляются друг к другу во всех возможных комбинациях. Чувствую, придётся запасной switch на объект везти.
Update2: Удалось воспроизвести проблему на стенде. Цепляю к одному B несколько C, через некоторое время часть клиентов (C) перестаёт цепляться. Что удивительно: когда клиент цепляться перестал, я пробую с его машины пинговать машину, на которой развёрнут сервер (B) - и она не пингуется. И начинает пинговаться только после того, как машина B в свою очередь начнёт пинговать клиентскую (С). У меня появилось подозрение, что Ubuntu воспринимает эту кучу запросов, приходящую с клиента, как DOS-атаку, и блокирует хост клиента. Осталось найти, где в настройках системы это прописано, и отключить.
Update3: Ситуация действительно очень похожа на защиту от DDOS-атаки. И такая версия объясняет, почему связь между A и B работает, а между B и C - нет. К одному источнику данных (A) цепляется максимум один промежуточный обработчик (B), а вот к нему может цепляться несколько клиентов C. И именно обращения на один порт с разных узлов, видимо, интерпретируются как DDOS-атака. При этом первый подключившийся клиент остаётся работоспособным, а вот последующие обрубаются так, что даже ping'и от них не проходят.
Update4: найден workaround. Я запустил на машине, на которой запущен B, команду ping до хостов, на которых запущены C. После этого отрубаться эти хосты перестали. Вместе с тем, понять, что же вызывает такую реакцию системы, пока не получается. ufw отключён.
Update5: проблема решена. Сервер не должен рвать соединение, надо дождаться, пока соединение разорвёт клиент (см. Update к комментарию).


Ответ

Причины возникновения проблемы нашлись. Их было две. Одна - в тестовой системе, там серьёзные неполадки с сетью, и даже pingи теряются процентов на 30. Вторая и главная причина - в моём коде, сервер неправильно закрывал соединение. Обработав входящее соединение и сделав socket->write(), он висел на socket->waitForBytesWritten() (таким образом, до завершения отправки поток сервера был заблокирован), а после этого выполнял socket->close(). Видимо, такое принудительное закрытие сокета и не нравилось ОС. Когда я убрал waitForBytesWritten() и вместо close() сделал disconnectFromHost(), всё заработало.
Update: Заработало не всё. На сервере проблема осталась, workaround из Update4 помогает, пока конфигурация остаётся стационарной. Когда мы добавляем мобильный клиент, подключающийся из разных мест, с разных IP, добавить его в пингующий скрипт уже не получается, и он довольно скоро отваливается. На клиентах, даже стационарных, иногда появляются сообщения об обрыве и восстановлении связи. Решение проблемы нашёл только недавно.
Итак: когда сервер стал закрывать сокет более осторожно, через disconnectFromHost, проблема стала менее острой. Однако правильное решение оказалось таким: сервер вообще не должен закрывать сокет самостоятельно! Он должен реагировать на сигналы disconnected и error, по которым вызывает deleteLater()
Когда сервер сам решает закрыть сокет, завершив отправку данных, возникает гонка сигналов. Сигнал об отключении с сервера может прилететь раньше, чем ответ сервера клиенту, и тогда мы на клиенте получаем ситуацию "сервер отключился - сервер подключился". А когда до сервера долетает запрос на отключение от клиента, а сокета уже нет, видимо, возникает аварийная ситуация, и накопление таких ситуаций приводит к блокировке IP клиента (под Linux. Windows такие запросы, похоже, просто игнорирует).
Итог: из сервера просто удалил disconnectFromHost(), и старый клиент работает (завершается неделя тестирования 2 клиента на 1 сервер) без единой потери связи. Timeoutы уменьшил с 400-600 мс до 10мс, и всё равно разрывов нет. Главный вывод: сервер не должен закрывать соединение сам, он должен реагировать на сигналы disconnected и error, возникающие вследствие действий клиента и среды передачи данных.