Страницы

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

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

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

Как в TCP определить, что кабель отключен?

#tcp #передача_данных #сетевое_программирование


При работе сетевого приложения сервер-клиент может произойти обрыв кабеля или его
вытащат на клиенте. TCP протокол никак не реагирует на это событие, иногда даже ОС
не сразу показывает, что кабель отключен. 
Мое приложение работает почти в реальном времени и пользователь должен знать, что
соединение прервалось.

Как в приложении определить, что канал связи перестал работать?
    


Ответы

Ответ 1



Протокол TCP по модели OSI отделен от физической среды передачи, потому ему не нужно знать, что там произошло. Не поможет просто периодически записывать пустой пакет в сокет, операция send просто копирует данные в буфер ОС, а ОС сама их уже отправляет. При этом если с первого раза не дошло TCP отправляет еще несколько раз (до 15), в итоге о том что отправка не удалась вы узнаете через 5 минут или чуть раньше. Я решил этот вопрос на уровне протокола приложения. Эти решения предназначены только для обработки случая с потерей связи на физическом уровне. Вариант 1 "Клиент-маяк" Это случай, когда клиенту не важно работает сервер или нет, и установкой соединения управляет сервер. Сервер сам подключает своих клиентов и решает, когда разорвать соединение. Проверка реализована на стороне сервера. На сервере: Подключиться к клиенту. Принять сообщение "пакет жизни" или другое сообщение от клиента. Установить время для таймера ожидания следующего сообщения 4 сек. Проверять истекло ли время ожидания или пришло сообщение. 4.1. Если Время истекло, то разорвать соединение с клиентом принудительно (считаем нет связи). 4.2. Если пришло сообщение, то шаг 3. На клиенте: Установить соединение к серверу. Отправлять "пакеты жизни" каждые 2 сек без ожидания ответа. Вариант 2 "Взаимный активный контроль" Если нужно оперативно реагировать на отсутствие связи и это два равноправных узла взаимодействующие по каналу связи, например видеозвонок между абонентами. В этом случае каждый из участников выполняет мониторинг состояния соединения самостоятельно. Используется активный периодический опрос. На каждой стороне: Установить соединение с ответной стороной. Отправить запрос PING, установить время ожидания для таймера ответа 4 сек. Проверять наличие ответа и окончание времени ожидания ответа. 3.1 Если пришел ответ PONG или сообщение, то сбросить таймер ожидания ответа, затем подождав 2 сек выполнить шаг 2. 3.2 Если время истекло, то разорвать соединение принудительно (считаем нет связи). Второй участник выполняет мониторинг таким же образом. Вариант 3 (упрощенный вариант 2, активный контроль) Если один из участников сервер и все клиенты подключаются к нему. Протокол приложения должен поддерживать модель "запрос-ответ". В этом случае только клиенты всегда подключаются к серверу. На клиенте: Подключиться к серверу. Выполнить запрос PING, установить время ожидания для таймера ответа 4 сек. Проверять наличие ответа и окончание времени ожидания ответа. 3.1. Если Время истекло, то разорвать соединение принудительно (считаем нет связи). 3.2. Если пришло сообщение, то подождать 2 сек, затем шаг 3. На сервере: Принять соединение клиента. Ожидать 4 сек получения запросов от клиента. Если запрос получен, то шаг 2. Если время ожидания вышло, то разорвать соединение принудительно (считаем нет связи).

вторник, 17 марта 2020 г.

Замена данных в TCP пакете

#java #замена #tcp


В общем, есть два устройства.
Одно отсылает пакеты, а другое, соответственно, их принимает, изменяет и дальше отправляет
в сеть. 

И тут еще суть в том, что нужно оставить заголовки пакета, окончание, а содержимое
заменить на другое. 

То есть что получается: мне нужно поднять сервер, получить пакет, убрать из него
содержимое и вставить своё. Отправить пакет дальше. Даже схемка есть: 

Вот - N - это количество "D".



Не понимаю, с чего начинать, т.к. с этим сталкиваюсь в первый раз. И хотелось бы
понять, что нарисовано на картинке.
    


Ответы

Ответ 1



