Как пишется сетевой протокол

Вы в жизни не раз сталкивался с разными протоколами — одни использовал, другие, возможно, реверсил. Одни были легко читаемы, в других без hex-редактора не разобраться. В сегодняшней статье я покажу, как создать свой собственный сетевой протокол передачи данных, который будет работать поверх TCP/IP. Мы разработаем свою структуру данных и реализуем сервер на C#.

Итак, протокол передачи данных — это соглашение между приложениями о том, как должны выглядеть передаваемые данные. Например, сервер и клиент могут использовать WebSocket в связке с JSON. Вот так приложение на Android могло бы запросить погоду с сервера:

{

    «request»: «getWeather»,

    «city»: «cityname»

}

И сервер мог бы ответить:

{

    «success»: true,

    «weatherHumanReadable»: «Warm»,

    «degrees»: 18

}

Пропарсив ответ по известной модели, приложение предоставит информацию пользователю. Выполнить парсинг такого пакета можно, только располагая информацией о его строении. Если ее нет, протокол придется реверсить.

Содержание

  1. Создание базовой структуры протокола
  2. Написание клиента и сервера
  3. Запись и считывание данных из пакетов
  4. Установка значения
  5. Проверка на работоспособность
  6. Ввод типов пакетов
  7. Создание структуры пакетов для их сериализации и десериализации
  8. Создание сериализатора
  9. Создание десериализатора
  10. Первое рукопожатие
  11. Имплементация простой защиты протокола
  12. Заключение

Создание базовой структуры протокола

Этот протокол будет базовым для простоты. Но мы будем вести его разработку с расчетом на то, что впоследствии его расширим и усложним.

Первое, что необходимо ввести, — это наш собственный заголовок, чтобы приложения могли отличать пакеты нашего сетевого протокола. У нас это будет набор байтов
0xAF,
0xAA,
0xAF. Именно они и будут стоять в начале каждого сообщения.

Почти каждый бинарный протокол имеет свое «магическое число» (также «заголовок» и «сигнатура») — набор байтов в начале пакета. Оно используется для идентификации пакетов своего протокола. Остальные пакеты будут игнорироваться.

Каждый пакет будет иметь тип и подтип и будет размером в байт. Так мы сможем создать 65 025 (255 * 255) разных типов пакетов. Пакет будет содержать в себе поля, каждое со своим уникальным номером, тоже размером в один байт. Это предоставит возможность иметь 255 полей в одном пакете. Чтобы удостовериться в том, что пакет дошел до приложения полностью (и для удобства парсинга), добавим байты, которые будут сигнализировать о конце пакета.

Завершенная структура пакета:

XPROTOCOL PACKET STRUCTURE

(offset: 0) HEADER (3 bytes) [ 0xAF, 0xAA, 0xAF ]

(offset: 3) PACKET ID

  (offset: 3) PACKET TYPE (1 byte)

  (offset: 4) PACKET SUBTYPE (1 byte)

(offset: 5) FIELDS (FIELD[])

(offset: END) PACKET ENDING (2 bytes) [ 0xFF, 0x00 ]

FIELD STRUCTURE

(offset: 0) FIELD ID (1 byte)

(offset: 1) FIELD SIZE (1 byte)

(offset: 2) FIELD CONTENTS

Назовем наш протокол передачи данных, как вы могли заметить, XProtocol. На третьем сдвиге начинается информация о типе пакета. На пятом начинается массив из полей. Завершающим звеном будут байты
0xFF и
0x00, закрывающие пакет.

Написание клиента и сервера

Для начала нужно ввести основные свойства, которые будет иметь пакет:

  1. тип пакета;
  2. подтип;
  3. набор полей.

public class XPacket

{

    public byte PacketType { get; private set; }

    public byte PacketSubtype { get; private set; }

    public List<XPacketField> Fields { get; set; } = new List<XPacketField>();

}

Добавим класс для описания поля пакета, в котором будут его данные, ID и размер.

public class XPacketField

{

    public byte FieldID { get; set; }

    public byte FieldSize { get; set; }

    public byte[] Contents { get; set; }

}

Сделаем обычный конструктор приватным и создадим статический метод для получения нового экземпляра объекта.

private XPacket() {}

public static XPacket Create(byte type, byte subtype)

{

    return new XPacket

    {

        PacketType = type,

        PacketSubtype = subtype

    };

}

Теперь можно задать тип пакета и поля, которые будут внутри него. Создадим функцию для этого. Записывать будем в поток
MemoryStream. Первым делом запишем байты заголовка, типа и подтипа пакета, а потом отсортируем поля по возрастанию
FieldID.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public byte[] ToPacket()

{

    var packet = new MemoryStream();

    packet.Write(

    new byte[] {0xAF, 0xAA, 0xAF, PacketType, PacketSubtype}, 0, 5);

    var fields = Fields.OrderBy(field => field.FieldID);

    foreach (var field in fields)

    {

        packet.Write(new[] {field.FieldID, field.FieldSize}, 0, 2);

        packet.Write(field.Contents, 0, field.Contents.Length);

    }

    packet.Write(new byte[] {0xFF, 0x00}, 0, 2);

    return packet.ToArray();

}

Теперь запишем все поля. Сначала пойдет ID поля, его размер и данные. И только потом конец пакета —
0xFF,
0x00.

Теперь пора научиться парсить пакеты.

Минимальный размер пакета — 7 байт.

HEADER (3) +
TYPE (1) +
SUBTYPE (1) +
PACKET ENDING (2)

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public static XPacket Parse(byte[] packet)

{

    if (packet.Length < 7)

    {

        return null;

    }

    if (packet[0] != 0xAF ||

        packet[1] != 0xAA ||

        packet[2] != 0xAF)

    {

        return null;

    }

    var mIndex = packet.Length 1;

    if (packet[mIndex 1] != 0xFF ||

        packet[mIndex] != 0x00)

    {

        return null;

    }

    var type = packet[3];

    var subtype = packet[4];

    var xpacket = Create(type, subtype);

    /* <—> */

Пора перейти к парсингу полей. Так как наш пакет заканчивается двумя байтами, мы можем узнать, когда закончились данные для парсинга. Получим ID поля и его размер, добавим к списку. Если пакет будет поврежден и будет существовать поле с
ID, равным нулю, и
SIZE, равным нулю, то необходимости его парсить нет.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

    /* <—> */

    var fields = packet.Skip(5).ToArray();

    while (true)

    {

        if (fields.Length == 2)

        {

            return xpacket;

        }

        var id = fields[0];

        var size = fields[1];

        var contents = size != 0 ?

        fields.Skip(2).Take(size).ToArray() : null;

        xpacket.Fields.Add(new XPacketField

        {

            FieldID = id,

            FieldSize = size,

            Contents = contents

        });

        fields = fields.Skip(2 + size).ToArray();

    }

}

У кода выше есть проблема: если подменить размер одного из полей, парсинг завершится с необработанным исключением или пропарсит пакет неверно. Необходимо обеспечить безопасность пакетов. Но об этом поговорим чуть позже.

Запись и считывание данных из пакетов

Из-за строения класса
XPacket нужно хранить бинарные данные для полей. Чтобы установить значение поля, нам необходимо конвертировать имеющиеся данные в массив байтов. Язык C# не предоставляет идеальных способов сделать это, поэтому внутри пакетов будут передаваться только базовые типы:
int,
double,
float и так далее. Так как они имеют фиксированный размер, можно считать его напрямую из памяти.

РЕКОМЕНДУЕМ:
Программирование в консоли

Чтобы получить чистые байты объекта из памяти, иногда используется метод небезопасного кода и указателей, но есть и способы проще: благодаря классу
Marshal в C# можно взаимодействовать с
unmanaged-областями нашего приложения. Чтобы перевести любой объект фиксированной длины в байты, мы будем пользоваться такой функцией:

public byte[] FixedObjectToByteArray(object value)

{

    var rawsize = Marshal.SizeOf(value);

    var rawdata = new byte[rawsize];

    var handle = GCHandle.Alloc(rawdata,

        GCHandleType.Pinned);

    Marshal.StructureToPtr(value,

        handle.AddrOfPinnedObject(),

        false);

    handle.Free();

    return rawdata;

}

Здесь мы делаем следующее:

  • получаем размер нашего объекта;
  • создаем массив, в который будет записана вся информация;
  • получаем дескриптор на наш массив и записываем в него объект.

Теперь сделаем то же самое, только наоборот.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

private T ByteArrayToFixedObject<T>(byte[] bytes) where T: struct

{

    T structure;

    var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

    try

    {

        structure = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));

    }

    finally

    {

        handle.Free();

    }

    return structure;

}

Только что вы научились превращать объекты в массив байтов и обратно. Сейчас можно добавить функции для установки и получения значений полей. Давайте сделаем функцию для простого поиска поля по его ID.

public XPacketField GetField(byte id)

{

    foreach (var field in Fields)

    {

        if (field.FieldID == id)

        {

            return field;

        }

    }

    return null;

}

