свой сервер на сетевом движке Netty

Перед тем как перейти к туториалу, хочу выразить благодарность нескольким людям, и организациям за их open source, благодаря которому и написан этот туториал.
Хочу выразить благодарность:

Rena4ka — http://greencubes.org/ — http://habrahabr.ru/post/136456/
Solver — http://habrahabr.ru/post/136765/
Spout — http://www.spout.org/
Glowstone — https://github.com/SpaceManiac/Glowstone
Если бы не эти люди, думаю не было бы этого туториала.
Особая благодарность Rena4ka.

На примере туториала Rena4k’и я и пишу этот туториал, также буду добавлять и свои вещи.
Ну, думаю приступим…
И так, что же нам для всего этого нужно спросите вы?
1. Исходники CraftBukkit++ от Afforessa ТЫК.
2. Net Beans
3. Netty (в ходе статьи, расскажу как его добавить)
4. Руки
5. Малейшее понимание того, что вы делаете. (Хотя когда я первый раз пытался это сделать, я слабо понимал, что делаю)
6. Терпение и точное следование инструкции.
Первое с чего мы начнем, это скачаем архив с исходниками сервера.
Скачали? Отлично.
Распакуйте на диск D. Или туда, где нету русских букв в пути к файлу.
Открываем Net Beans… Loading… ^^
Открыли? Отлично.
Жмем на вот такую кнопочку:

И указываем путь к распакованным сорцам нашего CraftBukkit’a.
Наглядная картинка:

Открыли? Отлично.
Если вы видите вот такое:

То можно идти дальше.
Открываем наш проект CraftBukkit и переходим в любой класс.
Например откроем BlockDoor.java и видим как в строчках:
import org.bukkit.event.block.BlockRedstoneEvent;
И тому подобных подчеркнуто org.
Не обращайте на это внимания, это уберется при компилировании.
Все не достающие зависимости должны загрузиться.
Первое, что мы сделаем это перейдем по ссылке http://habrahabr.ru/post/136456/.
Почитали? Почитали.
Далее открываем файл MinecraftServer.java в нашем проекте.
Листаем вниз и видим строчки:
public MinecraftServer(OptionSet options) { // CraftBukkit – adds argument OptionSet
new ThreadSleepForever(this);

// CraftBukkit start
this.options = options;
try {
this.reader = new ConsoleReader(System.in, new PrintWriter(System.out)); // CraftBukkit – Added “System.in, new PrintWriter(System.out)” in the constuctor
} catch (IOException ex) {
Logger.getLogger(MinecraftServer.class.getName()).log(Level.SEVERE, null, ex);
}
Runtime.getRuntime().addShutdownHook(new ServerShutdownThread(this));
// CraftBukkit end
}

Дописываем в эти строчки код, который описан у Rena4k’и.
Должно получится вот так:
public MinecraftServer(OptionSet options) { // CraftBukkit – adds argument OptionSet
new ThreadSleepForever(this);

// CraftBukkit start
this.options = options;
try {
this.reader = new ConsoleReader(System.in, new PrintWriter(System.out)); // CraftBukkit – Added “System.in, new PrintWriter(System.out)” in the constuctor
} catch (IOException ex) {
Logger.getLogger(MinecraftServer.class.getName()).log(Level.SEVERE, null, ex);
}
Runtime.getRuntime().addShutdownHook(new ServerShutdownThread(this));
// CraftBukkit end

ExecutorService bossExec = new OrderedMemoryAwareThreadPoolExecutor(1, 400000000, 2000000000, 60, TimeUnit.SECONDS);
ExecutorService ioExec = new OrderedMemoryAwareThreadPoolExecutor(4, 400000000, 2000000000, 60, TimeUnit.SECONDS);
ServerBootstrap networkServer = new ServerBootstrap(new NioServerSocketChannelFactory(bossExec, ioExec, 4));
networkServer.setOption(“backlog”, 500);
networkServer.setOption(“connectTimeoutMillis”, 10000);
networkServer.setPipelineFactory(new ServerPipelineFactory());
}