Тут вроде ничего сложного. На картинке показана структура 2-х пакетов: Сообщение, которое к вам приходит. Сообщение, которое вы должны отослать. По каждому сообщению подробно расписана структура следования байтов (Б1, Б2 и т.д.), также показано, какую часть и куда нужно переносить. Тело исходного сообщения состоит из массива данных структуры "D", каждый элемент по 6 байт. Но я сейчас какие-то прописные истины перечислил)), которые вы и сами, наверное, поняли) Но вот что мне не понятно по этому рисунку: Во-первых, "Измерительное сообщение 98 байт максимум" - следовательно, пакет нефиксированной длины, а как мы узнаем об окончании пакета? + не хватает 10 байт в описании, максимум 98, а на картинке расписано только 88 байт. Может, еще есть какая-нибудь информация? Во-вторых, тело исходного сообщения на картинке 84 байта, а уместить его нужно в 103 байта, с ним производятся какие-то манипуляции? Или нужно добивать нулями до определенной длинны? =============================================== Все еще остаются вопросы) вот смотрите примерный алгоритм На вашем сервере(на рис - прибор 2), считываем поток байт, для начала берем первый байт и сверяем заголовок, судя по рисунку заголовок всегда 23 если да то продолжаем читать, если нет то нужно больше информации о других пакетах и дальнейших действиях Теперь когда мы поняли что за пакет, нам нужно узнать остальные данные, для этого нужно понять длинну пакета, и тут опять непонятно как ее вычислить она всегда фиксирована и равна = 113 байт ( тогда вам нужно считать еще 110 байт + crc(2 байта) ), или "N" - это количество D, как говорил @avp, тогда вам нужно считать еще один байт, будем считать что это count, тогда формула будет такая count * 6 + crc( 2 байта ) Когда вы получили тело сообщения, нужно сверить crc ( последние 2 байта), но тут не написано для какой части сообщения его сверять? Если crc совпал то теперь можно формировать новый пакет, но как его формировать тоже не совсем понятно

Ответ 2



Короче, вот что я сумел настрогать: Вот прибор 1: import java.io.*; import java.net.*; import java.util.*; import java.nio.ByteBuffer; import java.math.BigInteger; class TCPClient { public static void main(String argv[]) throws Exception { Random rand = new Random(); int[] keys = {20, 1480, 70, 1520, 1470, 60, 0, -16, -15, -14}; float[] values = {0.000f, 0.000f, 0.000f, 0.000f, 0.006f, 0.000f, 0f, 22.150f, 744.301f, 41.136f}; byte[] bots = new byte[88]; byte[] one_byte = new byte[4]; //one_byte = intToByteArray(023, 1); bots[0] = intToByteArray(23, 1)[0]; bots[1] = intToByteArray(14, 1)[0]; for(int i=2, key_store = 0; i < 81; i++, key_store++) { one_byte = intToByteArray(keys[key_store], 2); // код ЗХВ bots[i++] = one_byte[0]; bots[i++] = one_byte[1]; one_byte = FloatToByteArray(values[key_store]); // отчет float bots[i++] = one_byte[0]; bots[i++] = one_byte[1]; bots[i++] = one_byte[2]; bots[i++] = one_byte[3]; } one_byte = intToByteArray(rand.nextInt(), 2); // код CRC 16 bots[86] = one_byte[0]; bots[87] = one_byte[1]; for(int i=0; i < 88; i++) { System.out.print(bots[i] + " "); } Socket socket = new Socket("localhost", 6789); DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream()); BufferedReader inFromServer = new BufferedReader(new InputStreamReader(socket.getInputStream())); outToServer.write(bots); int bytesWritten = outToServer.size(); System.out.println("Total " + bytesWritten + " bytes are written to stream."); String modifiedSentence = inFromServer.readLine(); System.out.println("FROM SERVER: " + modifiedSentence); socket.close(); } public static byte[] FloatToByteArray(float value) { int bits = Float.floatToIntBits(value); byte[] bytes = new byte[4]; bytes[0] = (byte)(bits & 0xff); bytes[1] = (byte)((bits >> 8) & 0xff); bytes[2] = (byte)((bits >> 16) & 0xff); bytes[3] = (byte)((bits >> 24) & 0xff); return bytes; } public static byte[] intToByteArray(int value, int length) { if(length == 2) { byte[] data = new byte[2]; data[0] = (byte) (value & 0xFF); data[1] = (byte) ((value >> 8) & 0xFF); return data; } else { BigInteger bigInt = BigInteger.valueOf(value); return bigInt.toByteArray(); } } static int crc16(final byte[] buffer) { int crc = 0xFFFF; for (int j = 0; j < buffer.length ; j++) { crc = ((crc >>> 8) | (crc << 8) )& 0xffff; crc ^= (buffer[j] & 0xff);//byte to int, trunc sign crc ^= ((crc & 0xff) >> 4); crc ^= (crc << 12) & 0xffff; crc ^= ((crc & 0xFF) << 5) & 0xffff; } crc &= 0xffff; return crc; } } Как я думаю, как оно работает: первые 2 байта - кодирую int в byte и записываю. Дальше идут структуры "D", содержащие по 6 байт, где первые 2 байта ключ, а остальные 4 - значение. Ну и в конце код CRC. Но что получается, на устройстве 2 я принимаю запрос так: Socket connectionSocket = welcomeSocket.accept(); BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream())); DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream()); while ((i = inFromClient.read()) != -1) { } и получаю int значение, когда должно приходить byte, отправлял же в byte. Что делать? Вообще правильно?

пятница, 13 марта 2020 г.

Простейший TCP server на node.js

#nodejs #tcp


Пытаюсь создать простейший TCP server (node.js)
Собственно с кодом вроде проблем нет:

var net = require('net');

var HOST = '0.0.0.0';
var PORT = 6969;

// Create a server instance, and chain the listen function to it
// The function passed to net.createServer() becomes the event handler for the 'connection'
event
// The sock object the callback function receives UNIQUE for each connection
net.createServer(function(sock) {

    // We have a connection - a socket object is assigned to the connection automatically
    console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort);

    // Add a 'data' event handler to this instance of socket
    sock.on('data', function(data) {
        console.log('DATA ' + sock.remoteAddress + ': ' + data);
        // Write the data back to the socket, the client will receive it as data
from the server
        sock.write('You said "' + data + '"');
    });

    // Add a 'close' event handler to this instance of socket
    sock.on('close', function(data) {
        console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort);
    });

}).listen(PORT, HOST);