Добавим функцию для проверки существования поля.

public bool HasField(byte id)

{

    return GetField(id) != null;

}

Получаем значение из поля.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public T GetValue<T>(byte id) where T : struct

{

    var field = GetField(id);

    if (field == null)

    {

        throw new Exception($«Field with ID {id} wasn’t found.»);

    }

    var neededSize = Marshal.SizeOf(typeof(T));

    if (field.FieldSize != neededSize)

    {

        throw new Exception($«Can’t convert field to type {typeof(T).FullName}.n» + $«We have {field.FieldSize} bytes but we need exactly {neededSize}.»);

    }

    return ByteArrayToFixedObject<T>(field.Contents);

}

Добавив несколько проверок и используя уже известную нам функцию, превратим набор байтов из поля в нужный нам объект типа
T.

Установка значения

Мы можем принять только объекты
ValueType. Они имеют фиксированный размер, поэтому мы можем их записать.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public void SetValue(byte id, object structure)

{

    if (!structure.GetType().IsValueType)

    {

        throw new Exception(«Only value types are available.»);

    }

    var field = GetField(id);

    if (field == null)

    {

        field = new XPacketField

        {

            FieldID = id

        };

        Fields.Add(field);

    }

    var bytes = FixedObjectToByteArray(structure);

    if (bytes.Length > byte.MaxValue)

    {

        throw new Exception(«Object is too big. Max length is 255 bytes.»);

    }

    field.FieldSize = (byte) bytes.Length;

    field.Contents = bytes;

}

Проверка на работоспособность

Проверим создание пакета, его перевод в бинарный вид и парсинг назад.

var packet = XPacket.Create(1, 0);

packet.SetValue(0, 123);

packet.SetValue(1, 123D);

packet.SetValue(2, 123F);

packet.SetValue(3, false);

var packetBytes = packet.ToPacket();

var parsedPacket = XPacket.Parse(packetBytes);

Console.WriteLine($«int: {parsedPacket.GetValue<int>(0)}n» +

                  $«double: {parsedPacket.GetValue<double>(1)}n» +

                  $«float: {parsedPacket.GetValue<float>(2)}n» +

                  $«bool: {parsedPacket.GetValue<bool>(3)}»);

Судя по всему, все работает прекрасно. В консоли должен появиться результат.

int: 123

double: 123

float: 123

bool: False

Ввод типов пакетов

Запомнить ID всех пакетов, которые будут созданы, сложно. Отлаживать пакет с типом
N и подтипом
Ns не легче, если не держать все ID в голове. В этом разделе мы дадим нашим пакетам имена и привяжем эти имена к ID пакета. Для начала создадим перечисление, которое будет содержать имена пакетов.

public enum XPacketType

{

    Unknown,

    Handshake

}

Unknown будет использоваться для типа, который нам неизвестен.
Handshake — для пакета рукопожатия.

Теперь, когда нам известны типы пакетов, пора привязать их к ID. Необходимо создать менеджер, который будет этим заниматься.

public static class XPacketTypeManager

{

    private static readonly Dictionary<XPacketType, Tuple<byte, byte>> TypeDictionary = new Dictionary<XPacketType, Tuple<byte, byte>>();

    /* < … > */

}

Статический класс хорошо подойдет для этой функции. Его конструктор вызывается лишь один раз, что позволит нам зарегистрировать все известные типы пакетов. Невозможность вызвать статический конструктор извне поможет не проходить повторную регистрацию типов.

РЕКОМЕНДУЕМ:
Обработка сложных форм на Python с помощью WTForms

Dictionary<TKey, TValue> хорошо подходит для этой задачи. Поместим тип (
XPacketType) как ключ,
Tuple<T1, T2> будет хранить в себе значение типа и подтипа пакета:
T1 — тип,
T2 — подтип.

Создаем функцию для регистрации типов пакета.

public static void RegisterType(XPacketType type, byte btype, byte bsubtype)

{

    if (TypeDictionary.ContainsKey(type))

    {

        throw new Exception($«Packet type {type:G} is already registered.»);

    }

    TypeDictionary.Add(type, Tuple.Create(btype, bsubtype));

}

Имплементируем получение информации по типу:

public static Tuple<byte, byte> GetType(XPacketType type)

