Это седьмой день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления
Введение
В предыдущей серии статей мы узнали базовую структуру и принцип работы Netty. Вы, ребята, не должны иметь возможность удерживать радость в своем сердце. Вы хотите начать писать код, чтобы испытать эту волшебную структуру Netty. Просто недавно токио Олимпиада Мы написали Netty клиент и сервер China Hors для Китая?
Планирование сценария
Итак, какую систему мы собираемся построить сегодня?
Прежде всего, нам нужно создать серверный сервер для обработки всех сетевых клиентских подключений и обработки сообщений, отправляемых клиентом на сервер.
Также необходимо построить клиент, который отвечает за установление соединения с сервером сервера и отправку сообщений на сервер сервера. В сегодняшнем примере после того, как клиент установит соединение, он сначала отправит серверу сообщение «Китай», а затем сервер вернет клиенту сообщение «Давай!» после получения сообщения, а затем клиент получить сообщение, а затем отправить сообщение "Китай" на сервер .... и так далее, цикл повторяется до конца Олимпиады!
Мы знаем, что обработка сообщения на клиенте и сервере осуществляется через обработчик, в обработчике мы можем переписать метод channelRead, чтобы после прочтения сообщения в канале сообщение могло быть обработано, а затем клиент Конфигурацию обработчика клиента и сервера можно запустить в Bootstrap, очень просто? Давай сделаем это вместе.
запустить сервер
Предполагая, что обработчик на стороне сервера называется CheerUpServerHandler, мы используем ServerBootstrap для создания двух групп EventLoopGroups для запуска сервера.Те, кто читал первую статью в этой серии, могут знать, что для стороны сервера необходимо запустить две группы EventLoopGroups, bossGroup, одна workerGroup, эти две Группа представляет собой отношения родитель-потомок, bossGroup отвечает за обработку проблем, связанных с подключением, а workerGroup отвечает за обработку определенных сообщений в канале.
Код для запуска службы тот же, а именно:
// Server配置
//boss loop
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//worker loop
EventLoopGroup workerGroup = new NioEventLoopGroup();
final CheerUpServerHandler serverHandler = new CheerUpServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// tcp/ip协议listen函数中的backlog参数,等待连接池的大小
.option(ChannelOption.SO_BACKLOG, 100)
//日志处理器
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
//初始化channel,添加handler
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//日志处理器
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// 启动服务器
ChannelFuture f = b.bind(PORT).sync();
// 等待channel关闭
f.channel().closeFuture().sync();
Сервисы разные, код для запуска сервера в принципе одинаков, вот на эти моменты нужно обратить внимание.
В ServerBootstrap мы добавили опцию: ChannelOption.SO_BACKLOG, ChannelOption.SO_BACKLOG соответствует параметру невыполненной работы в функции listen(int socketfd, int backlog) протокола tcp/ip, которая используется для инициализации подключаемой очереди на стороне сервера, а параметр backlog указывает размер этой очереди. Поскольку для подключения обработка клиентских запросов на подключение выполняется последовательно, поэтому одновременно может обрабатываться только одно клиентское подключение.Когда приходит несколько клиентов, сервер помещает клиентские запросы на подключение, которые не могут быть обработаны, в очередь. для обработки,
Кроме того, мы также добавили два LoggingHandler, один для обработчика и один для дочернего обработчика. LoggingHandler в основном отслеживает различные события в канале, а затем выводит соответствующие сообщения, что очень удобно в использовании.
Например, при запуске сервера выводится следующий лог:
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4] REGISTERED
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4] BIND: 0.0.0.0/0.0.0.0:8007
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4, L:/0:0:0:0:0:0:0:0:8007] ACTIVE
Этот журнал выводится первым LoggingHandler и представляет события REGISTERED, BIND и ACTIVE на стороне сервера. Из вывода видно, что сам сервер привязан к 0.0.0.0:8007.
Когда клиент начинает устанавливать соединение с сервером, выводится следующий лог:
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x37a4ba9f, L:/0:0:0:0:0:0:0:0:8007] READ: [id: 0x6dcbae9c, L:/127.0.0.1:8007 - R:/127.0.0.1:54566]
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x37a4ba9f, L:/0:0:0:0:0:0:0:0:8007] READ COMPLETE
В приведенном выше журнале показаны два события: READ и READ COMPLETE, где L:/127.0.0.1:8007 — R:/127.0.0.1:54566 означает, что порт 8007 локального сервера подключен к порту 54566 клиента.
Для второго LoggingHandler будут выводиться некоторые конкретные сообщения, связанные с обработкой сообщений. Например, REGISTERED, ACTIVE, READ, WRITE, FLUSH, READ COMPLETE и другие события здесь не перечислены.
запустить клиент
Точно так же, если предположить, что имя обработчика клиента называется ChinaClientHandler, то клиент может быть запущен как сервер следующим образом:
// 客户端的eventLoop
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//添加日志处理器
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new ChinaClientHandler());
}
});
// 启动客户端
ChannelFuture f = b.connect(HOST, PORT).sync();
Клиент запускается через Bootstrap, для него мы также настроили LoggingHandler и добавили кастомный ChinaClientHandler.
Обработка сообщений
Мы знаем, что есть два вида обработчиков: inboundHandler и outboundHandler.Здесь мы хотим отслеживать событие чтения данных из сокета, поэтому здесь и клиентские, и серверные обработчики наследуются от ChannelInboundHandlerAdapter.
Процесс обработки сообщений заключается в том, что после того, как клиент установит соединение с сервером, он сначала отправит на сервер сообщение «Китай».
После того, как клиент и сервер установят соединение, будет запущено событие channelActive, поэтому сообщения могут быть отправлены в обработчике клиента:
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush("中国");
}
На стороне сервера инициируется событие channelRead при чтении сообщений из канала, поэтому обработчик на стороне сервера может переопределить метод channelRead:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("收到消息:{}",msg);
ctx.writeAndFlush("加油!");
}
Затем клиент читает из канала «Давай!», а затем пишет в канал «Китай», поэтому клиенту также необходимо переписать метод channelRead:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.writeAndFlush("中国");
}
Можно ли это сделать в цикле?
Ловушки в обработке сообщений
На самом деле, когда вы выполните приведенный выше код, вы обнаружите, что клиент записывает сообщение «Китай» в канал, но channelRead на стороне сервера не запускается. Зачем?
Исследуй и выдай, если написанный объект является String, то внутри программы будет такая ошибка, но эта ошибка скрыта, в выводе работающей программы ты ее не увидишь, так что это все равно очень недружелюбно к начинающим друзьям из. Ошибка:
DefaultChannelPromise@57f5c075(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))
Как видно из сообщения об ошибке, в настоящее время поддерживается два типа сообщений: ByteBuf и FileRegion.
Что ж, давайте попробуем изменить тип сообщения выше на ByteBuf:
message = Unpooled.buffer(ChinaClient.SIZE);
message.writeBytes("中国".getBytes(StandardCharsets.UTF_8));
public void channelActive(ChannelHandlerContext ctx) {
log.info("可读字节:{},index:{}",message.readableBytes(),message.readerIndex());
log.info("可写字节:{},index:{}",message.writableBytes(),message.writerIndex());
ctx.writeAndFlush(message);
}
Выше мы определили объект глобального сообщения ByteBuf и отправили его на сервер, а затем, прочитав сообщение на стороне сервера, мы отправили объект глобального сообщения ByteBuf клиенту и так далее.
Но когда вы запустите вышеуказанную программу, вы обнаружите, что сервер получил «Китай», а клиент получил «Давай!», но сообщение «Китай», отправленное клиентом, не было получено сервером, как может Я отвечаю? В чем дело?
Мы знаем, что у ByteBuf есть атрибуты readableBytes, readerIndex, writableBytes, WriterIndex, capacity и refCnt, мы сравним эти свойства после сообщения перед отправкой и отправкой:
Перед отправкой сообщения:
可读字节:6,readerIndex:0
可写字节:14,writerIndex:6
capacity:20,refCnt:1
После отправки сообщения:
可读字节:6,readerIndex:0
可写字节:-6,writerIndex:6
capacity:0,refCnt:0
Итак, проблема найдена, потому что после того, как ByteBuf был обработан один раз, refCnt становится равным 0, поэтому он не может продолжать записываться снова, как это решить?
Самый простой способ — обновлять ByteBuf каждый раз, когда он отправляется, так что проблем не возникает.
Но каждый раз создавать новый объект кажется пустой тратой места, что мне делать? Поскольку refCnt стал равным 0, можем ли мы просто вызвать метод keep() в ByteBuf, чтобы увеличить refCnt?
Ответ таков, но следует отметить, что перед отправкой необходимо вызвать метод continue().Если метод continue() будет вызван после обработки сообщения, будет сообщено об исключении.
Суммировать
Что ж, запустите вышеуказанную программу, и вы всегда сможете поболеть за Китай, YYDS!
Примеры этой статьи могут относиться к:learn-netty4
Эта статья была включена вwoohoo. Флойд press.com/06-Netty-eat…
Самая популярная интерпретация, самая глубокая галантерея, самые краткие уроки и множество трюков, о которых вы не знаете, ждут вас!
Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: «Программируйте эти вещи», разбирайтесь в технологиях, лучше поймите себя!