console.log('Server listening on ' + HOST +':'+ PORT);


Проблема в том, что это надо разместить на VPS (Ubuntu 14 (64-bit). Все попытки послать
пакет по адресу x.x.x.x:6969 не дают результата.
Сервер стартую командой nodejs file.js. Печатает подтверждение (Server listening
on...) и тишина.
    


Ответы

Ответ 1



iptables -I INPUT -p tcp -m tcp --dport 6969 -j ACCEPT решило проблему

среда, 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(); } } }

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

Как сгенерировать ASCII для сетевых протоколов в консоли?

#python #сеть #tcp #протоколы


Есть какая-нибудь консольная утилита, которая позволяет просматривать ASCII-диаграммы
для сетевых протоколов непосредственно в консоли?
    


Ответы

Ответ 1



Да, есть консольная утилита protocol, написанная на python, которая позволяет взглянуть на стандартные заголовки сетевых протоколов прямо в консоли, без необходимости искать информацию в интернетах. Для установки инструмента выполните: git clone https://github.com/luismartingarcia/protocol.git сd protocol sudo ./setup.py install Собственно, всё! Проверим как работает, выполним: protocol ethernet Получаем ASCII-диаграмму для ethernet: Более подробная информация здесь.

понедельник, 10 февраля 2020 г.

Какие типы данных и в каком виде можно передавать через TCP bsd socket?

#cpp #c #сокет #tcp


Во всех примерах использования bsd сокетов на си для передачи и приема информации
используется массив символов фиксированной длинны char buff[255]. Например read(socketFD,
buff, sizeof(buff));. Возникает пара вопросов:


Обязательно ли использовать массив char, или же можно передавать произвольные объекты?
например можно ли передать double buff[255] или даже struct someStructType buff[255]?
Что будет, если отправить массив например в 200 элементов, а на принимающей стороне
считать только 100? При повторном чтении мы прочитаем те же элементы, или же следующие
100 элементов?

    


Ответы

Ответ 1



В сокет передаются исключительно байты, что бы ваши объекты превратить в байты или создать их из них нужно использовать протоколы сериализации, такие как например T-L-V или ProtoBuf. В примерах приводится массив-буфер для считывания в него информации, как правило функция чтения возвращает реальное количество считанных байт. Если вы отправите 200 байт, а считаете 100, то вам надо будет считать потом еще 100. Буфер может быть любого размера (но как правило его делают не более MTU/MRU). Как правило при обмене данными через сеть, они упаковываются в пакет вида: packetLength:packetData, что позволяет гарантированно считывать переданный пакет. Так, например, отправляя 200 байт нужной информации вы: Считываете в буфер приема данные из сокета. Читаете из этого буфера длину пакета packetLenght (сколько байт - зависит от типа переменной и архетиктруры); Создаете буфер размером packetLength. Это не тот же самый буфер в который вы читаете из сокета!! Копируете из буфера приема данные в созданный буфер, при этом если размер данных меньше, чем осталось в буфере, то копируете нужно кол-во, если больше, то пишите все, а затем дочитываете далее из сокета в буфер приема до тех пор, пока не вытащите все.

Ответ 2



Первый, по сути, вопрос переносимости. Вы просто передаете блок байтов. Нужны гарантии корректной интерпретации их с другой стороны. Вдруг там другой порядок байтов? Другой формат данных? Или еще что-то? Вот поэтому и используется максимально обобщенный вариант - просто сколько-то байтов. Что касается структур - то учтите еще и выравнивание. Надеюсь, чем чревата передача просто объектов в духе string s; write(fd, &s, sizeof(s)); вы понимаете? :) А то некоторые ухитряются... Второй - прочтете при следующем чтении остальные 100 байтов. Если, конечно, до того не закроете соединение :)

среда, 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)

Что происходит когда сокет записывает данные в то время, когда с другой стороны читаются предыдущие данные?

#cpp #qt5 #tcp #сокеты


К примеру у меня есть сокет, который в цикле (2 итерации к примеру) записывает данные
подряд. При этом на другом конце (где этот сокет прослушивается) в это время происходит
считывание. Так вот, что будет когда происходит вторая попытка записи данных в сокет,
когда на другом конце еще считывается первая часть пересланых данных?
    


Ответы

Ответ 1