{

    if (!TypeDictionary.ContainsKey(type))

    {

        throw new Exception($«Packet type {type:G} is not registered.»);

    }

    return TypeDictionary[type];

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public static XPacketType GetTypeFromPacket(XPacket packet)

{

    var type = packet.PacketType;

    var subtype = packet.PacketSubtype;

    foreach (var tuple in TypeDictionary)

    {

        var value = tuple.Value;

        if (value.Item1 == type && value.Item2 == subtype)

        {

            return tuple.Key;

        }

    }

    return XPacketType.Unknown;

}

Создание структуры пакетов для их сериализации и десериализации

Чтобы не парсить все ручками, обратимся к сериализации и десериализации классов. Для этого надо создать класс и расставить атрибуты. Все остальное код сделает самостоятельно; потребуется только атрибут с информацией о том, с какого поля писать и читать.

[AttributeUsage(AttributeTargets.Field)]

public class XFieldAttribute : Attribute

{

    public byte FieldID { get; }

    public XFieldAttribute(byte fieldId)

    {

        FieldID = fieldId;

    }

}

Используя
AttributeUsage, мы установили, что наш атрибут можно будет установить только на поля классов.
FieldID будет использоваться для хранения ID поля внутри пакета.

Создание сериализатора

Для сериализации и десериализации в C# используется
Reflection. Этот набор классов позволит узнать всю нужную информацию и установить значение полей во время рантайма.

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

private static List<Tuple<FieldInfo, byte>> GetFields(Type t)

{

    return t.GetFields(BindingFlags.Instance |

                       BindingFlags.NonPublic |

                       BindingFlags.Public)

    .Where(field => field.GetCustomAttribute<XFieldAttribute>() != null)

    .Select(field => Tuple.Create(field, field.GetCustomAttribute<XFieldAttribute>().FieldID))

    .ToList();

}

Так как необходимые поля помечены атрибутом
XFieldAttribute, найти их внутри класса не составит труда. Сначала получим все нестатичные, приватные и публичные поля при помощи
GetFields(). Выбираем все поля, у которых есть наш атрибут. Собираем новый
IEnumerable, который содержит
Tuple<FieldInfo, byte>, где
byte — ID нашего поля в пакете.

Здесь мы вызываем
GetCustomAttribute<>() два раза. Это не обязательно, но таким образом код будет выглядеть аккуратнее.

Итак, теперь вы умеете получать все
FieldInfo для типа, который будете сериализовать. Пришло время создать сам сериализатор: у него будут обычный и строгий режимы работы. Во время обычного режима будет игнорироваться тот факт, что разные поля используют один и тот же ID поля внутри пакета.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public static XPacket Serialize(byte type, byte subtype, object obj, bool strict = false)

{

    var fields = GetFields(obj.GetType());

    if (strict)

    {

        var usedUp = new List<byte>();

        foreach (var field in fields)

        {

            if (usedUp.Contains(field.Item2))

            {

                throw new Exception(«One field used two times.»);

            }

            usedUp.Add(field.Item2);

        }

    }

    var packet = XPacket.Create(type, subtype);

    foreach (var field in fields)

    {

        packet.SetValue(field.Item2, field.Item1.GetValue(obj));

    }

    return packet;

}

Внутри
foreach происходит самое интересное:
fields содержит все нужные поля в виде
Tuple<FieldInfo, byte>.
Item1 — искомое поле,
Item2 — ID этого поля внутри пакета. Перебираем их все, следом устанавливаем значения полей при помощи
SetPacket(byte, object). Теперь пакет сериализован.

Создание десериализатора

Создавать десериализатор в разы проще. Нужно использовать функцию
GetFields(), которую мы имплементировали в прошлом разделе.

public static T Deserialize<T>(XPacket packet, bool strict = false)

{

    var fields = GetFields(typeof(T));

    var instance = Activator.CreateInstance<T>();

    if (fields.Count == 0)

    {

        return instance;

    }

    /* <—> */

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

    /* <—> */

    foreach (var tuple in fields)

    {

        var field = tuple.Item1;

        var packetFieldId = tuple.Item2;

        if (!packet.HasField(packetFieldId))

        {

            if (strict)

            {

                throw new Exception($«Couldn’t get field[{packetFieldId}] for {field.Name}»);

            }

            continue;

        }

        /* Очень важный костыль, который многое упрощает

         * Метод GetValue<T>(byte) принимает тип как type-параметр

         * Наш же тип внутри field.FieldType

         * Используя Reflection, вызываем метод с нужным type-параметром

         */

        var value = typeof(XPacket)

            .GetMethod(«GetValue»)?

            .MakeGenericMethod(field.FieldType)

            .Invoke(packet, new object[] {packetFieldId});

        if (value == null)

        {

            if (strict)

            {

                throw new Exception($«Couldn’t get value for field[{packetFieldId}] for {field.Name}»);

            }

            continue;

        }

        field.SetValue(instance, value);

    }

    return instance;

}

Создание десериализатора завершено. Теперь можно проверить работоспособность кода. Для начала создадим простой класс.

class TestPacket

{

    [XField(0)]

    public int TestNumber;

    [XField(1)]

    public double TestDouble;

    [XField(2)]

    public bool TestBoolean;

}

Напишем простой тест.

var t = new TestPacket {TestNumber = 12345,

                        TestDouble = 123.45D,

                        TestBoolean = true};

var packet = XPacketConverter.Serialize(0, 0, t);

var tDes = XPacketConverter.Deserialize<TestPacket>(packet);

if (tDes.TestBoolean)

{

    Console.WriteLine($«Number = {tDes.TestNumber}n» +

                      $«Double = {tDes.TestDouble}»);

}

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

Number = 12345

Double = 123,45

А теперь перейдем к тому, для чего все это создавалось.

Первое рукопожатие

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

Примеры работы с сокетами вы найдете в официальной документации в главе Socket Code Examples.

Мы создали простой пакет для обмена рукопожатиями.

public class XPacketHandshake

{

    [XField(1)]

    public int MagicHandshakeNumber;

}

Рукопожатие будет инициировать клиент. Он отправляет пакет рукопожатия с рандомным числом, а сервер в свою очередь должен ответить числом, на 15 меньше полученного.

Отправляем пакет на сервер.

var rand = new Random();

HandshakeMagic = rand.Next();

client.QueuePacketSend(

        XPacketConverter.Serialize(

            XPacketType.Handshake,

            new XPacketHandshake

            {

                MagicHandshakeNumber = HandshakeMagic

            }).ToPacket());

При получении пакета от сервера обрабатываем
handshake отдельной функцией.

private static void ProcessIncomingPacket(XPacket packet)

{

    var type = XPacketTypeManager.GetTypeFromPacket(packet);

    switch (type)

    {

        case XPacketType.Handshake:

            ProcessHandshake(packet);

            break;

        case XPacketType.Unknown:

            break;

        default:

            throw new ArgumentOutOfRangeException();

    }

}

Десериализуем, проверяем ответ от сервера.

private static void ProcessHandshake(XPacket packet)

{

    var handshake = XPacketConverter.Deserialize<XPacketHandshake>(packet);

    if (HandshakeMagic handshake.MagicHandshakeNumber == 15)

    {

        Console.WriteLine(«Handshake successful!»);

    }

}

На стороне сервера есть свой идентичный
ProcessIncomingPacket. Разберем процесс обработки пакета на стороне сервера. Десериализуем пакет рукопожатия от клиента, отнимаем пятнадцать, сериализуем и отправляем обратно.

private void ProcessHandshake(XPacket packet)

{

    Console.WriteLine(«Recieved handshake packet.»);

    var handshake = XPacketConverter.Deserialize<XPacketHandshake>(packet);

    handshake.MagicHandshakeNumber -= 15;

    Console.WriteLine(«Answering..»);

    QueuePacketSend(

        XPacketConverter.Serialize(XPacketType.Handshake, handshake)

            .ToPacket());

}

Собираем и проверяем.

создание сетевого протокола

Тестирование рукопожатия

Все работает!

Имплементация простой защиты протокола

Наш протокол будет иметь два типа пакетов — обычный и защищенный. У обычного наш стандартный заголовок, а у защищенного вот такой:
[0x95, 0xAA, 0xFF].

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

public bool Protected { get; set; }

После этого модифицируем функцию
XPacket.Parse(byte[]), чтобы она принимала и расшифровывала новые пакеты. Вначале модифицируем функцию проверки заголовка:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

var encrypted = false;

if (packet[0] != 0xAF ||

    packet[1] != 0xAA ||

    packet[2] != 0xAF)

{

    if (packet[0] == 0x95 ||

        packet[1] == 0xAA ||

        packet[2] == 0xFF)

    {

        encrypted = true;

    }

    else

    {

        return null;

    }

}

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

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

public static XPacket Parse(byte[] packet, bool markAsEncrypted = false)

Добавляем функциональность в цикл парсинга полей.

if (fields.Length == 2)

{

    return encrypted ? DecryptPacket(xpacket) : xpacket;

}

Так как мы принимаем только структуры как типы данных, мы не сможем записать
byte[] внутрь поля. Поэтому немного модифицируем код, добавив новую функцию, которая будет принимать массив данных.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public void SetValueRaw(byte id, byte[] rawData)

{

    var field = GetField(id);

    if (field == null)

    {

        field = new XPacketField

        {

            FieldID = id

        };

        Fields.Add(field);

    }

    if (rawData.Length > byte.MaxValue)

    {

        throw new Exception(«Object is too big. Max length is 255 bytes.»);

    }

    field.FieldSize = (byte) rawData.Length;

    field.Contents = rawData;

}

Сделаем такую же, но уже для получения данных из поля.

public byte[] GetValueRaw(byte id)

{

    var field = GetField(id);

    if (field == null)

    {

        throw new Exception($«Field with ID {id} wasn’t found.»);

    }

    return field.Contents;

}

Теперь все готово для создания функции расшифровки пакета. Шифрование будет использовать класс
RijndaelManaged со строкой в качестве пароля для шифрования. Строка с паролем будет константна. Это шифрование поможет защититься от атаки типа MITM.

Создадим класс, который будет шифровать и расшифровывать данные.

Так как процесс шифрования выглядит идентично, возьмем готовое решение для шифрования строки с Stack Overflow и адаптируем его для себя.

Модифицируем методы, чтобы они принимали и возвращали массивы байтов.

public static byte[] Encrypt(byte[] data, string passPhrase)

public static byte[] Decrypt(byte[] data, string passPhrase)

И простой хендлер, который будет хранить секретный ключ.

public class XProtocolEncryptor

{

    private static string Key { get; } = «2e985f930853919313c96d001cb5701f»;

    public static byte[] Encrypt(byte[] data)

    {

        return RijndaelHandler.Encrypt(data, Key);

    }

    public static byte[] Decrypt(byte[] data)

    {

        return RijndaelHandler.Decrypt(data, Key);

    }

}

Затем создаем функцию для расшифровки. Данные обязательно должны быть в поле с ID = 0. Как иначе нам его искать?

private static XPacket DecryptPacket(XPacket packet)

{

    if (!packet.HasField(0))

    {

        return null;

    }

    var rawData = packet.GetValueRaw(0);

    var decrypted = XProtocolEncryptor.Decrypt(rawData);

    return Parse(decrypted, true);

}

Получаем данные, расшифровываем и парсим заново. То же самое проделываем с обратной процедурой.

РЕКОМЕНДУЕМ:
Как сделать свою структуру данных в Python

Вводим свойство, чтобы пометить надобность в заголовке зашифрованного пакета.

private bool ChangeHeaders { get; set; }

Создаем простой пакет и помечаем, что в нем зашифрованные данные.

public static XPacket EncryptPacket(XPacket packet)

{

    if (packet == null)

    {

        return null;

    }

    var rawBytes = packet.ToPacket();

    var encrypted = XProtocolEncryptor.Encrypt(rawBytes);

    var p = Create(0, 0);

    p.SetValueRaw(0, encrypted);

    p.ChangeHeaders = true;

    return p;

}

И добавляем две функции для более удобного обращения.

public XPacket Encrypt()

{

    return EncryptPacket(this);

}

public XPacket Decrypt() {

    return DecryptPacket(this);

}

Модифицируем
ToPacket(), чтобы тот слушался значения
ChangeHeaders.

packet.Write(ChangeHeaders

    ? new byte[] {0x95, 0xAA, 0xFF, PacketType, PacketSubtype}

    : new byte[] {0xAF, 0xAA, 0xAF, PacketType, PacketSubtype},

    0, 5);

Проверяем:

var packet = XPacket.Create(0, 0);

packet.SetValue(0, 12345);

var encr = packet.Encrypt().ToPacket();

var decr = XPacket.Parse(encr);

Console.WriteLine(decr.GetValue<int>(0));

В консоли получаем число
12345.

Заключение

Только что мы создали свой собственный протокол. Это был долгий путь от базовой структуры на бумаге до его полной имплементации в коде. Надеюсь, вам было интересно!

Исходный код проекта можно найти в GitHub.

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (8 оценок, среднее: 4,25 из 5)

Загрузка…

Cтек протоколов TCP/IP широко распространен. Он используется в качестве основы для глобальной сети интернет. Разбираемся в основных понятиях и принципах работы стека.

Стек протоколов TCP/IP (Transmission Control Protocol/Internet Protocol, протокол управления передачей/протокол интернета) — сетевая модель, описывающая процесс передачи цифровых данных. Она названа по двум главным протоколам, по этой модели построена глобальная сеть интернет. Сейчас это кажется невероятным, но в 1970-х информация не могла быть передана из одной сети в другую. Чтобы обеспечить такую возможность, был разработан стек интернет-протоколов, известный как TCP/IP.

Разработка сетевой модели осуществлялась при содействии Министерства обороны США, поэтому иногда модель TCP/IP называют DoD (Department of Defence) модель. Если вы знакомы с моделью OSI, то вам будет проще понять построение модели TCP/IP, потому что обе модели имеют деление на уровни, внутри которых действуют определенные протоколы и выполняются собственные функции. Мы разделили статью на смысловые части, чтобы было проще понять, как устроена модель TCP/IP:

Сравнение моделей

Уровневая модель TCP/IP

Выше мы уже упоминали, что модель TCP/IP разделена на уровни, как и OSI, но отличие двух моделей в количестве уровней. Документами, определяющими сертификацию модели, являются RFC 1122 и RFC1123. Эти стандарты описывают четыре уровня абстракции модели TCP/IP: прикладной, транспортный, межсетевой и канальный. Существуют и другие версии описания модели, в том числе включающие иное количество уровней и их наименований. Однако в этой статье мы придерживаемся оригинальной версии и далее рассмотрим четыре уровня модели.

Канальный уровень (link layer)

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

Этот уровень также вычисляет максимальное расстояние, на которое пакеты возможно передать, частоту сигнала, задержку ответа и т.д. Все это — физические свойства среды передачи информации. На канальном уровне самым распространенным протоколом является Ethernet, который мы рассмотрим в конце статьи.

Межсетевой уровень (internet layer)

Глобальная сеть интернет состоит из множества локальных сетей, взаимодействующих между собой. Межсетевой уровень используется, чтобы описать обеспечение такого взаимодействия.

Межсетевое взаимодействие — это основной принцип построения интернета. Локальные сети по всему миру объединены в глобальную, а передачу данных между этими сетями осуществляют магистральные и пограничные маршрутизаторы.

Именно на межсетевом уровне функционирует протокол IP, позволивший объединить разные сети в глобальную. Как и протокол TCP, он дал название модели, рассматриваемой в статье.

Маска подсети и IP-адреса

IPv4 и IPv6

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

Протокол IP (Internet Protocol) используется маршрутизатором, чтобы определить, к какой подсети принадлежит получатель. Свой уникальный IP-адрес есть у каждого сетевого устройства, при этом в глобальной сети не может существовать два устройства с одинаковым IP. Протокол имеет две действующие версии, первая из которых — IPv4 (IP version 4, версии 4) — была описана в 1981 году.


IPv4 предусматривает назначение каждому устройству 32-битного IP-адреса, что ограничивало максимально возможное число уникальных адресов 4 миллиардами (2^32). В более привычном для человека десятичном виде IPv4 выглядит как четыре блока (октета) чисел от 0 до 255, разделенных тремя точками. Первый октет IP-адреса означает класс сети, классов всего 5: A, B, C, D, E. Gри этом адреса сети D являются мультикастовыми, а сети E вообще не используются.


Рассмотрим, например, IPv4 адрес класса С 223.135.100.7. Первые три октета определяют класс и номер сети, а последний означает номер конечного устройства. Например, если необходимо отправить информацию с компьютера номер 7 с IPv4 адресом 223.135.100.7 на компьютер номер 10 в той же подсети, то адрес компьютера получателя будет следующий: 223.135.100.10.

В связи с быстрым ростом сети интернет остро вставала необходимость увеличения числа возможных IP-адресов. В 1995 году впервые был описан протокол IPv6 (IP version 6, версии 6), который использует 128-битные адреса и позволяет назначить уникальные адреса для 2^128 устройств.

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

2dab:ffff:0000:0000:01aa:00ff:dd72:2c4a.

Так как IPv6 адреса длинные, их разрешается сокращать по определенным правилам, которые также описываются RFC:

  • Для написания адреса используются строчные буквы латинского алфавита: a, b, c, d, e, f.
  • Ведущие нули допускается не указывать — например, в адресе выше :00ff: можно записать как :ff:.
  • Группы нулей, идущие подряд, тоже допустимо сокращать и заменять на двойное двоеточие. На примере выше это выглядит так: 2dab:аааа::01aa:00ff:dd72:2c4a. Допускается делать не больше одного подобного сокращения в адресе IPv6 на наибольшей последовательности нулей. Если одинаково длинных последовательностей несколько — на самой левой из них.

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

IP способен инкапсулировать другие протоколы, предоставлять место, куда они могут быть встроены. К таким протоколам, например, относятся ICMP (межсетевой протокол управляющих сообщений) и IGMP (межсетевой протокол группового управления). Информация о том, какой протокол инкапсулируется, отражается в заголовке IP-пакета. Так, ICMP будет обозначен числом 1, а IGMP будет обозначен числом 2.

ICMP

ICMP в основном используется устройствами в сети для доставки сообщений об ошибках и операционной информации, сообщающей об успехе или ошибке при связи с другим устройством. Например, именно с использованием ICMP осуществляется передача отчетов о недоступности устройств в сети. Кроме того, ICMP используется при диагностике сети — к примеру, в эксплуатации утилит ping или traceroute.

ICMP не передает какие-либо данные, что отличает его от протоколов, работающих на транспортном уровне — таких как TCP и UDP. ICMP, аналогично IP, работает на межсетевом уровне и фактически является неотъемлемой частью при реализации модели TCP/IP. Стоит отметить, что для разных версий IP используются и разные версии протокола ICMP.

Транспортный уровень (transport layer)

Постоянные резиденты транспортного уровня — протоколы TCP и UDP, они занимаются доставкой информации.

TCP (протокол управления передачей) — надежный, он обеспечивает передачу информации, проверяя дошла ли она, насколько полным является объем полученной информации и т.д. TCP дает возможность двум конечным устройствам производить обмен пакетами через предварительно установленное соединение. Он предоставляет услугу для приложений, повторно запрашивает потерянную информацию, устраняет дублирующие пакеты, регулируя загруженность сети. TCP гарантирует получение и сборку информации у адресата в правильном порядке.

UDP (протокол пользовательских датаграмм) — ненадежный, он занимается передачей автономных датаграмм. UDP не гарантирует, что всех датаграммы дойдут до получателя. Датаграммы уже содержат всю необходимую информацию, чтобы дойти до получателя, но они все равно могут быть потеряны или доставлены в порядке отличном от порядка при отправлении.

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

Протоколы транспортного уровня не интерпретируют информацию, полученную с верхнего или нижних уровней, они служат только как канал передачи, но есть исключения. RSVP (Resource Reservation Protocol, протокол резервирования сетевых ресурсов) может использоваться, например, роутерами или сетевыми экранами в целях анализа трафика и принятия решений о его передаче или отклонении в зависимости от содержимого.

Прикладной уровень (application layer)

В модели TCP/IP отсутствуют дополнительные промежуточные уровни (представления и сеансовый) в отличие от OSI. Функции форматирования и представления данных делегированы библиотекам и программным интерфейсам приложений (API) — своего рода базам знаний, содержащим сведения о том, как приложения взаимодействуют между собой. Когда службы или приложения обращаются к библиотеке или API, те в ответ предоставляют набор действий, необходимых для выполнения задачи и полную инструкцию, каким образом эти действия нужно выполнять.

Протоколы прикладного уровня действуют для большинства приложений, они предоставляют услуги пользователю или обмениваются данными с «коллегами» с нижних уровней по уже установленным соединениям. Здесь для большинства приложений созданы свои протоколы. Например, браузеры используют HTTP для передачи гипертекста по сети, почтовые клиенты — SMTP для передачи почты, FTP-клиенты — протокол FTP для передачи файлов, службы DHCP — протокол назначения IP-адресов DHCP и так далее.

Сети в Selectel

Узнайте, как устроена сетевая архитектура крупного провайдера.

Узнать

Зачем нужен порт и что означает термин «сокет»

Приложения прикладного уровня, общаются также с предыдущим, транспортным, но они видят его протоколы как «черные ящики». Для приема-передачи информации они могут работать, например, с TCP или UDP, но понимают только конечный адрес в виде IP и порта, а не принцип их работы.

Как говорилось ранее, IP-адрес присваивается каждому конечному устройству протоколом межсетевого уровня. Но обмен данными происходит не между конечными устройствами, а между приложениями, установленными на них. Чтобы получить доступ к тому или иному сетевому приложению недостаточно только IP-адреса, поэтому для идентификации приложений применяют также порты. Комбинация IP-адреса и порта называется сокетом, или гнездом (socket).

Кроме собственных протоколов, приложения на прикладном уровне зачастую имеют и фиксированный номер порта для обращения к сети. Администрация адресного пространства интернет (IANA), занимающаяся выделением диапазонов IP-адресов, отвечает еще за назначение сетевым приложениям портов.

Так почтовые приложения, которые общаются по SMTP-протоколу, используют порт 25, по протоколу POP3 — порт 110, браузеры при работе по HTTP — порт 80, FTP-клиенты — порт 21. Порт всегда записывается после IP и отделяется от него двоеточием, выглядит это, например, так: 192.168.1.1:80.

Что такое DNS и для чего используется эта служба

Чтобы не запоминать числовые адреса интернет-серверов была создана DNS — служба доменных имен. DNS всегда слушает на 53 порту и преобразует буквенные имена сетевых доменов в числовые IP-адреса и наоборот. Служба DNS позволяет не запоминать IP — компьютер самостоятельно посылает запрос «какой IP у selectel.ru?» на 53 порт DNS-сервера, полученного от поставщика услуг интернет.

DNS-сервер дает компьютеру ответ «IP для selectel.ru — XXX.XXX.XXX.XXX». Затем, компьютер устанавливает соединение с веб-сервером полученного IP, который слушает на порту 80 для HTTP-протокола и на порту 443 для HTTPS. В браузере порт не отображается в адресной строке, а используется по умолчанию, но, по сути, полный адрес сайта Selectel выглядит вот так: https://selectel.ru:443.

Декапсуляция и инкапсуляция

Для обеспечения корректной работы протоколов различных уровней в сетевых моделях используется специальный метод — инкапсуляция. Суть этого метода заключается в добавлении заголовка протокола текущего уровня к данным, полученным от протокола вышестоящего уровня. Процесс, обратный описанному, называется декапсуляцией. Оба процесса осуществляются на компьютерах получателя и отправителя данных попеременно, это позволяет долго не удерживать одну сторону канала занятой, оставляя время на передачу информации другому компьютеру.

Инкапсуляция

Стек протоколов, снова канальный уровень

О канальном уровне модели TCP/IP мы рассказали меньше всего. Давайте вернемся еще раз к началу, чтобы рассмотреть инкапсуляцию протоколов и что значит «стек».

Большинству пользователей знаком протокол Ethernet. В сети, по стандарту Ethernet, устройства отправителя и адресата имеют определенный MAC-адрес — идентификатор «железа». MAC-адрес инкапсулируется в Ethernet вместе с типом передаваемых данных и самими данными. Фрагмент данных, составленных в соответствии с Ethernet, называется фреймом, или кадром (frame).

MAC-адрес каждого устройства уникален, и двух «железок» с одинаковым адресом не должно существовать. Совпадение может привести к сетевым проблемам. Таким образом, при получении кадра сетевой адаптер занимается извлечением полученной информации и ее дальнейшей обработкой.

После ознакомления с уровневой структурой модели становится понятно, что информация не может передаваться между двумя компьютерами напрямую. Сначала кадры передаются на межсетевой уровень, где компьютеру отправителя и компьютеру получателя назначается уникальный IP. После чего, на транспортном уровне, информация передается в виде TCP-фреймов либо UDP-датаграмм.

На каждом этапе, подобно снежному кому, к уже имеющейся информации добавляется служебная информация, например, порт на прикладном уровне, необходимый для идентификации сетевого приложения. Добавление служебной информации к основной обеспечивают разные протоколы — сначала Ethernet, поверх него IP, еще выше TCP, над ним порт, означающий приложение с делегированным ему протоколом. Такая вложенность называется стеком, названным TCP/IP по двум главным протоколам модели.

Point-to-Point протоколы

Протоколы модели TCP/IP

Отдельно расскажем о Point-to-Point (от точки к точке, двухточечный) протоколе, также известном как PPP. PPP уникален по своим функциям, он применяется для коммуникации между двумя маршрутизаторами без участия хоста или какой-либо сетевой структуры в промежутке. При необходимости PPP обеспечивает аутентификацию, шифрование, а также сжатие данных. Он широко используется при построении физических сетей, например, кабельных телефонных, сотовых телефонных, сетей по кабелю последовательной передачи и транк-линий (когда один маршрутизатор подключают к другому для увеличения размера сети).

У PPP есть два подвида — PPPoE (PPP по Ethernet) и PPPoA (PPP через асинхронный способ передачи данных — ATM), интернет-провайдеры часто их используют для DSL соединений.

PPP и его старший аналог SLIP (протокол последовательной межсетевой связи) формально относятся к межсетевому уровню TCP/IP, но в силу особого принципа работы, иногда выделяются в отдельную категорию. Преимущество PPP в том, что для установки соединения не требуется сетевая инфраструктура, а необходимость маршрутизаторов отпадает. Эти факторы обуславливают специфику использования PPP протоколов.

Заключение

Стек TCP/IP регламентирует взаимодействие разных уровней. Ключевым понятием здесь являются протоколы, формирующие стек, встраиваясь друг в друга с целью передать данные. Рассмотренная модель по сравнению с OSI имеет более простую архитектуру.

Сама модель остается неизменной, в то время как стандарты протоколов могут обновляться, что еще дальше упрощает работу с TCP/IP. Благодаря всем преимуществам стек TCP/IP получил широкое распространение и использовался сначала в качестве основы для создания глобальной сети, а после для описания работы интернета.

Большинство из нас знает TCP/IP как «клей», связующий Internet. Но не многие способны дать убедительное описание того, что этот протокол представляет собой и как работает. Итак, что же такое TCP/IP в действительности?

TCP/IP — это средство для обмена информацией между компьютерами, объединенными в сеть. Не имеет значения, составляют ли они часть одной и той же сети или подключены к отдельным сетям. Не играет роли и то, что один из них может быть компьютером Cray, а другой Macintosh. TCP/IP — это не зависящий от платформы стандарт, который перекидывает мосты через пропасть, лежащую между разнородными компьютерами, операционными системами и сетями. Это протокол, который глобально управляет Internet, и в значительной мере благодаря сети TCP/IP завоевал свою популярность.

Понимание TCP/IP главным образом подразумевает способность разбираться в наборах таинственных протоколов, которые используются главными компьютерами TCP/IP для обмена информацией. Давайте рассмотрим некоторые из этих протоколов и выясним, что составляет оболочку TCP/IP.

  • Основы TCP/IP
  • Архитектура TCP/IP
  • Краткое заключение

TCP/IP — это аббревиатура термина Transmission Control Protocol/Internet Protocol (Протокол управления передачей/Протокол Internet). В терминологии вычислительных сетей протокол — это заранее согласованный стандарт, который позволяет двум компьютерам обмениваться данными. Фактически TCP/IP не один протокол, а несколько. Именно поэтому вы часто слышите, как его называют набором, или комплектом протоколов, среди которых TCP и IP — два основных.

Программное обеспечение для TCP/IP, на вашем компьютере, представляет собой специфичную для данной платформы реализацию TCP, IP и других членов семейства TCP/IP. Обычно в нем также имеются такие высокоуровневые прикладные программы, как FTP (File Transfer Protocol, Протокол передачи файлов), которые дают возможность через командную строку управлять обменом файлами по Сети.

TCP/IP — зародился в результате исследований, профинансированных Управлением перспективных научно-исследовательских разработок (Advanced Research Project Agency, ARPA) правительства США в 1970-х годах. Этот протокол был разработан с тем, чтобы вычислительные сети исследовательских центров во всем мире могли быть объединены в форме виртуальной «сети сетей» (internetwork). Первоначальная Internet была создана в результате преобразования существующего конгломерата вычислительных сетей, носивших название ARPAnet, с помощью TCP/IP.

Причина, по которой TCP/IP столь важен сегодня, заключается в том, что он позволяет самостоятельным сетям подключаться к Internet или объединяться для создания частных интрасетей. Вычислительные сети, составляющие интрасеть, физически подключаются через устройства, называемые маршрутизаторами или IP-маршрутизаторами. Маршрутизатор — это компьютер, который передает пакеты данных из одной сети в другую. В интрасети, работающей на основе TCP/IP, информация передается в виде дискретных блоков, называемых IP-пакетами (IP packets) или IP-дейтаграммами (IP datagrams). Благодаря программному обеспечению TCP/IP все компьютеры, подключенные к вычислительной сети, становятся «близкими родственниками». По существу оно скрывает маршрутизаторы и базовую архитектуру сетей и делает так, что все это выглядит как одна большая сеть. Точно так же, как подключения к сети Ethernet распознаются по 48-разрядным идентификаторам Ethernet, подключения к интрасети идентифицируются 32-разрядными IP-адресами, которые мы выражаем в форме десятичных чисел, разделенных точками (например, 128.10.2.3). Взяв IP-адрес удаленного компьютера, компьютер в интрасети или в Internet может отправить данные на него, как будто они составляют часть одной и той же физической сети.

TCP/IP дает решение проблемы данными между двумя компьютерами, подключенными к одной и той же интрасети, но принадлежащими различным физическим сетям. Решение состоит из нескольких частей, причем каждый член семейства протоколов TCP/IP вносит свою лепту в общее дело. IP — самый фундаментальный протокол из комплекта TCP/IP — передает IP-дейтаграммы по интрасети и выполняет важную функцию, называемую маршрутизацией, по сути дела это выбор маршрута, по которому дейтаграмма будет следовать из пункта А в пункт B, и использование маршрутизаторов для «прыжков» между сетями.

TCP — это протокол более высокого уровня, который позволяет прикладным программам, запущенным на различных главных компьютерах сети, обмениваться потоками данных. TCP делит потоки данных на цепочки, которые называются TCP-сегментами, и передает их с помощью IP. В большинстве случаев каждый TCP-сегмент пересылается в одной IP-дейтаграмме. Однако при необходимости TCP будет расщеплять сегменты на несколько IP-дейтаграмм, вмещающихся в физические кадры данных, которые используют для передачи информации между компьютерами в сети. Поскольку IP не гарантирует, что дейтаграммы будут получены в той же самой последовательности, в которой они были посланы, TCP осуществляет повторную «сборку» TCP-сегментов на другом конце маршрута, чтобы образовать непрерывный поток данных. FTP и telnet — это два примера популярных прикладных программ TCP/IP, которые опираются на использование TCP.

Другой важный член комплекта TCP/IP — User Datagram Protocol (UDP, протокол пользовательских дейтаграмм), который похож на TCP, но более примитивен. TCP — «надежный» протокол, потому что он обеспечивает проверку на наличие ошибок и обмен подтверждающими сообщениями чтобы данные достигали своего места назначения заведомо без искажений. UDP — «ненадежный» протокол, ибо не гарантирует, что дейтаграммы будут приходить в том порядке, в котором были посланы, и даже того, что они придут вообще. Если надежность — желательное условие, для его реализации потребуется программное обеспечение. Но UDP по-прежнему занимает свое место в мире TCP/IP, и испльзуется во многих программах. Прикладная программа SNMP (Simple Network Management Protocol, простой протокол управления сетями), реализуемый во многих воплощениях TCP/IP, — это один из примеров программ UDP.

Другие TCP/IP протоколы играют менее заметные, но в равной степени важные роли в работе сетей TCP/IP. Например, протокол определения адресов (Address Resolution Protocol, ARP) ппреобразует IP-адреса в физические сетевые адреса, такие, как идентификаторы Ethernet. Родственный протокол — протокол обратного преобразования адресов (Reverse Address Resolution Protocol, RARP) — выполняет обеспечивает обратное действие, преобразуя физические сетевые адреса в IP-адреса. Протокол управления сообщениями Internet (Internet Control Message Protocol, ICMP) представляет собой протокол сопровождения, который использует IP для обмена управляющей информацией и контроля над ошибками, относящимися к передаче пакетов IP. Например, если маршрутизатор не может передать IP-дейтаграмму, он использует ICMP, с тем чтобы информировать отправителя, что возникла проблема. Краткое описание некоторых других протоколов, которые «прячутся под зонтиком» TCP/IP, приведено во врезке.

Краткое описание протоколов семейства TCP/IP с расшифровкой аббревиатур
ARP (Address Resolution Protocol, протокол определения адресов): конвертирует 32-разрядные IP-адреса в физические адреса вычислительной сети, например, в 48-разрядные адреса Ethernet.

FTP (File Transfer Protocol, протокол передачи файлов): позволяет передавать файлы с одного компьютера на другой с использованием TCP-соединений. В родственном ему, но менее распространенном протоколе передачи файлов — Trivial File Transfer Protocol (TFTP) — для пересылки файлов применяется UDP, а не TCP.

ICMP (Internet Control Message Protocol, протокол управляющих сообщений Internet): позволяет IP-маршрутизаторам посылать сообщения об ошибках и управляющую информацию другим IP-маршрутизаторам и главным компьютерам сети. ICMP-сообщения «путешествуют» в виде полей данных IP-дейтаграмм и обязательно должны реализовываться во всех вариантах IP.

IGMP (Internet Group Management Protocol, протокол управления группами Internet): позволяет IP-дейтаграммам распространяться в циркулярном режиме (multicast) среди компьютеров, которые принадлежат к соответствующим группам.

IP (Internet Protocol, протокол Internet): низкоуровневый протокол, который направляет пакеты данных по отдельным сетям, связанным вместе с помощью маршрутизаторов для формирования Internet или интрасети. Данные «путешествуют» в форме пакетов, называемых IP-дейтаграммами.

RARP (Reverse Address Resolution Protocol, протокол обратного преобразования адресов): преобразует физические сетевые адреса в IP-адреса.

SMTP (Simple Mail Transfer Protocol, простой протокол обмена электронной почтой): определяет формат сообщений, которые SMTP-клиент, работающий на одном компьютере, может использовать для пересылки электронной почты на SMTP-сервер, запущенный на другом компьютере.

TCP (Transmission Control Protocol, протокол управления передачей): протокол ориентирован на работу с подключениями и передает данные в виде потоков байтов. Данные пересылаются пакетами — TCP-сегментами, — которые состоят из заголовков TCP и данных. TCP — «надежный» протокол, потому что в нем используются контрольные суммы для проверки целостности данных и отправка подтверждений, чтобы гарантировать, что переданные данные приняты без искажений.

UDP (User Datagram Protocol, протокол пользовательских дейтаграмм): протокол, не зависящий от подключений, который передает данные пакетами, называемыми UDP-дейтаграммами. UDP — «ненадежный» протокол, поскольку отправитель не получает информацию, показывающую, была ли в действительности принята дейтаграмма.

Проектировщики вычислительных сетей часто используют семиуровневую модель ISO/OSI (International Standards Organization/Open Systems Interconnect, Международная организация по стандартизации/ Взаимодействие открытых систем), которая описывает архитектуру сетей. Каждый уровень в этой модели соответствует одному уровню функциональных возможностей сети. В самом основании располагается физический уровень, представляющий физическую среду, по которой «путешествуют» данные, — другими словами, кабельную систему вычислительной сети. Над ним имеется канальный уровень, или уровень звена данных, функционирование которого обеспечивается сетевыми интерфейсными платами. На самом верху размещается уровень прикладных программ, где работают программы, использующие служебные функции сетей.

На рисунке показано, как TCP/IP согласуется с моделью ISO/OSI. Этот рисунок также иллюстрирует уровневое строение TCP/IP и показывает взаимосвязи между основными протоколами. При переносе блока данных из сетевой прикладной программы в плату сетевого адаптера он последовательно проходит через ряд модулей TCP/IP. При этом на каждом шаге он доукомплектовывается информацией, необходимой для эквивалентного модуля TCP/IP на другом конце цепочки. К тому моменту, когда данные попадают в сетевую плату, они представляют собой стандартный кадр Ethernet, если предположить, что сеть основана именно на этом интерфейсе. Программное обеспечение TCP/IP на приемном конце воссоздает исходные данные для принимающей программы путем захвата кадра Ethernet и прохождения его в обратном порядке по набору модулей TCP/IP. (Один из наилучших способов разобраться во внутреннем устройстве TCP/IP стоит в использовании программы-«шпиона», чтобы найти внутри кадров, «пролетающих» по сети, информацию, добавленную различными модулями TCP/IP.)

Уровни сетей и протоколы TCP/IP

ISO/OSI                             TCP/IP
    _____________________________      __________________________
   | Уровень прикладных программ |    |                          |
   |_____________________________|    |  _________    _________  |
    _____________________________     | |Сетевая  |  |Сетевая  | | Уровень
   |    Уровень представления    |    | |программа|  |программа| | прикладных
   |_____________________________|    | |_________|  |_________| | программ
    _____________________________     |                          |
   |       Уровень сеанса        |    |                          |
   |_____________________________|    |__________________________|
                                            |             |
    _____________________________      _____|_____________|______
   |    Транспортный уровень     |    |    TCP           UDP     | Транспортный
   |_____________________________|    |_____|_____________|______| уровень
                                            |             |
    _____________________________      _____|_____________|______
   |       Сетевой уровень       |    |     |             |      | Сетевой
   |_____________________________|    |      ----> IP <---       | уровень
                                      |__________________________|
                                               _________
    _____________________________      _______| Сетевая |________
   |    Уровень звена данных     |    | ARP<->| плата   |<->RARP | Уровень
   |_____________________________|    |_______|_________|________| звена
                                                   |               данных
    _____________________________                  |
   |    Физический уровень       |    _____________|______________ Физический
   |_____________________________|      Кабельные соединения сети  уровень

В левой части этой диаграммы показаны уровни модели ISO/OSI. Правая часть диаграммы иллюстрирует корреляцию TCP/IP с этой моделью.

Для иллюстрации роли, которую TCP/IP играет в вычислительных сетях в реальном мире, рассмотрим, что происходит, когда Web-браузер использует HTTP (HyperText Transfer Protocol, протокол передачи гипертекста) для извлечения страницы HTML-данных из Web-сервера, подключенного к Internet. Для формирования виртуального подключения к серверу браузер использует абстракцию программного обеспечения высокого уровня, называемую гнездом (socket). А чтобы извлечь страницу Web, он посылает на сервер команду GET HTTP, записывая ее в гнездо. Программное обеспечение гнезда, в свою очередь, применяет TCP для пересылки битов и байтов, составляющих команду GET на Web-сервер. TCP сегментирует данные и передает отдельные сегменты модулю IP, который пересылает сегменты в дейтаграммах на Web-сервер.

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

И это практически все, чем занимается TCP/IP: превращением множества небольших сетей в одну большую и предоставлением услуг, которые нужны прикладным программам для обмена информацией друг с другом по получающейся в итоге Internet.

О TCP/IP можно было бы рассказать много больше, но есть три ключевых момента:

* TCP/IP — это набор протоколов, которые позволяют физическим сетям объединяться вместе для образования Internet. TCP/IP соединяет индивидуальные сети для образования виртуальной вычислительной сети, в которой отдельные главные компьютеры идентифицируются не физическими адресами сетей, а IP-адресами.
* В TCP/IP используется многоуровневая архитектура, которая четко описывает, за что отвечает каждый протокол. TCP и UDP обеспечивают высокоуровневые служебные функции передачи данных для сетевых программ, и оба опираются на IP при передаче пакетов данных. IP отвечает за маршрутизацию пакетов до их пункта назначения.
* Данные, перемещающиеся между двумя прикладными программами, работающими на главных компьютерах Internet, «путешествуют» вверх и вниз по стекам TCP/IP на этих компьютерах. Информация, добавленная модулями TCP/IP на стороне отправителя, «разрезается» соответствующими TCP/IP-модулями на принимающем конце и используется для воссоздания исходных данных.

В основе работы глобальной сети Интернет лежит  набор (стек) протоколов TCP/IP. Но эти термины лишь на первый взгляд кажутся сложными. На самом деле стек протоколов TCP/IP — это простой набор правил обмена информацией, и правила эти на самом деле вам хорошо известны, хоть вы, вероятно, об этом и не догадываетесь. Да, все именно так, по существу в принципах, лежащих в основе протоколов TCP/IP, нет ничего нового: все новое — это хорошо забытое старое.

Человек может учиться двумя путями:

  1. Через тупое формальное зазубривание шаблонных способов  решения  типовых задач (чему сейчас в основном и учат в школе). Такое обучение малоэффективно. Наверняка вам приходилось наблюдать панику и полную беспомощность бухгалтера при смене версии офисного софта — при малейшем изменении последовательности кликов мышки, требуемых для выполнения привычных действий. Или  приходилось видеть человека, впадающего в ступор при изменении интерфейса рабочего стола? 
  2. Через понимание сути проблем, явлений, закономерностей. Через понимание принципов построения той или иной системы. В этом случае обладание энциклопедическими знаниями не играет большой роли — недостающую информацию легко найти. Главное — знать, что искать. А для этого необходимо не формальное знание предмета, а понимание сути.

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

Итак, начнем.

Принципы работы интернет-протоколов TCP/IP по своей сути очень просты и сильно напоминают работу нашей советской почты.

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

На конверте письма будет написано примерно следующее:

Адрес отправителя:
От кого: Иванов Иван Иванович
Откуда: Ивантеевка, ул. Большая , д. 8, кв. 25

Адрес получателя:
Кому: Петров Петр Петрович
Куда: Москва, Усачевский переулок, д. 105, кв. 110

Теперь мы готовы рассмотреть взаимодействие компьютеров и приложений в сети Интернет (да и в локальной сети тоже). Обратите внимание, что аналогия с обычной почтой будет почти полной.

Каждый компьютер (он же: узел, хост) в рамках сети Интернет тоже имеет уникальный адрес, который называется IP-адрес (Internet Protocol Address), например: 195.34.32.116. IP адрес состоит из четырех десятичных чисел (от 0 до 255), разделенных точкой. Но знать только IP адрес компьютера еще недостаточно, т.к. в конечном счете обмениваются информацией не компьютеры сами по себе, а приложения, работающие на них. А на компьютере может одновременно работать сразу несколько приложений (например почтовый сервер, веб-сервер  и пр.). Для доставки обычного бумажного письма недостаточно знать только адрес дома — необходимо еще знать номер квартиры. Также и каждое программное приложение имеет подобный номер, именуемый номером порта. Большинство серверных приложений имеют стандартные номера, например: почтовый сервис привязан к порту с номером 25 (еще говорят: «слушает» порт, принимает на него сообщения), веб-сервис привязан к порту 80, FTP — к порту 21 и так далее.

Таким образом имеем следующую практически полную аналогию с нашим обычным почтовым адресом:

"адрес дома" = "IP компьютера"
"номер квартиры" = "номер порта"

В компьютерных сетях, работающих по протоколам TCP/IP, аналогом бумажного письма в конверте является пакет, который содержит собственно передаваемые данные и адресную информацию — адрес отправителя и адрес получателя, например:

Адрес отправителя (Source address):
IP: 82.146.49.55
Port: 2049

Адрес получателя (Destination address):
IP: 195.34.32.116
Port: 53

Данные пакета:
...

Конечно же в пакетах также присутствует служебная информация, но для понимания сути это не важно.

Обратите внимание, комбинация: «IP адрес и номер порта» — называется  «сокет».

В нашем примере мы с сокета 82.146.49.55:2049 посылаем пакет на сокет 195.34.32.116:53, т.е. пакет пойдет на компьютер, имеющий IP адрес 195.34.32.116, на порт 53. А порту 53 соответствует сервер распознавания имен (DNS-сервер), который примет этот пакет. Зная адрес отправителя, этот сервер  сможет после обработки нашего запроса сформировать ответный пакет, который пойдет в обратном направлении на сокет отправителя 82.146.49.55:2049, который для DNS сервера будет являться сокетом получателя.

Как правило взаимодействие осуществляется по схеме «клиент-сервер»: «клиент» запрашивает какую-либо информацию (например страницу сайта), сервер принимает запрос, обрабатывает его и посылает результат. Номера портов серверных приложений общеизвестны, например:  почтовый SMTP сервер «слушает» 25-й порт,  POP3 сервер, обеспечивающий чтение почты из ваших почтовых ящиков «слушает» 110-порт, веб-сервер — 80-й порт и пр.

Большинство программ на домашнем компьютере являются клиентами — например почтовый клиент Outlook, веб-обозреватели IE, FireFox и пр. 

Номера портов на клиенте не фиксированные как у сервера, а назначаются операционной системой динамически. Фиксированные серверные порты как правило имеют номера до 1024 (но есть исключения), а клиентские начинаются после 1024.

Повторение — мать учения: IP — это адрес компьютера (узла, хоста) в сети, а порт — номер конкретного приложения, работающего на этом компьютере.

Однако человеку запоминать цифровые IP адреса трудно — куда удобнее работать с буквенными именами. Ведь намного легче запомнить слово, чем набор цифр. Так и сделано — любой цифровой IP адрес можно связать с  буквенно-цифровым именем. В результате например вместо 82.146.49.55 можно использовать имя www.ofnet.ru.  А преобразованием доменного имени в цифровой IP адрес занимается сервис доменных имен — DNS (Domain Name System).

Рассмотрим подробнее, как это работает. Ваш провайдер явно (на бумажке, для ручной настройки соединения) или неявно (через автоматическую настройку соединения) предоставляет вам IP адрес сервера имен (DNS). На компьютере с этим IP адресом работает приложение (сервер имен), которое знает все доменные имена в Интернете и соответствующие им цифровые IP адреса. DNS-сервер «слушает» 53-й порт, принимает на него запросы и выдает ответы, например:

Запрос от нашего компьютера: "Какой IP адрес соответствует имени www.ofnet.ru?"
Ответ сервера: "82.146.49.55."

Теперь рассмотрим, что происходит, когда в своем браузере вы набираете доменное имя (URL) этого сайта (www.ofnet.ru) и, нажав <enter>, в ответ от веб-сервера получаете страницу этого сайта.

Например:

IP адрес нашего компьютера: 91.76.65.216
Браузер: Internet Explorer (IE),
DNS сервер (стрима): 195.34.32.116 (у вас может быть другой), Страница, которую мы хотим открыть: www.ofnet.ru.

Набираем в адресной строке браузера доменное имя www.ofnet.ru и жмем <enter>. Далее операционная система производит примерно следующие действия:

Отправляется запрос (точнее пакет с запросом) DNS серверу на сокет 195.34.32.116:53. Как было рассмотренно выше, порт 53 соответствует DNS-серверу — приложению,  занимающемуся распознаванием имен. А DNS-сервер, обработав наш запрос, возвращает IP-адрес, который соответствует введенному имени.

Диалог примерно следующий:

- Какой IP адрес соответствует имени www.ofnet.ru?
- 82.146.49.55.

Далее наш компьютер устанавливает соединение с портом 80 компьютера  82.146.49.55 и посылает запрос (пакет с запросом) на получение страницы www.ofnet.ru. 80-й порт соответствует веб-серверу. В адресной строке браузера 80-й порт как правило не пишется, т.к. используется по умолчанию, но его можно и явно указать после двоеточия — http://www.ofnet.ru:80.

Приняв от нас запрос, веб-сервер обрабатывает его и в нескольких пакетах посылает нам страницу в на языке HTML — языке разметки текста, который понимает браузер.

Наш браузер, получив страницу, отображает ее. В результате мы видим на экране главную страницу этого сайта.

Зачем эти принципы надо понимать?

Например, вы заметили странное поведение своего компьютера — непонятная сетевая активность, тормоза и пр. Что делать? Открываем консоль (нажимаем кнопку «Пуск» — «Выполнить» — набираем cmd — «Ок»). В  консоли набираем команду netstat -anи жмем <Enter>. Эта утилита отобразит список установленных соединений между сокетами нашего компьютера и сокетами удаленных узлов. Если мы видим в колонке «Внешний адрес» какие-то чужие IP адреса, а через двоеточие 25-й порт, что это может означать? (Помните, что 25-й порт соответствует почтовому серверу?) Это означает то, что ваш компьютер установил соединение с каким-то почтовым сервером (серверами) и шлет через него какие-то письма. И если ваш почтовый клиент (Outlook например) в это время не запущен, да если еще таких соединений на 25-й порт много, то, вероятно, в вашем компьютере завелся вирус, который рассылает от вашего имени спам или пересылает номера ваших кредитных карточек вкупе с паролями злоумышленникам.

Также понимание принципов работы Интернета необходимо для правильной настройки файерволла (проще говоря брандмауэра :)). Эта программа (которая часто поставляется вместе с антивирусом), предназначенна для фильтрации пакетов — «своих» и «вражеских». Своих пропускать, чужих не пущать. Например, если ваш фаерволл сообщает вам, что некто хочет установить соединение с каким-либо портом вашего компьютера. Разрешить или запретить?

