Основы работы с сетью на примере консольного чата
Если вы начали читать эту статью, то, скорее всего, имеете какое-то отношение к IT-и понимаете что такое IP-адрес – уникальный адрес, который определяет компьютер в сети.
Но достаточно ли такой адресации для полноценной работы? Предположим, что на некотором компьютере запущены одновременно две программы, которые взаимодействуют с интернетом – получают и/или отсылают какие-то данные. Программы никак между собой не связаны и общаются с разными интернет-сервисами. Но они расположены на одном компьютере, следовательно, имеют один IP-адрес. Если они одновременно должны получить данные от двух разных серверов, как же они определят, кому какие данные предназначались?
Для того, чтобы решить эту проблему, нужно вместе с самими данными передавать информацию о том, какой программе они предназначаются. Эта информация и есть порт.
В нашем случае каждая из программ (точнее, программисты, написавшие их) должна определить, по какому порту она хочет взаимодействовать с сетью. Сервер, в свою очередь, должен тоже знать этот порт и посылать данные именно на него.
Что же собой представляет этот загадочный порт? Вы можете взять отвёртку и перебрать весь компьютер, но портов так и не найдёте. Это просто число, которое передаётся вместе с данными. Теоретически, оно может находиться в диапазоне от 1 до 65535, но порты 1..1024 используются системными программами и занимать их не стоит. Поэтому порт следует выбирать из диапазона 1025..65535.
Итак, IP-адрес это адрес компьютера в сети; порт – «адрес» программы на этом компьютере. А сокет – это их объединение, т.е. адрес программы в сети. Именно сокеты мы и будем использовать, чтобы указать программе, куда же стоит отправлять сообщения.
Планируем
Даже в таких простых программах, как чат, не нужно сразу лезть в IDEи писать что-то невнятное. Для начала стоит осмыслить теорию, с которой мы ознакомились (вы же её не пропустили, правда?) и понять, что она значит в контексте нашей программы.
Во-первых, в проектах, которые предназначены для работы с сетью, нужны две программы (не будет же она одна слать сама себе сообщения). Одна из них является сервером, а другая – клиентом. Отсюда первое следствие:
При этом можно создать либо две разные программы, либо одну и спрашивать пользователя, в каком режиме её запустить. Первый способ лучше тем, что серверу не придётся хранить файлы для клиентской части. С другой стороны, так чтобы проверить работу сервера и клиента придётся качать и запускать две разные программы. Остановимся на втором способе – в одной программе в зависимости от желания пользователя, будем запускать серверный или клиентский режим.
Дальше самое время подумать об архитектуре. С двумя пользователями всё легко – они просто пересылают друг другу свои сообщения. Но как мы помним, чат у нас планируется многопользовательский. Хранить у каждого клиента список всех остальных пользователей бессмысленно (следует понимать, что в данном случае клиент и пользователь – одно и то же). Поэтому сделаем так:
И последнее, с чем нужно разобраться это протокол – алгоритм взаимодействия программ в сети. Очевидно, что и сервер и клиент должны слать друг другу сообщения по одному алгоритму. Ведь если клиент отправит строку, в то время как сервер будет ждать число, они друг друга не поймут, и будут работать неправильно.
Раз пользователей будет много, неплохо бы их как-то идентифицировать. Для этого сразу после подключения клиент должен отправить серверу свой ник. Дальше клиент посылает сообщения в чат до тех пор, пока очередное сообщение не будет равно «exit». Это означает, что пользователь хочет покинуть чат и закрыть программу.
Написание программы
Теперь, когда все подготовительные момент ясны, можно преступить к самому интересному – написанию программы.
Файлы и структура пакетов
Вы, конечно, знаете, что любая программа на Javaначинается с метода main(String[] args). Для большей наглядности не будем добавлять его к другим классам, а создадим отдельный класс Mainи пакет для него – main. В любой программе наверняка будут какие-то константы. Я предпочитаю выносить их в отдельный файл в виде publicstaticполей, поэтому создам класс Constи также добавлю его в пакет main.
Как мы помним, программа должна работать в режиме клиента или сервера. Создадим два соответствующих класса Clientи Server.
В итоге дерево пакетов выглядит так:
Выбор режима работы
Для начала нужно выбрать, в каком режиме запускать программу – сервер или клиент. Это нам и нужно первым делом узнать у пользователя, поэтому в метод main(…) пишем следующее:
Здесь всё достаточно просто – спрашиваем, как запускать программу, считываем букву ответа и запускаем соответствующий класс. Стоит пояснить только по поводу класса Scanner – это класс стандартной библиотеки, который облегчает работу с вводом данных из консоли. Он инициализируется стандартным потоком ввода.
Режим клиента
Пойдём от простого к сложному и сначала реализует клиентский режим работы.
Если сервер просто запускается и ждём пользователей, то клиентам приходится проявлять некоторую активность, а именно – подключиться к серверу. Для этого нужно знать его IPи порт подключения. Порт является константой, поэтому зададим его в Const.java:
Constобъявлен как abstract, т.к. содержит только статические данные и создавать его экземпляр ни к чему.
IPдолжен ввести пользователь, поэтому в конструкторе Client пишем:
Теперь у нас есть все необходимые данные – ip, порт, режим работы программы. Можно подключаться к серверу. Сначала создадим сокет:
При этом сразу же производится подключение и можно передавать и считывать данные. Но как это сделать, если данные передаются только через потоки? Каждый Socketсодержит входной и выходной потоки класса InputStreamи OutputStream. Можно работать прямо с ними, но лучше для удобства «завернуть» их во что-то более функциональное:
Любые операции с потоками и сокетами должны выполняться внутри блока try..catch, для того, чтобы обрабатывать ошибки.
Вот теперь можно начать обмен сообщениями с сервером. Как мы помним, по протоколу, нужно сначала передать имя пользователя а затем каждое введённое (в консоль) сообщение отправлять серверу. Так и сделаем:
Метод println() объекта outотправляет данные на сервер, а метод readLine() объекта in– считывает полученные данные. Но как нам печатать в консоль полученные от сервера сообщения? Ведь нам нужно одновременно ожидать сообщений из консоли (от пользователя) и сообщений из потока (от сервера). Придётся для этого создать дополнительную нить.
Thread– класс Java, который реализует такую незаменимую вещь, как многопоточность. Это возможность программы одновременно выполнять разные наборы действий. Как раз то, что нам сейчас нужно.
Создадим внутренний класс, который будет получать сообщения от сервера и выводить их в консоль.
До тех пор, пока поток не будет остановлен, он просто считывает все сообщения сервера и выводит их в консоль.
В конструкторе создадим объект этого класса и запустим поток:
Итоговый файл Client.java вместе с остальными приведён в конце статьи.
Режим сервера
Сервер, в отличие от клиента, работает не с классом Socket, а с ServerSocket. При создании его объекта программа никуда не подключается, а просто создаётся сервер на порту, переданном в конструктор.
Вся логика работы с конкретным пользователем будем находиться во внутреннем класса Connection, а Serverбудет только принимать новые подключения и оперировать существующими. Начнём «снизу» и создадим класс Connection, который должен в отдельной нити принимать от пользователя сообщения и рассылать их остальным клиентам:
connection это массив со всеми соединениями пользователей. Когда необходимо отправить какое-то сообщение всем, мы перебираем этот массив и обращаемся к каждому клиенту.
Конструктор, как и в классе Client, преобразовывает потоки, связанные с сокетом. Метод run() запускается в отдельной нити и выполняется параллельно с остальной частью программы. В нём, согласно протоколу, сначала считывается имя пользователя, а потом все остальные его сообщения рассылаются всем клиентам чата. Когда приходит сообщение “exit”, пользователь отключается от чата, а все связанные с ним потоки закрываются методом close().
Теперь осталось только создать сервер, который будет принимать подключения, создавать объекты Connectionи добавлять их в массив. В конструкторе класса Server пишем:
Метод server.accept() указывает серверу ожидать подключения. Как только какой-то клиент подключится к серверу, метод вернёт объект Socket, связанный с этим подключением. Дальше создаётся объект Connection, инициализированный этим сокетом и добавляется в массив. Не забываем про try..catchи в конце закрываем все сокеты вместе с потоками методом closeAll();
Исходники
Где-то на середине статьи я вдруг осознал, что худшего учителя найти сложно, поэтому вот вам хотя бы исходники с комментариями. Может там что-то будет понятно. Спойлеров что-то нету, так что лучше выложу на bitbucket.
Если Вам понравилась статья, проголосуйте за нее
Голосов: 29 Голосовать
Разработка клиент-серверного чата на Java. Часть 1. Немного теории и сервер
Краткое описание
На данный момент заканчиваю 2-й курс универститета, одной из лабораторных работ по курсу Java было написание чата. После того, как разобрался в теме сокетов, сериализации объектов и MVC, хотелось бы поделиться с читателим, тем более, что оно мне несказанно помогло при написании проекта.
Ну и, разумеется, учту все ошибки и недочеты, которые будут озвучены.
Немного теории
Итак, для начала. Проект будет состоять из двух частей: клиента и сервера. Клиент будет иметь GUI, написанный с помощью библиотеки Swing. Сервер GUI иметь не будет, только log-файл и небольшой вывод в консоль.
Для написания чата, нам понадобятся некоторые знания.
Сокеты
Поскольку взаимосвязь клиента и сервера у нас будет реализована с помощью сокетов, познакомимся с ними поближе.
Со́кеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Сокет — абстрактный объект, представляющий конечную точку соединения.
Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы. Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения.
Для нас это означает, что сервер будет слушать какой-либо порт (для большей универсальности, мы будем задавать порт в конфигурационном файле) и после подключения клиента, будем производить с ним какие-то действия.
Передача объектов по сети
Но просто получать сообщение от клиента нам мало. Нам хочется знать его имя, IP-адрес, а также передавать ему в ответ список подключенных пользователей. Таким образом, просто передача текстовых данных нас не устроит. У нас есть 2 выхода:
1. Передавать xml-строку с описанием всей информации
2. Передавать сериализованный объект, в котором будут храниться все необходимые нам данные в виде полей.
Не будем говорить о плюсах и минусах каждого подхода. Воспользуемся вторым вариантом. (А если понадобится, когда-нибудь потом, я напишу второй)
Итак, что такое сериализация объектов.
Сериализация — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.
Сериализация используется для передачи объектов по сети и для сохранения их в файлы.
В Java единственное, что нужно для сериализации объекта — имплементировать интерфейс Serializable, который является интерфейсом-маркером, т.е не содержит методов.
Пишем Сервер
Итак, краткое описание работы сервера.
Сервер работает в вечном цикле. Как только подключается новый клиент, он создает для работы с ним новый поток, оповещает уже подключенных клиентов о новом пользователей, а новичку отсылает какое-то количество последних сообщений в чате. Клиент же, при подключении сообщает о себе некоторую информациию, а также какое-то сообщение, идентифицирующее то, что он только что подключился.
Но помимо этого, надо не забыть о том, что клиенты могут и отключаться. То есть мы периодически должны обмениваться с клиентами сигналами (ping’овать друг друга), чтобы в случае отключения клиента (или сервера) все об этом узнали.
Итак, приступим. Создадим наш первый класс
Думаю, данный код, пока что, не нуждается в комментариях. Я не стал особо заморачиваться на обработке исключений, но нам ведь не это важно, верно?
Ах да, мы же хотели получать номер порта из конфигурационного файла. Создадим для конфига отдельный класс, в котором будем хранить статические поля — параметры нашего чата.
Удобнее всего хранить параметры в properties-файле, благо Java предоставляет удобный интерфейс для работы с ними.
Итак, наш properties-файл будет состоять всего из одной строки (пока что)
PORT=1234
Вся загрузка параметров происходит в блоке статической инициализации, поскольку полноценный конструктор в нашем случае — непозволительная роскошь.
Осталось только заменить в Server.java
ServerSocket socketListener = new ServerSocket(«1234»); на ServerSocket socketListener = new ServerSocket(Config.PORT); и добавить нужные import’ы
В дальнейшем, import’ы в коде буду упускать, поскольку любая IDE их сама подставит.
Этот класс у нас будет отвечать за прием и передачу сообщений между клиентом и сервером, а значит, самый главный класс в чате — именно ClientThread.
Поскольку он работает в отдельном потоке, первое, что мы должны сделать — написать
А теперь подумаем, что же написать в методе run().
Итак, по порядку. Для начала, мы должны получить от клиента информацию «Ты кто такой?» в противном случае — «Давай, до свидания!». Как только мы узнали кто он такой, мы должны отправить ему последние сообщения в нашем чате.
Далее, периодически, мы должны его ping’овать его каким-нибудь запросом, чтобы убедиться, что не ведем общение с трупом.
Затем, мы должны получать от него сообщения, о которых должны уведомлять всех подключенных клиентов.
Ну и, как только мы перестали получать от него запросы — клиента следует удалить из списка доступнх пользователей.
На время забудем о ClientThread, а задумаемся «Каким образом будет происходить общение?»
Мы уже решили, что будем передавать сериализованный объект. Итак, чтоже должно быть в этом объекте?
Я остановился на следующем варианте:
1. Логин пользователя, отправившего это сообщение (или Server-Bot)
2. Собственно, само сообщение
3. Время отправки
4. Список доступных серверу клиентов (для отображения у пользователя)
Для успешной сериализации/десериализации, класс в клиенте и сервере должен быть одинаковым. Поэтому позаботимся сразу и о том, и о другом.
Думаю, всё очевидно. Также, помимо сообщений мы хотим передавать нечто вроде ping’ов.
По сути, этот класс нам не сильно-то нужен, просто потом код будет удобнее читать
Итак, приступим к написанию ClientThread
Код немного комментирован, поэтому все должны разобраться. Осталось дописать только несколько функций.
Итак, начнем по порядку.
Данная функция рассылает какое-то сообщение всем клиентам
Ах да, мы еще забыли вписать некоторые поля класса ClientThread, которые активно использовали. Итак, класс ClientThread,java целиком
Осталось разобраться с функциями getUserList() и getChatHistory
Для начала, определим еще 3 класса
По сути, сервер готов к работе, осталось только немного модифицировать 2 класса.
Методы getChatHistory() и getUserList() сделаны синхронизированными, потому что с ними могут работать несколько потоков
И, доделаем наш конфиг, так как у нас добавились некоторые параметры
Заключение
Теперь наш сервер готов к использованию. Мы познакомились с сериализацией объектов и работой с сокетами в Java.
В следующей статье (завтра-послезавтра) мы напишем клиент для нашего чата, а пока жду комментариев, особенно относительно устройства чата, в частности — «Здесь абсолютно неправильно реаизовано XXX» (разумеется, жду только аргументированных оценок)
В любом случае, данная статья не призвана стать эталоном написания чата на Java, это лишь инструмент для понимания как вообще создавать такие приложения на хорошем примере, а не на эхо-чате в 10 строк.
Рекомендованный контент
Все ясно, но проблема NetBeans ругается 🙁 Можно мне архив с сорсами?)
Ты нашел решение проблемы?
Поддержу вышеотписавшегося, можете ли дать ссылку на проет?
то Игорь: Подключайте таймер из библиотеки awt.
Вопрос к автору: где клиент.
Ребят, автор выложил клиент или нет еще?
У socketListener в конструкторе указан порт в виде String.Надо бы исправить.
ServerSocket socketListener = new ServerSocket(“1234”);
у кого-нидь была проблема с timer.stop()?
как ее решить?
getUserList() не видит этот метод, предлагает его объявить.Что делать?
Где продолжение? Ссылку, пожалуйста.
Перелыл весь сайт так и не нашел продолжения этой хорошей статьи! Может кто нибудь подскажет. дайте ссылку плизззз…..
Хотя если не пытаться вдаваться в код, а просто понять общий принцип работы, то более менее понятно
Ну и где продолжение
хорошая статья, а вот продолжение где?
В пакете java.awt я не нашел ни одного таймера…
Алекс надо смотреть тут import java.awt.event.*;
В Server, там они определены. Просто из ClientThread они не видны…
Ребята кто-нибудь написал клиент к этому серверу? Поделитесь, у меня постоянно ошибки вылетают.
Ребят, вот я честно дуб-дубом в этом всем….. но мне нужна помощ как раз тех кто шарит. Хотим с кентами просто сделать свой чатик что бы там трещать на досуге, но вот проблема никто в этом не шарит, денег платить как то не охота,
кто может помочь, киньте инструкцию полную на почту tarasovdaniil22@gmail.com
Вопрос автору: а как поведёт себя сервер, если будет 10 000 000+ клиентов онлайн? сервак от количества потоков не упадёт? и сколько надо оперативной памяти под это? и ещё один вопрос не лучше ли сообщения передавать в хотя бы xml формате, на серваке брать парсером его статус(ping | text | login |…) и дальше зависимости от статуса его обрабатывать или всё таки лучше сериализованный объект передавать и на сервере не парсить а десериализовывать?
Где можно найти клиент?
ThreadClient не очень написан. Запускать поток в конструкторе как-то не очень.
Что бы не было ошибки связанной с с timer.stop() надо подключить import javax.swing.Timer;
Проблема с классом СlientTread.
Искал ошибки, но не нашел
ServerSocket socketListener = new ServerSocket(Config.PORT);
Не пойму почему ругается, в классе Server проблема, почему то не видит в классе config, не могли бы вы помочь разобраться?
В классе ClientThread не видит методы getChatHistory и getUserList. Как решить проблему?
Эти методы находятся в классе Server, и являются статическими. Вызываются так: Server.getChatHistory(), Server.getUserList().
порт в кавычках. мдаааааа….бб ламер
в общем иди нахер…твой код безнадежно устарел…в топку его и тебя
Может кому пригодится клиент 🙂
Написал его за пару часов, и опыта в работе с потоками нет, поэтому не придирайтесь.
Всего в проекте 4 файла:
public class SocketClient <
private final static String address = “127.0.0.1”; // это IP-адрес компьютера, где исполняется наша серверная программа
private final static int serverPort = 6666; // здесь обязательно нужно указать порт к которому привязывается сервер
private static String userName = “”;
static Socket socket = null;
public static void main( String[] args ) <
System.out.println(“Вас приветствует клиент чата!\n”);
System.out.println(“Введите свой ник и нажмите \”Enter\””);
// Создаем поток для чтения с клавиатуры
BufferedReader keyboard = new BufferedReader( new InputStreamReader( System.in ) );
try <
// Ждем пока пользователь введет свой ник и нажмет кнопку Enter
userName = keyboard.readLine();
System.out.println();
> catch ( IOException e )
try <
try <
InetAddress ipAddress = InetAddress.getByName( address ); // создаем объект который отображает вышеописанный IP-адрес
socket = new Socket( ipAddress, serverPort ); // создаем сокет используя IP-адрес и порт сервера
// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// Конвертируем потоки в другой тип, чтоб легче обрабатывать текстовые сообщения
ObjectOutputStream objectOutputStream = new ObjectOutputStream( outputStream );
ObjectInputStream objectInputStream = new ObjectInputStream( inputStream );
new PingThread( objectOutputStream, objectInputStream );
// Создаем поток для чтения с клавиатуры
String message = null;
System.out.println(“Наберите сообщение и нажмите \”Enter\”\n”);
public class ServerListenerThread implements Runnable <
private Thread thread = null;
private ObjectOutputStream objectOutputStream = null;
private ObjectInputStream objectInputStream = null;
public ServerListenerThread( ObjectOutputStream objectOutputStream, ObjectInputStream objectInputStream ) <
this.objectOutputStream = objectOutputStream;
this.objectInputStream = objectInputStream;
thread = new Thread( this );
thread.start();
>
@Override
public void run() <
try <
while (true) <
Message messageIn = (Message) objectInputStream.readObject();
if ( messageIn instanceof Ping ) <
Ping ping = (Ping) messageIn;
objectOutputStream.writeObject( new Ping() );
> else <
System.out.println(“[ ” + messageIn.getDate().toString() + ” ] ” + messageIn.getLogin() + ” : ” + messageIn.getMessage() );
>
>
>
catch ( SocketException e ) < e.getMessage(); >
catch ( ClassNotFoundException e ) < e.getMessage(); >
catch ( IOException e ) < e.getMessage(); >
>
>
3, 4) Это файла протокола обмена с сервером: Message.java и Ping.java
Собственно вот и весь клиент. А дальше уже сами наращивайте функционал и придумывайте GUIню какую хотите)
Клиент-сервер шаг — за — шагом, от однопоточного до многопоточного (Client-Server step by step)
Цель публикации показать начинающим Java программистам все этапы создания многопоточного сервера. Для полного понимания данной темы основная информация содержится в комментариях моего кода и в выводимых в консоли сообщениях для лучшего понимания что именно происходит и в какой именно последовательности.
В начале будет рассмотрено создание элементарного клиент-сервера, для усвоения базовых знаний, на основе которых будет строиться многопоточная архитектура.
— Потоки: для того чтобы не перепутать что именно подразумевается под потоком я буду использовать существующий в профессиональной литературе синоним — нить, чтобы не путать Stream и Thread, всё-таки более профессионально выражаться — нить, говоря про Thread.
— Сокеты(Sockets): данное понятие тоже не однозначно, поскольку в какой-то момент сервер выполняет — клиентские действия, а клиент — серверные. Поэтому я разделил понятие серверного сокета — (ServerSocket) и сокета (Socket) через который практически осуществляется общение, его будем называть сокет общения, чтобы было понятно о чём речь.
Спасибо за подсказку про Thread.sleep();!
Конечно в реальном коде Thread.sleep(); устанавливать не нужно — это моветон! В данной публикации я его использую только для того чтобы выполнение программы было нагляднее, что бы успевать разобраться в происходящем.
Так что тестируйте, изучайте и в своём коде никогда не используйте Thread.sleep();!
1) Однопоточный элементарный сервер.
2) Клиент.
3) Многопоточный сервер – сам по себе этот сервер не участвует в общении напрямую, а лишь является фабрикой однонитевых делегатов(делегированных для ведения диалога с клиентами серверов) для общения с вновь подключившимися клиентами, которые закрываются после окончания общения с клиентом.
4) Имитация множественного обращения клиентов к серверу.
Итак, начнём с изучения структуры однопоточного сервер, который может принять только одного клиента для диалога. Код приводимый ниже необходимо запускать в своей IDE в этом идея всей статьи. Предлагаю все детали уяснить из подробно задокументированного кода ниже:
Сервер запущен и находится в блокирующем ожидании server.accept(); обращения к нему с запросом на подключение. Теперь можно подключаться клиенту, напишем код клиента и запустим его. Клиент работает когда пользователь вводит что-либо в его консоли (внимание! в данном случае сервер и клиент запускаются на одном компьютере с локальным адресом — localhost, поэтому при вводе строк, которые должен отправлять клиент не забудьте убедиться, что вы переключились в рабочую консоль клиента!).
После ввода строки в консоль клиента и нажатия enter строка проверяется не ввёл ли клиент кодовое слово для окончания общения дальше отправляется серверу, где он читает её и то же проверяет на наличие кодового слова выхода. Оба и клиент и сервер получив кодовое слово закрывают ресурсы после предварительных приготовлений и завершают свою работу.
Посмотрим как это выглядит в коде:
А что если к серверу хочет подключиться ещё один клиент!? Ведь описанный выше сервер либо находится в ожидании подключения одного клиента, либо общается с ним до завершения соединения, что делать остальным клиентам? Для такого случая нужно создать фабрику которая будет создавать описанных выше серверов при подключении к сокету новых клиентов и не дожидаясь пока делегированный подсервер закончит диалог с клиентом откроет accept() в ожидании следующего клиента. Но чтобы на серверной машине хватило ресурсов для общения со множеством клиентов нужно ограничить количество возможных подключений. Фабрика будет выдавать немного модифицированный вариант предыдущего сервера(модификация будет касаться того что класс сервера для фабрики будет имплементировать интерфейс — Runnable для возможности его использования в пуле нитей — ExecutorServices). Давайте создадим такую серверную фабрику и ознакомимся с подробным описанием её работы в коде:
Для имитации множественного обращения клиентов к серверу, создадим и запустим (после запуска серверной части) фабрику Runnable клиентов которые будут подключаться серверу и писать сообщения в цикле:
Как видно из предыдущего кода фабрика запускает — TestRunnableClientTester() клиентов, напишем для них код и после этого запустим саму фабрику, чтобы ей было кого исполнять в своём пуле:
Запускайте, вносите изменения в код, только так на самом деле можно понять работу этой структуры.