Если сокет блокирующий и на чтение запрошено больше данных, чем Вы отправляете - будет ждать "второй итерации". Если сокет неблокирующий - вычитает, сколько сможет, и затем Вам снова придется опрашивать сокет. В связи с комментарием @VTT хочу дать немного более развернутый ответ. Если вызывается одна из функций read/recv/recvfrom для блокируемого сокета и при этом в буфере нет никаких данных - сокет переходит в спящее состояние до тех пор, пока не придут какие-либо данные. Достаточно одного байта. Тем не менее, мы можем задать флаг MSG_WAITALL - в этом случае мы будем ждать до тех пор, пока не будет доступно фиксированное (запрошенное нами) количество байт. В случае неблокируемого сокета - если не удовлетворены условия ввода - то ф-ия вернет управление установив ошибку EWOULDBLOCK. Если вызывается одна из функций write/send/sendto - данные копируются из буфера приложения в буфер отправки сокета. Для блокируемого сокета - если в буфере отправки недостаточно места, процесс переходит в состоянии оидания до тех пор, пока это самое место не освободится. Для неблокируемого сокета - если в буфере отправки недостаточно места - ф-ия вернет управление, установив ошибку EWOULDBLOCK. Комбинируйте и предполагайте наиболее вероятный результат того, чем же все-таки окончится посылка двух сообщений :)

Ответ 2



В общем случае, попытки записи и чтения данных будут не связаны. Вторая попытка записи может происходить совершенно независимо от поведения (или даже от наличия) партнера на другой стороне. Более того, когда вызов функции записи на первой итерации успешно вернется, нет оснований полагать, что только что отправленные данные на самом деле куда-то ушли. Если активирован алгоритм Нейгла, то только что посланные данные могут спокойно сидеть в буфере на стороне отправителя. Даже если подкрутить настройки (допустим TCP_NODELAY), то данные на момент второго вызова записи могут еще быть где-то в пути, или сидеть в буфере на принимающей стороне.

среда, 29 января 2020 г.

Отправка данных через сокет (упаковка) с++

#cpp #linux #сокет #tcp #udp


Необходимо отправить данные типа:float/int/char через сокет, как организовать "упаковку"
на стороне отправителя что бы отправить всё одним пакетом, и "распаковать" на принимающей
стороне.

В сети нашёл скрин приложения для игры, как упаковать так же? 


    


Ответы

Ответ 1