Ну и самое главное — эти знания крайне полезны при общении с техподдержкой.

Напоследок приведу список портов, с которыми вам, вероятно, придется столкнуться:

135-139 — эти порты используются Windows для доступа к общим ресурсам компьютера — папкам, принтерам. Не открывайте эти порты наружу, т.е. в районную локальную сеть и Интернет. Их следует закрыть фаерволлом. Также если в локальной сети вы не видите ничего в сетевом окружении или вас не видят, то вероятно это связано с тем, что фаерволл заблокировал эти порты. Таким образом для локальной сети эти порты должны быть открыты, а для Интернета закрыты.

21 — порт FTP сервера.

25 — порт почтового SMTP сервера. Через него ваш почтовый клиент отправляет письма. IP адрес SMTP сервера и его порт (25-й) следует указать в настройках вашего почтового клиента.

110 — порт POP3 сервера. Через него ваш почтовый клиент забирает письма из вашего почтового ящика. IP адрес POP3 сервера и его порт (110-й) также следует указать в настройках вашего почтового клиента.

80 — порт WEB-сервера.

3128, 8080 — прокси-серверы (настраиваются в параметрах браузера).

Несколько специальных IP адресов:

127.0.0.1 — это localhost, адрес локальной системы, т.е. локальный адрес вашего компьютера.
0.0.0.0 - так обозначаются все IP-адреса.
192.168.xxx.xxx — адреса, которые можно произвольно использовать в локальных сетях, в глобальной сети Интернет они не используются. Они уникальны только в рамках локальной сети. Адреса из этого диапазона вы можете использовать по своему усмотрению, например, для построения домашней или офисной сети.