Я выделил наклонным, что мы добавляем.
Заметьте я убрал:
Channel channel = networkServer.bind(new InetSocketAddress(address, port));
Так как бинд в порту и ip уже происходит где-то…
Должно получится вот так:

В этом коде:
ExecutorService ioExec = new OrderedMemoryAwareThreadPoolExecutor(4, 400000000, 2000000000, 60, TimeUnit.SECONDS);
ServerBootstrap networkServer = new ServerBootstrap(new NioServerSocketChannelFactory(bossExec, ioExec, 4));

Можно сменить кол-во потоков.
Тут стоит 4.
Если у вас получилось как и на картинке, значит можно переходить далее.
Ах да, я думаю вас не много раздражает подчеркивание красной волнистой линией разных слов аля ExecutorService?
Пропишем зависимость Netty в наш проект, нажав на лампочку мы должны увидеть вот такое:

Нажмем на это:

И у нас должно высветится вот это:

Выберем вот это:

И нажмем на:

И на “Добавить”.
Все, больше ничего с этим всем не делаем.
(Думаю знатоки заметили, что у меня библиотека Netty находится локально, но мавен все решит за вас.)
Следующим нашим действием будет создание класса ServerPipelineFactory.java
На самом деле, этот класс можно назвать как угодно, у меня он назван MinecraftPipelineFactory, но это лишние мороки, которые я не хочу описывать.
Создали новый класс ServerPipelineFactory.java?
Создается вот так:


И нажимаем “Готово”.
Откроем наш класс ServerPipelineFactory и добавим в него код:
public class ServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
PacketFrameDecoder decoder = new PacketFrameDecoder();
PacketFrameEncoder encoder = new PacketFrameEncoder();
return Channels.pipeline(decoder, encoder, new PlayerHandler(decoder, encoder));
}
}

Этот класс отвечает за обработчики, но думаю, что вам это совсем не интересно.
Тут больше ничего.
Не обращаем внимания на подчеркивания, их исправим потом.
Далее открываем класс Packet.java
Прокрутив вниз наш код, видим вот такое:

Сразу после этого, не забываем про }
Пишем вот такой код:
public static Packet read(ChannelBuffer buffer) throws IOException {
int id = buffer.readUnsignedShort();
Packet packet = getPacket(id);
if(packet == null)
throw new IOException(“Bad packet ID: ” + id);
packet.get(buffer);
return packet;
}

Должно получится вот так:

Далее пишем вот такой код:
public static Packet write(Packet packet, ChannelBuffer buffer) {
buffer.writeChar(packet.getId());
packet.send(buffer);
return packet;
}

Изначально был вот такой код:
public statuc Packet write(Packet packet, ChannelBuffer buffer) {
buffer.writeChar(packet.getId()); // Отправляем ID пакета
packet.send(buffer); // Отправляем данные пакета
}

Но видимо уважаемая Rena4ka ошиблась, и написала statuc и забыла присвоить return packet;
Но мы это исправили.
Должно получится вот так:

Далее отступим строчку и допишем вот это:
public abstract void get(ChannelBuffer buffer);

public abstract void send(ChannelBuffer buffer);

В итоге все должно получится вот так:

Если у вас все также, то можно переходить далее.
Создаем класс PlayerHandler.java
И вписываем в него вот такой код (его там дофига):
public class PlayerHandler extends SimpleChannelUpstreamHandler {

private PlayerWorkerThread worker;

@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {

worker = new PlayerWorkerThread(this, e.getChannel());
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {

worker.disconnectedFromChannel();
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {

if(e.getChannel().isOpen())
worker.acceptPacket((Packet) e.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {

ctx.getChannel().close();
}
}

Должно получится вот так:

Создадим еще класс PlayerWorkerThread он нам пригодится потом. Ничего не пишем в него. Просто пускай висит.
Далее нам нужно будет создать декодеры и енкодеры.
Для этого создадим класс PacketFrameEncoder.
И впишем в него вот такой код:
public class PacketFrameEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext channelhandlercontext, Channel channel, Object obj) throws Exception {
if(!(obj instanceof Packet))
return obj;
Packet p = (Packet) obj;

ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
Packet.write(p, buffer);
return buffer;
}
}

Должно получится вот так:

Далее создадим класс PacketFrameDecoder.
И впишем в него вот такой код:
public class PacketFrameDecoder extends ReplayingDecoder<VoidEnum> {
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
ctx.sendUpstream(e);
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
ctx.sendUpstream(e);
}
@Override
protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer, VoidEnum e) throws Exception {
return Packet.read(buffer);
}
}

Хочу вас поздравить, с частью кодинга мы окончили.
Теперь давайте исправлять все ошибки, которые мы наделали, при вписывании кода.
Начнем с MinecraftServer.java
Я один раз объясню, как их исправлять и далее буду использовать слово “лампочка” для исправления в нужных местах. В принципе все наши исправления, это есть не больше чем прописывание импортов из библиотеки Netty.
Видим, что ExecutorService, OrderedMemoryAwareThreadPoolExecutor, ServerBootstrap, NioServerSocketChannelFactory и т.п подчеркнуто, для исправления нажмем на лампочку:

И увидим вот такое:

Для начала нажмем на:

Потом на:

Потом на:

Потом на:

Потом на:

Все, пока что ничего не трогаем. Да да, ServerPipelineFactory все также подчеркнуто, но не обращаем внимания, там все само исправится.
Далее переходим в класс ServerPipelineFactory.
Нажимаем на лампочку и видим:

И нажимаем на это.
Потом на:

Потом на:

Потом на:

Тут все. Да да, там может остаться что-то подчеркнутым, но это глюки Net Beans так что, не обращаем внимания.
Далее идем в класс Packet.
Все также нажимаем на лампочку и видим:

Нажимаем на это.
Потом на:

Потом на:

Все с Packet закончили.
Я думаю, вы заметили, что классы начали быть красными, я имею в виду это:

Зайдем в класс Packet20NamedEntitySpawn.
И нажмем на лампочку и увидим это:

Нажмем на это, и к нам добавится импотр ChannelBuffer’a.
Далее идем в Packet3Chat, Packet51MapChunk и проделываем там тоже самое, что и в Packet20NamedEntitySpawn.
Но мы видим еще кое что, что в классах Packet3Chat, Packet51MapChunk в самом низу что-то подчеркнулось красным.
Видимо это (хотя ничто другое не может подчеркнуться):

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

Нажмем на это. Тоже самое проделаем и в Packet51MapChunk.
Все? Все чисто.
Далее идем в класс PlayerHandler.
Нажимаем на лампочку и видим:

Нажимаем на это.
Потом на:

Потом на:

Потом на:

Вот тут нам и понадобился наш ранее созданный класс PlayerWorkerThread.
Возвращаемся в PlayerHandler.
Потом нажимаем на:

Потом на:

Потом на:

Потом на:

Все.
Далее переходим к PacketFrameDecoder.
Все также нажимаем на лампочку и видим:

Нажимаем на это.
Потом на:

Потом на:

Потом на:

Потом на:

Потом на:

Все.
Теперь перейдем к PacketFrameEncoder.
Все также нажимаем на лампочку и видим вот это:

Нажимаем на это.
Потом на:

Потом на:

Потом на:

Потом на:

Думаю, если вы следовали моей инструкции, то все должно быть ровно и четко.
Теперь попробуйте скомпилить.
Нажав на проект и выбрав “Построить с зависимостями”.
Получилось? Отлично.
Вот сорцы того, что получилось у меня. Там же в архиве и сервер.
http://fbe.am/58P
Поддержите меня и мой проект:
R234006518840
U224504576313
Z700209618540