Примитивный способ для простых случаев - описать структуру данных: struct Data { int a; float b; char c[32]; } data; Записать её в сокет на передающей стороне (send(socket, &data, sizeof(data), 0) и на принимающей прочитать в точно такую же структуру (recv(socket, &data, sizeof(data), 0)). Очень важно чтобы стуктура на обоих сторонах (передающей и приемной) была идентичной по расположению в памяти (одинаковые размеры типов, одинаковый порядок байтов в системе, одинаковое выравнивание полей структуры, одинаковое представление чисел с плавающей точкой). Иначе получаем не те данные, что отправили. На практике, если принимающая сторона ещё и на другом языке написана, получим лишнюю возню и простор для появления ошибок. Следующий вариант - набивать буфер данных вручную: int foo = 42; long bar = 0; std::string str; str.append((char*)&foo, sizeof(int)); str.append((char*)&bar, sizeof(long)); Здесь уже нет проблемы с выравниванием полей структуры как в первом варианте, т.к. данные мы склеиваем сами, без промежутков. Но остальные проблемы пока ещё с нами (по прежнему порядок байтов, размеры типов, представление чисел с плавающей точкой должны быть идентичными на передатчике и приемнике). Ручная, побайтовая набивка потока. uint32_t foo = 42; std::vector buffer; buffer.push_back(static_cast(foo >> 0)); buffer.push_back(static_cast(foo >> 8)); buffer.push_back(static_cast(foo >> 16)); buffer.push_back(static_cast(foo >> 24)); Здесь просто берем каждый кусок данных и вручную переносим в выходной поток в независимом от системы порядке. Разбирать тоже придется вручную. Наиболее универсальный способ, т.к. все аспекты генерируемого потока контролируем сами. Для удобства можно написать класс сериализатора/десериализатора для требуемых типов (включая пользовательские). Со временем (а может быть и сразу) добавляются сложности, связанные с изменением передаваемых данных (например понадобилось передать дополнительные данные или какие-то старые уже стали неактуальными). Особенно если приемник должен принимать данные и в старом формате и в новом. Придется добавлять какие-то идентификаторы версии. Дополнительно нужно обработать случаи, когда нужно передать опциональные данные (которые могут отсутствовать) или данные динамического размера (массивы). Чтобы не решать все эти задачи самостоятельно, можно взять готовое решение, например protobuf от google. Поддерживает разные языки, имеет систему версий, поддержку комплексных данных. Или немного более простое решение (но и более быстрое), тоже от google flatbuffers. Если объем передаваемой информации не критичен, возможно будет удобным формировать данные в виде json (например с помощью https://github.com/nlohmann/json). Если на принимающей стороне JavaScript программист, он будет вам очень благодарен (да и не только JavaScript программист). Также, как программисту из типизированного языка, рекомендую использовать схемы для проверки json. Как альтернативу json можно взять messagepack, который "как json", но компактнее. Если нужно ещё компактнее, можно пожать передаваемую строку с помощью zlib например. Для всех вариантов также надо учитывать, что передавать указатели бессмысленно, т.к. на принимающей стороне они будут указывать неизвестно куда. Также понимать тонкости передачи данных по сети. К примеру данные, отправленные по UDP, могут не дойти до получателя, данные отправленные по TCP могут быть фрагментированы или склеены с соседними при получении и т.п. Возможно стоит подумать о готовых сетевых библиотеках, например RakNet, которая включает в себя практически все для построения мультиплеерной игры.

Ответ 2



В функциях отправки данных на другой сокет (например, send) и функциях приема данных (например, recv) одним из параметров всегда является указатель на буфер с этими данными (байтами). Необходимо предварительно сформировать этот буфер. Сделать это можно очень разными способами. Например, если структура передаваемых данных динамическая и/или таких структур очень много, то можно формировать буфер, так сказать, "на лету". Т.е. мы нужные данные постепенно, по мере их получения, запихиваем в буфер. std::string buffer; uint32_t i32 = 0x32fe56ad; float f = 1.0; std::string str = "1234"; uint8_t sz = str.size(); buffer.append((char*)&i32, sizeof(i32)); buffer.append((char*)&f, sizeof(f)); buffer.append((char*)&sz, sizeof(sz)); buffer.append(str); std::cout << "lenght message: " <

Ответ 3



Правильным подходом будет использование сериализации данных. Например: Protocol Buffers, JSON, XML, ASN.1, и т.п. Сравнительная таблица.

пятница, 10 января 2020 г.

Можно ли присоединить пользователя к другому tcp соединению, используя текущее?

#linux #tcp


Ситуация такая, что пользователь подключается к определенному адресу и порту, и я
не могу управлять его клиентом-программой, то есть повторно ещё одно соединение сделать,
потому что разорвется текущее соединение, и ему снова вручную придется подключится
уже к новому адресу, а мне нужно, чтобы он не отключался, но при этом сначала был подсоединен
к одному серверу, а потом, после того как получил с него данные, присоединился к другому,
так чтобы для него это было незаметно.
Как вариант можно сделать прокси и уже с него направлять трафик то туда, то сюда.
Но вариант с прокси не устраивает, потому что канал прокси перегрузится, а канал остальных
серверов будет почти пустовать.
Не знаю, доступно ли объяснил ситуацию. Сейчас только начинаю знакомство с сетями,
чтобы решить потом эту проблему, но даже без знаний с самого начала не понимал, как
можно иначе сделать, кроме как через прокси. Получается, клиент установил соединение
с ip сервера и портом, и, используя это соединение, я не могу сказать машине клиента,
чтобы она присоединилась к другому серверу?    


Ответы

Ответ 1



ответ из комментария: Насколько мне известно -- нет. Я знаю, что можно передать открытый сокет (файловый дескриптор) от одного процесса другому на том же хосте (через sendmsg()), но это отличается от того, что Вы описываете. По сути Вы хотите динамически управлять подменой dst-адреса установленного tcp соединения у клиента.

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

диагностика с помощью telnet

#сеть #tcp #faq #telnet


как при помощи программы telnet проверить, доступен ли указанный tcp-порт на указанной
машине, и слушает ли этот порт какая-нибудь программа?



приветствуются канонические ответы с примерами для разных операционных систем и реализаций
программы telnet.
    


Ответы

Ответ 1



unix-подобные операционные системы вероятно, чаще всего здесь можно встретить bsd- и gnu-реализации: netkit-telnet и inetutils-telnet. для указанной диагностики разница между ними несущественна. пример неудачного подключения к порту 12345 на каком-нибудь из серверов, в ip-адрес которого резолвится имя github.com: $ telnet github.com 12345 Trying 192.30.253.113... прервать неудачную попытку можно с помощью ctrl+c пример удачного подключения к тому же серверу на порт 22: $ telnet 192.30.253.113 22 Trying 192.30.253.113... Connected to 192.30.253.113. Escape character is '^]'. SSH-2.0-libssh-0.7.0 последняя строчка здесь — это уже информация от программы, слушающей 22 порт на опрашиваемом сервере. если соединение сразу же не было разорвано слушающей программой (это зависит от её реализации и настроек), то прервать telnet-сессию можно, нажав предложенную программой комбинацию ctrl+], а затем клавишу enter, после чего появится приглашение программы telnet: telnet> в котором можно ввести команду q (или полностью — quit) и нажать клавишу enter. Microsoft Windows Действия практически идентичны варианту для unix-подобных ОС. В Windows имеется встроенный telnet-клиент, но он может быть отключен. Чтобы его включить нужно выполнить шаги, описанные на сайте Microsoft. Когда всё готово, запустить telnet-клиент можно так: Пуск - Выполнить - telnet - ОК В открывшемся консольном окне будут видны следующие строки: Welcome to Microsoft Telnet Client Escape Character is 'CTRL+]' Microsoft Telnet> Чтобы попытаться открыть тот или иной адрес и порт нужно написать букву o, а затем адрес и порт. Например: Microsoft Telnet> o github.com 12345

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

Асинхронный Tcp клиент-сервер

#c_sharp #асинхронность #tcp


Помогите, пожалуйста, с пониманием асинхронной реализации Tcp клиент-сервера. Читал
статьи на MDSN, гуглил. Но эта куча BeginWrite, BeginRead, коллбэков просто выносит мозг.


Правда ли то, что несмотря на задающийся размер буфера в BeginRead, могут прийти
меньше или больше данных? И как тогда с этим справляться?


Как вообще эффективно реализовать простейший клиент-сервер, если первыми двумя байтами
идет "код действия", а затем произвольное кол-во байт, в которых содержатся строки,
int, ushort и т.д., если часть пакета может где-то застрять или прийти излишек?


С синхронной реализацией все ок, но ест много ЦП т.к. цикл while.    


Ответы

Ответ 1



Сейчас принято делать всю асинхронность через async/await. Вот несколько примеров: [1], [2], я надёргаю кусков из них. Сервер слушает входящие сообщения, и при приходе запускает на обработку. Обработка бежит параллельно, а сервер продолжает слушать дальше в цикле (AcceptTcpClientAsync() и дальше). Получится что-то такое: Весь сервер: void RunServer() { var tcpListener = TcpListener.Create(<порт>); tcpListener.Start(); while (true) // тут какое-то разумное условие выхода { var tcpClient = await tcpListener.AcceptTcpClientAsync(); processClientTearOff(tcpClient); // await не нужен } } async Task processClientTearOff(TcpClient c) { using (var client = new Client(c)) await client.ProcessAsync(); } Обработчик одного клиентского запроса: class Client : IDisposable { NetworkStream s; public Client(TcpClient c) { s = client.GetStream(); } public void Dispose() { s.Dispose(); } async Task ReadFromStreamAsync(int nbytes) { var buf = new byte[nbytes]; var readpos = 0; while (readpos < nbytes) readpos += await s.ReadAsync(buf, readpos, nbytes - readpos); return buf; } public async Task ProcessAsync() { var actionBuffer = await ReadFromStreamAsync(2); var action = (ActionEnum)BitConverter.ToInt16(actionBuffer, 0); switch (action) { // логика в зависимости от кода команды } } } Если сервер надо останавливать, вам придётся дождаться окончания работы запущенных Task'ов: async void RunServer() { var tcpListener = TcpListener.Create(<порт>); tcpListener.Start(); while (можно продолжать) { var tcpClient = await tcpListener.AcceptTcpClientAsync(); processClient(tcpClient); // await не нужен } await Task.WaitAll(activeClientTasks.ToList()); // нужна копия } HashSet activeClientTasks = new HashSet(); async Task processClient(TcpClient c) { using (var client = new Client(c)) { Task task = null; try { task = client.ProcessAsync(); activeClientTasks.Add(task); await task; } finally { if (task != null) activeClientTasks.Remove(task); } } } Вот реализация таймаута (набросал, код не запускал, возможны ошибки): async Task ReadFromStreamAsync(int nbytes, CancellationToken ct) { var buf = new byte[nbytes]; var readpos = 0; while (readpos < nbytes) readpos += await s.ReadAsync(buf, readpos, nbytes - readpos, ct); return buf; } async Task ReadWithTimeout(int n) { using (var cts = new CancellationTokenSource()) { var readTask = ReadFromStreamAsync(n, cts.Token); var timeoutTask = Task.Delay(1000); await Task.WhenAny(readTask, timeoutTask); if (!readTask.IsCompleted) { cts.Cancel(); // cancel read task return null; } else { var bytes = readTask.Result; return Decode(bytes); } } } Обновление: Следуя совету @Pavel Mayorov, последний метод можно переписать проще и изящнее: async Task ReadWithTimeout(int n) // (*) { using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1000))) { try { return Decode(await ReadFromStreamAsync(n, cts.Token)); } catch (OperationCanceledException) { return null; } } } Обновление: К сожалению, несмотря свою изящность, метод с обрывом чтения (я имею в виду вариант, обозначенный (*)) не работает из-за бага в BCL. Я попробую уточнить код. Кажется, хорошей идеей является закрыть клиент полностью, в соответствии со старой семантикой WinAPI. Вот сообщение на Microsoft Connect, проголосуйте за исправление! Обновление: Поправил код, руководствуясь предыдущим обновлением. Теперь при обрыве по таймауту возвращается не null, а бросается TimeoutException (после которого клиент нужно закрывать). async Task ReadWithTimeout(int n) { using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1000))) { var token = cts.Token; try { // при обрыве ожидания по токену закрываем клиент // с выбросом ObjectDisposedException using (token.Register(client.Close)) return Decode(await ReadFromStreamAsync(n, cts.Token)); } catch (ObjectDisposedException) when (token.IsCancellationRequested) { throw new TimeoutException(); } } }

воскресенье, 1 декабря 2019 г.

Протокол для многопользовательской игры

#cpp #websocket #tcp #udp #протоколы


Добрый день,

проект - многопользовательская игра, расчитаная на множество игроков (до 100), наподобие
agar.io и тд. Сервер на C++, интерфейс - JavaScript (всё происходит внутри браузера).
Стоит вопрос, как реализовывать передачу данных. 

Идеи :


TCP - WebSocket
UDP - разрабатывающийся "протокол" netcode.io (но ввиду того, что портируемость только
на Windows, идея отброшена)


Уважаемые пользователи, может вы можете подсказать, как лучше сие реализовывать.
А если уж WebSocket предлагаете, то какую библиотеку для него использовать в C++?
    


Ответы

Ответ 1



Ну с протоколом вы уже определились в своем же вопросе. Как лучше "сие" реализовать: Для вас идеальный вариант (если это js клиент и c++ server и до 100 юзеров): Библиотека: socket.io Пример сервера на c++: c++ socket.io server При правильном подходе и железе такой сервер выдержит не одну тысячу пользователей онлайн.

Ответ 2



Привет, Вам подойдет Poco. Там есть реализация WebSocket и JSON - очень удобно. Для обеспечения хорошей скорости, желательно разбивать информацию на маленькие сообщения. Например, организовать связь между взаимосвязанными объектами на сервере и на странице, а WebSocket соединение использовать как роутер между ними.

суббота, 13 июля 2019 г.

BinaryReader есть ли timeout?

Есть ли способ, как можно установить время ожидания для BinaryReader?
Когда неожиданно происходит разрыв соединения, а затем его восстановление, программа-клиент шлёт данные, а вот серверная часть на BinaryReader.Read зависает и программа не идёт дальше этой строки. Как только все клиенты отсоединяются, программа оживает и начинает всё подгружать, как-будто из кэша какого-то и в конечном итоге выдаёт исключение о том, что удаленный клиент внезапно разорвал соединение.


Ответ

Установить таймаут для самого BinaryReader'а нельзя, но можно установить таймаут для TcpClient'а, котоорый поставляет ему несущий поток.
Для этого имеет смысл воспользоваться параметром TcpClient.ReceiveTimeout до запроса NetworkStream'а.
Ответ честно украден отсюда

четверг, 6 июня 2019 г.

Замена данных в TCP пакете

В общем, есть два устройства. Одно отсылает пакеты, а другое, соответственно, их принимает, изменяет и дальше отправляет в сеть.
И тут еще суть в том, что нужно оставить заголовки пакета, окончание, а содержимое заменить на другое.
То есть что получается: мне нужно поднять сервер, получить пакет, убрать из него содержимое и вставить своё. Отправить пакет дальше. Даже схемка есть:
Вот - N - это количество "D".

Не понимаю, с чего начинать, т.к. с этим сталкиваюсь в первый раз. И хотелось бы понять, что нарисовано на картинке.


Ответ

Тут вроде ничего сложного. На картинке показана структура 2-х пакетов: Сообщение, которое к вам приходит. Сообщение, которое вы должны отослать. По каждому сообщению подробно расписана структура следования байтов (Б1, Б2 и т.д.), также показано, какую часть и куда нужно переносить. Тело исходного сообщения состоит из массива данных структуры "D", каждый элемент по 6 байт. Но я сейчас какие-то прописные истины перечислил)), которые вы и сами, наверное, поняли) Но вот что мне не понятно по этому рисунку: Во-первых, "Измерительное сообщение 98 байт максимум" - следовательно, пакет нефиксированной длины, а как мы узнаем об окончании пакета? + не хватает 10 байт в описании, максимум 98, а на картинке расписано только 88 байт. Может, еще есть какая-нибудь информация? Во-вторых, тело исходного сообщения на картинке 84 байта, а уместить его нужно в 103 байта, с ним производятся какие-то манипуляции? Или нужно добивать нулями до определенной длинны? =============================================== Все еще остаются вопросы) вот смотрите примерный алгоритм На вашем сервере(на рис - прибор 2), считываем поток байт, для начала берем первый байт и сверяем заголовок, судя по рисунку заголовок всегда 23 если да то продолжаем читать, если нет то нужно больше информации о других пакетах и дальнейших действиях Теперь когда мы поняли что за пакет, нам нужно узнать остальные данные, для этого нужно понять длинну пакета, и тут опять непонятно как ее вычислить она всегда фиксирована и равна = 113 байт ( тогда вам нужно считать еще 110 байт + crc(2 байта) ), или "N" - это количество D, как говорил @avp, тогда вам нужно считать еще один байт, будем считать что это count, тогда формула будет такая count * 6 + crc( 2 байта ) Когда вы получили тело сообщения, нужно сверить crc ( последние 2 байта), но тут не написано для какой части сообщения его сверять? Если crc совпал то теперь можно формировать новый пакет, но как его формировать тоже не совсем понятно