Что такое маска подсети и шлюз по умолчанию (роутер, маршрутизатор)?

(Эти параметры задаются в настройках сетевых подключений).

Все просто. Компьютеры объединяются в локальные сети. В локальной сети компьютеры напрямую «видят» только друг друга. Локальные сети соединяются друг с другом через шлюзы (роутеры, маршрутизаторы). Маска подсети предназначена для определения — принадлежит ли компьютер-получатель к этой же локальной сети или нет. Если компьютер-получатель принадлежит этой же сети, что и компьютер-отправитель, то пакет передается ему напрямую, в противном случае пакет отправляется на шлюз по умолчанию, который далее, по известным ему маршрутам, передает пакет в другую  сеть, т.е. в другое почтовое отделение (по аналогии с советской почтой).

Напоследок рассмотрим что же означают непонятные термины:

TCP/IP — это название набора сетевых протоколов. На самом деле передаваемый пакет проходит несколько уровней. (Как на почте: сначала вы пишете писмо, потом помещаете в конверт с адресом, затем на почте на нем ставится штамп и т.д.).

IP протокол — это протокол так называемого сетевого уровня. Задача этого уровня — доставка ip-пакетов от компьютера отправителя к компьютеру получателю. По-мимо собственно данных, пакеты этого уровня имеют ip-адрес отправителя и ip-адрес получателя. Номера портов на сетевом уровне не используются. Какому порту, т.е. приложению адресован этот пакет, был ли этот пакет доставлен или был потерян, на этом уровне неизвестно — это не его задача, это задача транспортного уровня.

TCP и UDP — это протоколы так называемого транспортного уровня. Транспортный уровень находится над сетевым. На этом уровне к пакету добавляется порт отправителя и порт получателя.

TCP — это протокол с установлением соединения и с гарантированной доставкой пакетов. Сначала производится обмен специальными пакетами для установления соединения, происходит что-то вроде рукопожатия (-Привет. -Привет. -Поболтаем? -Давай.). Далее по этому соединению туда и обратно посылаются пакеты (идет беседа), причем с проверкой, дошел ли пакет до получателя. Если пакет не дошел, то он посылается повторно («повтори, не расслышал»).

UDP — это протокол без установления соединения и с негарантированной доставкой пакетов. (Типа: крикнул что-нибудь, а услышат тебя или нет — неважно).

Над транспортным уровнем находится прикладной уровень. На этом уровне работают такие протоколы, как httpftp и пр. Например HTTP и FTP — используют надежный протокол TCP, а DNS-сервер работает через ненадежный протокол UDP.

Как посмотреть текущие соединения?

Текущие соединения можно посмотреть с помощью команды

netstat -an

(параметр n указывает выводить IP адреса вместо доменных имен).

Запускается эта команда следующим образом:

«Пуск» — «Выполнить» — набираем cmd — «Ок». В появившейся консоли (черное окно) набираем команду netstat -an и жмем <Enter>. Результатом будет список установленных соединений между сокетами нашего компьютера и удаленных узлов.