понедельник, 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)

пятница, 19 апреля 2019 г.

Какие типы данных и в каком виде можно передавать через TCP bsd socket?

Во всех примерах использования bsd сокетов на си для передачи и приема информации используется массив символов фиксированной длинны char buff[255]. Например read(socketFD, buff, sizeof(buff));. Возникает пара вопросов:
Обязательно ли использовать массив char, или же можно передавать произвольные объекты? например можно ли передать double buff[255] или даже struct someStructType buff[255]? Что будет, если отправить массив например в 200 элементов, а на принимающей стороне считать только 100? При повторном чтении мы прочитаем те же элементы, или же следующие 100 элементов?


Ответ

В сокет передаются исключительно байты, что бы ваши объекты превратить в байты или создать их из них нужно использовать протоколы сериализации, такие как например T-L-V или ProtoBuf. В примерах приводится массив-буфер для считывания в него информации, как правило функция чтения возвращает реальное количество считанных байт. Если вы отправите 200 байт, а считаете 100, то вам надо будет считать потом еще 100. Буфер может быть любого размера (но как правило его делают не более MTU/MRU). Как правило при обмене данными через сеть, они упаковываются в пакет вида: packetLength:packetData, что позволяет гарантированно считывать переданный пакет. Так, например, отправляя 200 байт нужной информации вы:
Считываете в буфер приема данные из сокета. Читаете из этого буфера длину пакета packetLenght (сколько байт - зависит от типа переменной и архетиктруры); Создаете буфер размером packetLength. Это не тот же самый буфер в который вы читаете из сокета!! Копируете из буфера приема данные в созданный буфер, при этом если размер данных меньше, чем осталось в буфере, то копируете нужно кол-во, если больше, то пишите все, а затем дочитываете далее из сокета в буфер приема до тех пор, пока не вытащите все.

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

Что происходит когда сокет записывает данные в то время, когда с другой стороны читаются предыдущие данные?

К примеру у меня есть сокет, который в цикле (2 итерации к примеру) записывает данные подряд. При этом на другом конце (где этот сокет прослушивается) в это время происходит считывание. Так вот, что будет когда происходит вторая попытка записи данных в сокет, когда на другом конце еще считывается первая часть пересланых данных?


Ответ

Если сокет блокирующий и на чтение запрошено больше данных, чем Вы отправляете - будет ждать "второй итерации". Если сокет неблокирующий - вычитает, сколько сможет, и затем Вам снова придется опрашивать сокет.
В связи с комментарием @VTT хочу дать немного более развернутый ответ.

Если вызывается одна из функций read/recv/recvfrom для блокируемого сокета и при этом в буфере нет никаких данных - сокет переходит в спящее состояние до тех пор, пока не придут какие-либо данные. Достаточно одного байта.
Тем не менее, мы можем задать флаг MSG_WAITALL - в этом случае мы будем ждать до тех пор, пока не будет доступно фиксированное (запрошенное нами) количество байт.
В случае неблокируемого сокета - если не удовлетворены условия ввода - то ф-ия вернет управление установив ошибку EWOULDBLOCK

Если вызывается одна из функций write/send/sendto - данные копируются из буфера приложения в буфер отправки сокета. Для блокируемого сокета - если в буфере отправки недостаточно места, процесс переходит в состоянии оидания до тех пор, пока это самое место не освободится.
Для неблокируемого сокета - если в буфере отправки недостаточно места - ф-ия вернет управление, установив ошибку EWOULDBLOCK