Например получаем:

Активные подключения
Имя Локальный адрес Внешний адрес Состояние
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
TCP 91.76.65.216:139 0.0.0.0:0 LISTENING
TCP 91.76.65.216:1719 212.58.226.20:80 ESTABLISHED
TCP 91.76.65.216:1720 212.58.226.20:80 ESTABLISHED
TCP 91.76.65.216:1723 212.58.227.138:80 CLOSE_WAIT
TCP 91.76.65.216:1724 212.58.226.8:80 ESTABLISHED
...

В этом примере 0.0.0.0:135 — означает, что наш компьютер на всех своих IP адресах слушает (LISTENING) 135-й порт и готов принимать на него соединения от кого угодно (0.0.0.0:0) по протоколу TCP.

91.76.65.216:139 — наш компьютер слушает 139-й порт на своем IP-адресе 91.76.65.216.

Третья строка означает, что сейчас установлено (ESTABLISHED) соединение между нашей машиной (91.76.65.216:1719) и удаленной (212.58.226.20:80). Порт 80 означает, что наша машина обратилась с запросом к веб-серверу (у меня, действительно, открыты страницы в браузере).

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

Я сам сетевой администратор, понимаю как работает сеть, но я не понимаю каким образом реализуется написание такого протокола как например IP или ICMP. Я наталкивался на коды описывающие это, но мой вопрос чуть глубже а именно:

  1. Необходимо ли воздействовать на драйвер сетевой карты или требуется написать свой драйвер?
  2. Каким образом я регистрирую протокол на пк? А именно в каком виде хранятся протоколы на пк? Насколько я знаю протоколы в маршрутизаторах зашиты в ОС роутера.
  3. Насколько я знаю в виндовс имеется сектор коммуникации (WINRT) имеется ли необходимость воздействия с этим сектором и как это делать.
  4. Я знаю, что это частично невозможно, но я хочу попытаться разработать его с помощью c#. Я знаю что с++ это скальпель программиста но совсем нет желания сейчас изучать дополнительный язык.
  5. Какого рода программирование следует использовать, структурное, объектное?

Vladimir Gamalyan's user avatar

задан 9 дек 2017 в 23:55

Alpha Sigma's user avatar

5

Понравилась статья? Поделить с друзьями:
  • Как пишется сет хоум
  • Как пишется сет хом на английском
  • Как пишется сет спавн
  • Как пишется сет роллов
  • Как пишется сет ноль