Комбинируйте и предполагайте наиболее вероятный результат того, чем же все-таки окончится посылка двух сообщений :)

четверг, 29 ноября 2018 г.

диагностика с помощью telnet

как при помощи программы telnet проверить, доступен ли указанный tcp-порт на указанной машине, и слушает ли этот порт какая-нибудь программа?

приветствуются канонические ответы с примерами для разных операционных систем и реализаций программы telnet


Ответ

unix-подобные операционные системы
вероятно, чаще всего здесь можно встретить bsd- и gnu-реализации: netkit-telnet и inetutils-telnet. для указанной диагностики разница между ними несущественна.

пример неудачного подключения к порту 12345 на каком-нибудь из серверов, в ip-адрес которого резолвится имя github.com
$ telnet github.com 12345 Trying 192.30.253.113...
прервать неудачную попытку можно с помощью ctrl+c

пример удачного подключения к тому же серверу на порт 22
$ telnet 192.30.253.113 22 Trying 192.30.253.113... Connected to 192.30.253.113. Escape character is '^]'. SSH-2.0-libssh-0.7.0
последняя строчка здесь — это уже информация от программы, слушающей 22 порт на опрашиваемом сервере.
если соединение сразу же не было разорвано слушающей программой (это зависит от её реализации и настроек), то прервать telnet-сессию можно, нажав предложенную программой комбинацию ctrl+], а затем клавишу enter, после чего появится приглашение программы telnet
telnet>
в котором можно ввести команду q (или полностью — quit) и нажать клавишу enter
Microsoft Windows
Действия практически идентичны варианту для unix-подобных ОС. В Windows имеется встроенный telnet-клиент, но он может быть отключен. Чтобы его включить нужно выполнить шаги, описанные на сайте Microsoft
Когда всё готово, запустить telnet-клиент можно так:
Пуск - Выполнить - telnet - ОК
В открывшемся консольном окне будут видны следующие строки:
Welcome to Microsoft Telnet Client Escape Character is 'CTRL+]' Microsoft Telnet>
Чтобы попытаться открыть тот или иной адрес и порт нужно написать букву o, а затем адрес и порт. Например:
Microsoft Telnet> o github.com 12345