Netty官方 入门示例详解

different kind of transports. We are implementing a server-side  
application in this example, and therefore two NioEventLoopGroup will  
be used. The first one, often called ‘boss’, accepts an incoming  
connection. The second one, often called ‘worker’, handles the traffic  
of the accepted connection once the boss accepts the connection and  
registers the accepted connection to the worker. How many Threads are  
used and how they are mapped to the created Channels depends on the  
EventLoopGroup implementation and may be even configurable via a  
constructor.
  1. ServerBootstrap is a helper class that sets up a server. You can set up the server using a Channel directly. However, please note that
this is a tedious process, and you do not need to do that in most  
cases.
  1. Here, we specify to use the NioServerSocketChannel class which is used to instantiate a new Channel to accept incoming connections.
  1. The handler specified here will always be evaluated by a newly accepted Channel. The ChannelInitializer is a special handler that is
purposed to help a user configure a new Channel. It is most likely  
that you want to configure the ChannelPipeline of the new Channel by  
adding some handlers such as DiscardServerHandler to implement your  
network application. As the application gets complicated, it is likely  
that you will add more handlers to the pipeline and extract this  
anonymous class into a top-level class eventually.
  1. You can also set the parameters which are specific to the Channel implementation. We are writing a TCP/IP server, so we are allowed to
set the socket options such as tcpNoDelay and keepAlive. Please refer  
to the apidocs of ChannelOption and the specific ChannelConfig  
implementations to get an overview about the supported ChannelOptions.
  1. Did you notice option() and childOption()? option() is for the NioServerSocketChannel that accepts incoming connections.
childOption() is for the Channels accepted by the parent  
ServerChannel, which is NioServerSocketChannel in this case.
  1. We are ready to go now. What’s left is to bind to the port and to start the server. Here, we bind to the port 8080 of all NICs (network
interface cards) in the machine. You can now call the bind() method as  
many times as you want (with different bind addresses.)

Congratulations! You’ve just finished your first server on top of

Netty

  1. NioEventLoopGroup是处理I / O操作的多线程事件循环。NettyEventLoopGroup为不同类型的传输提供了各种实现。在此示例中,我们正在实现服务器端应用程序,因此NioEventLoopGroup将使用两个。第一个通常称为“boss,接受传入的连接。第二个通常称为“worker”,一旦boss接受了连接并且向工作进程worker注册接受的连接,便处理已接受连接的流量。使用多少线程以及如何将它们映射到创建的Channels取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。

  2. ServerBootstrap是设置服务器的帮助程序类。您可以Channel直接使用直接设置服务器。但是,请注意,这是一个单调乏味的过程,在大多数情况下,您无需这样做。

  3. 在这里,我们指定使用NioServerSocketChannel用于实例化新的类Channel以接受传入的连接。

  4. 在此指定的处理程序将始终由新接受的评估Channel。该ChannelInitializer是意要帮助用户配置一个新的特殊处理程序Channel。您很可能希望通过添加一些处理程序(例如实现您的网络应用程序)来配置ChannelPipeline新Channel的DiscardServerHandler。随着应用程序变得复杂,您可能会向管道添加更多处理程序,并最终将此匿名类提取到顶级类中。

  5. 您还可以设置特定于Channel实现的参数。我们正在编写一个TCP / IP服务器,因此可以设置诸如tcpNoDelay和的套接字选项keepAlive。请参考的apidocsChannelOption和特定的ChannelConfig实现以获取有关support的概述ChannelOption。

  6. 您注意到option()和childOption()了吗?option()用于接受传入连接的NioServerSocketChannel。childOption()用于父ServerChannel接受的通道,在本例中是NioServerSocketChannel。

  7. 我们现在准备开始。剩下的就是绑定到端口并启动服务器。在这里,我们绑定到计算机8080中所有NIC(网络接口卡)的端口。现在,您可以根据需要bind()多次调用该方法(使用不同的绑定地址。)

恭喜你!您刚刚在Netty上完成了第一台服务器。

查看接收到的数据

现在,我们已经编写了第一台服务器,我们需要测试它是否确实有效。测试它的最简单方法是使用telnet命令。例如,您可以telnet localhost 8080在命令行中输入并输入一些内容。

但是,我们可以说服务器工作正常吗?我们真的不知道这是因为它是一个废弃服务器。您根本不会得到任何回应。为了证明它确实有效,让我们修改服务器以打印收到的内容。

我们已经知道,channelRead()每当收到数据时都会调用该方法。让我们将一些代码放入DiscardServerHandler的channelRead()方法中:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ByteBuf in = (ByteBuf) msg;

try {

while (in.isReadable()) { // (1)

System.out.print((char) in.readByte());

System.out.flush();

}

} finally {

ReferenceCountUtil.release(msg); // (2)

}

}

  1. 这个低效的循环实际上可以简化为: System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))

  2. 或者,您可以在in.release()这里进行。

如果再次运行telnet命令,您将看到服务器打印收到的内容。

丢弃服务器的完整源代码位于io.netty.example.discard分发包中。

编写一个Echo服务器

到目前为止,我们一直在使用数据而没有任何响应。但是,服务器通常应该响应请求。让我们学习如何通过实现ECHO协议将响应消息写入客户端,在此将所有接收到的数据发送回去。

与前面几节中实现的丢弃服务器的唯一区别在于,它会将接收到的数据发回,而不是将接收到的数据打印到控制台。因此,再次修改该channelRead()方法就可以了:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ctx.write(msg); // (1)

ctx.flush(); // (2)

}

  1. A ChannelHandlerContext object provides various operations that enable you to trigger various I/O events and operations. Here, we
invoke write(Object) to write the received message in verbatim. Please  
note that we did not release the received message unlike we did in the  
DISCARD example. It is because Netty releases it for you when it is  
written out to the wire.
  1. ctx.write(Object) does not make the message written out to the wire. It is buffered internally and then flushed out to the wire by
ctx.flush(). Alternatively, you could call ctx.writeAndFlush(msg) for  
brevity.

If you run the telnet command again, you will see the server sends

back whatever you have sent to it.

The full source code of the echo server is located in the

io.netty.example.echo package of the distribution.

  1. 一个ChannelHandlerContext对象提供的各种操作,使您可以触发各种I / O的事件和操作。在这里,我们调用write(Object)以逐字书写接收到的消息。请注意,我们没有像DISCARD示例中那样释放收到的消息。这是因为Netty在将其写到网络时会为您释放它。

  2. ctx.write(Object)不会将消息写到网络上。它在内部进行缓冲,然后通过ctx.flush()冲洗到网络上。或者,为了简洁些,您可以调用ctx.writeAndFlush(msg)。

如果再次运行telnet命令,您将看到服务器发送回您发送给它的任何内容。

回显服务器的完整源代码位于io.netty.example.echo分发包中。

编写时间服务器

The protocol to implement in this section is the TIME protocol. It is

different from the previous examples in that it sends a message, which

contains a 32-bit integer, without receiving any requests and closes

the connection once the message is sent. In this example, you will

learn how to construct and send a message, and to close the connection

on completion.

Because we are going to ignore any received data but to send a message

as soon as a connection is established, we cannot use the

channelRead() method this time. Instead, we should override the

channelActive() method. The following is the implementation:

本节中要实现的协议是TIME协议。它与前面的示例不同之处在于,它不包含任何请求就发送包含32位整数的消息,并在发送消息后关闭连接。在此示例中,您将学习如何构造和发送消息,以及如何在完成时关闭连接。

因为我们将忽略任何接收到的数据,而是在建立连接后立即发送消息,所以这次我们不能使用channelRead()该方法。相反,我们应该重写该channelActive()方法。以下是实现:

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelActive(final ChannelHandlerContext ctx) { // (1)

final ByteBuf time = ctx.alloc().buffer(4); // (2)

time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));

final ChannelFuture f = ctx.writeAndFlush(time); // (3)

f.addListener(new ChannelFutureListener() {

@Override

public void operationComplete(ChannelFuture future) {

assert f == future;

ctx.close();

}

}); // (4)

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

cause.printStackTrace();

ctx.close();

}

}

  1. 如所解释的,channelActive()当建立连接并准备产生流量时,将调用该方法。让我们在该方法写一个代表当前时间的32位整数。

  2. 要发送新消息,我们需要分配一个包含消息的新缓冲区。我们将要写入一个32位整数,因此我们需要一个ByteBuf容量至少为4个字节。通过ChannelHandlerContext.alloc()获取ByteBufAllocator并分配一个新的缓冲区。

  3. 和往常一样,我们编写构造的消息。

但是,等等,翻转在哪里?java.nio.ByteBuffer.flip()在NIO中发送消息之前,我们不是已经调用过了java.nio.ByteBuffer.flip()在NIO中发送消息之前?ByteBuf没有这样的方法,因为它有两个指针:一个用于读操作,另一个用于写操作。当您向ByteBuf写入内容时,writer索引会增加,而reader索引不变。reader索引和writer索引分别表示消息的开始和结束位置。

相比之下,NIO缓冲区没有提供一种清晰的方法来确定消息内容在哪里开始和结束,而不用调用flip方法。如果您忘记翻转缓冲区,因为不会发送任何内容或不正确的数据,您将陷入麻烦。这样的错误在Netty中不会发生,因为我们对不同的操作类型有不同的指针。你会发现,当你习惯它时,它会让你的生活变得更轻松——一种没有flip的环境!

需要注意的另一点是ChannelHandlerContext.write()(和writeAndFlush())方法返回一个ChannelFuture。AChannelFuture表示尚未发生的I / O操作。这意味着,由于Netty中的所有操作都是异步的,因此可能尚未执行任何请求的操作。例如,以下代码甚至在发送消息之前就可能关闭连接:

Channel ch = …;

ch.writeAndFlush(message);

ch.close();

因此,您需要在ChannelFuture完成后调用close()方法,该方法通过write()方法返回,并在完成写操作后通知其侦听器。请注意,close()也可能不会立即关闭连接,并且返回ChannelFuture。

How do we get notified when a write request is finished then? This is

as simple as adding a ChannelFutureListener to the returned

ChannelFuture. Here, we created a new anonymous ChannelFutureListener

which closes the Channel when the operation is done.

Alternatively, you could simplify the code using a pre-defined

listener:

当写请求完成时,我们如何得到通知?这就像将添加一个ChannelFutureListener到return ChannelFuture一样简单。在这里,我们创建了一个新的匿名类操作ChannelFutureListener,该匿名类Channel操作将在操作完成时关闭。

另外,您可以使用预定义的侦听器简化代码:

f.addListener(ChannelFutureListener.CLOSE);

要测试我们的时间服务器是否按预期工作,可以使用UNIXrdate命令:

$ rdate -o -p

其中是您在main()方法中指定的端口号,通常为localhost。

编写时间客户端

与DISCARD和ECHO服务器不同,我们需要该TIME协议的客户端,因为人类无法将32位二进制数据转换为日历中的日期。在本节中,我们讨论如何确保服务器正常工作,并学习如何使用Netty编写客户端。

服务器和Netty中的客户机之间的最大和唯一的区别是不同的Bootstrap,与 使用的Channel实现类。请看下面的代码:

public class TimeClient {

public static void main(String[] args) throws Exception {

String host = args[0];

int port = Integer.parseInt(args[1]);

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

Bootstrap b = new Bootstrap(); // (1)

b.group(workerGroup); // (2)

b.channel(NioSocketChannel.class); // (3)

b.option(ChannelOption.SO_KEEPALIVE, true); // (4)

b.handler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast(new TimeClientHandler());

}

});

// Start the client.

ChannelFuture f = b.connect(host, port).sync(); // (5)

// Wait until the connection is closed.

f.channel().closeFuture().sync();

} finally {

workerGroup.shutdownGracefully();

}

}

}

  1. BootstrapServerBootstrap除了用于非服务器通道(例如客户端通道或无连接通道)之外,其他方面与之相似。

  2. 如果仅指定一个EventLoopGroup,它将同时用作老板组和工人组。但是,老板工人并不用于客户端。

  3. 相反的NioServerSocketChannel,NioSocketChannel被用来创建一个客户端Channel。

  4. 请注意,与ServerBootstrap不同,我们在这里没有使用childOption(),因为客户端SocketChannel没有父级。

  5. 我们应该调用connect()方法而不是bind()方法。

如您所见,它与服务器端代码并没有真正的区别。怎么样实现ChannelHandler?它应该从服务器接收一个32位整数,将其转换为人类可读的格式,打印转换后的时间,然后关闭连接:

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ByteBuf m = (ByteBuf) msg; // (1)

try {

long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L;

System.out.println(new Date(currentTimeMillis));

ctx.close();

} finally {

m.release();

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

cause.printStackTrace();

ctx.close();

}

}

  1. 在TCP / IP中,Netty将从对等方发送的数据读入ByteBuf。

它看起来非常简单,并且与服务器端示例没有任何不同。但是,此处理程序有时会拒绝工作IndexOutOfBoundsException。我们将在下一节讨论为什么会发生这种情况。

处理基于流的传输

套接字缓冲区的一个小警告

在基于流的传输中(例如TCP / IP),将接收到的数据存储到套接字接收缓冲区中。不幸的是,基于流的传输的缓冲区不是数据包队列而是字节队列。这意味着,即使您将两个消息作为两个独立的数据包发送,操作系统也不会将它们视为两个消息,而只是一堆字节。因此,不能保证您阅读的内容完全是您的远程对等方写的内容。例如,让我们假设操作系统的TCP / IP堆栈已收到三个数据包:

由于基于流的协议具有此一般属性,因此很有可能在您的应用程序中以以下分段形式读取它们:

因此,无论是服务器端还是客户端,接收方都应将接收到的数据整理到一个或多个有意义的帧中,以使应用程序逻辑易于理解。在上面的示例中,接收到的数据应采用以下格式:

第一个解决方案

现在让我们回到TIME客户示例。我们在这里有同样的问题。32位整数是非常少量的数据,并且不太可能经常碎片化。但是,问题在于它可以被碎片化,并且碎片化的可能性会随着流量的增加而增加。

一种简单的解决方案是创建一个内部累积缓冲区,然后等待直到所有4个字节都被接收到内部缓冲区中为止。以下是修改后的TimeClientHandler实现,可以解决此问题:

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

private ByteBuf buf;

@Override

public void handlerAdded(ChannelHandlerContext ctx) {

buf = ctx.alloc().buffer(4); // (1)

}

@Override

public void handlerRemoved(ChannelHandlerContext ctx) {

buf.release(); // (1)

buf = null;

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ByteBuf m = (ByteBuf) msg;

buf.writeBytes(m); // (2)

m.release();

if (buf.readableBytes() >= 4) { // (3)

long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L;

System.out.println(new Date(currentTimeMillis));

ctx.close();

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

cause.printStackTrace();

ctx.close();

}

}

  1. A ChannelHandler has two life cycle listener methods: handlerAdded() and handlerRemoved(). You can perform an arbitrary
(de)initialization task as long as it does not block for a long time.
  1. First, all received data should be cumulated into buf.
  1. And then, the handler must check if buf has enough data, 4 bytes in this example, and proceed to the actual business logic. Otherwise,
Netty will call the channelRead() method again when more data arrives,  
and eventually all 4 bytes will be cumulated.
  1. 一个ChannelHandler有两个生命周期侦听器方法:handlerAdded()和handlerRemoved()。您可以执行任意(取消)初始化任务,只要它不会长时间阻塞即可。

  2. 首先,应将所有接收到的数据累加到buf中。

  3. 然后,处理程序必须检查是否buf有足够的数据(在此示例中为4个字节),然后继续进行实际的业务逻辑。否则,当有更多数据到达时,Netty将再次调用channelRead()方法,最终所有4个字节将被累加。

第二种解决方案

Although the first solution has resolved the problem with the TIME

client, the modified handler does not look that clean. Imagine a more

complicated protocol which is composed of multiple fields such as a

variable length field. Your ChannelInboundHandler implementation will

become unmaintainable very quickly.

As you may have noticed, you can add more than one ChannelHandler to a

ChannelPipeline, and therefore, you can split one monolithic

ChannelHandler into multiple modular ones to reduce the complexity of

your application. For example, you could split TimeClientHandler into

two handlers:

  • TimeDecoder which deals with the fragmentation issue, and
  • the initial simple version of TimeClientHandler.

Fortunately, Netty provides an extensible class which helps you write

the first one out of the box:

尽管第一个解决方案已经解决了TIME客户端的问题,但修改后的处理程序看起来并不干净。想象一个更复杂的协议,它由多个字段组成,例如可变长度字段。您的ChannelInboundHandler实现将很快变得难以维护。

正如你可能已经注意到,您可以添加多个ChannelHandler到ChannelPipeline,因此,您可以拆分一个单片ChannelHandler成多个模块 减少了应用程序的复杂性。例如,您可以分为TimeClientHandler两个处理程序:

  • TimeDecoder 处理碎片问题

  • TimeClientHandler的初始简单版本

幸运的是,Netty提供了一个可扩展的类,可以帮助您开箱即用地编写第一个类:

public class TimeDecoder extends ByteToMessageDecoder { // (1)

@Override

protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { // (2)

if (in.readableBytes() < 4) {

return; // (3)

}

out.add(in.readBytes(4)); // (4)

}

}

  1. ByteToMessageDecoder是一个ChannelInboundHandler的实现类,可以轻松处理碎片问题。

  2. ByteToMessageDecoderdecode()每当接收到新数据时,都使用内部维护的累积缓冲区调用该方法。

  3. decode()``out当累积缓冲区中没有足够的数据时,可以决定不添加任何内容。收到更多数据时ByteToMessageDecoder将decode()再次调用。

  4. 如果decode()将对象添加到out,则表示解码器成功解码了一条消息。ByteToMessageDecoder将丢弃累积缓冲区的读取部分。请记住,您不需要解码多条消息。ByteToMessageDecoder会一直调用该decode()方法,直到该方法不添任何内容到out中。

现在我们要在ChannelPipeline中插入另一个处理程序,我们应该在TimeClient中修改ChannelInitializer实现:

b.handler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());

}

});

如果您是一个冒险的人,则可能需要尝试,ReplayingDecoder它可以进一步简化解码器。不过,您将需要查阅API参考以获取更多信息。

public class TimeDecoder extends ReplayingDecoder {

@Override

protected void decode(

ChannelHandlerContext ctx, ByteBuf in, List out) {

out.add(in.readBytes(4));

}

}

此外,Netty提供了开箱即用的解码器,使您能够非常轻松地实现大多数协议,并避免最终以单一的,不可维护的处理程序实现而告终。请参考以下软件包以获取更多详细示例:

  • io.netty.example.factorial 对于二进制协议

  • io.netty.example.telnet 用于基于文本行的协议。

用POJO代替 ByteBuf


到目前为止,我们已经回顾的所有示例都使用aByteBuf作为协议消息的主要数据结构。在本节中,我们将改进TIME协议客户端和服务器示例,以使用POJO而不是ByteBuf。

在ChannelHandlers中使用POJO的优势是显而易见的;通过将从ByteBuf中提取信息的代码从处理程序中分离出来,处理程序变得更加可维护和可重用。在TIME客户机和服务器示例中,我们只读取一个32位整数,直接使用ByteBuf不是一个主要问题。但是,您会发现在实现一个真实的协议时,有必要进行分离。

首先,让我们定义一个名为的新类型UnixTime。

import java.util.Date;

public class UnixTime {

private final long value;

public UnixTime() {

this(System.currentTimeMillis() / 1000L + 2208988800L);

}

public UnixTime(long value) {

this.value = value;

}

public long value() {

return value;

}

@Override

public String toString() {

return new Date((value() - 2208988800L) * 1000L).toString();

}

}

现在,我们可以修改,TimeDecoder以产生UnixTime而不是ByteBuf。

@Override

protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) {

if (in.readableBytes() < 4) {

return;

}

out.add(new UnixTime(in.readUnsignedInt()));

}

使用修改的解码器,TimeClientHandler不再使用ByteBuf:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

UnixTime m = (UnixTime) msg;

System.out.println(m);

ctx.close();

}

简单又优雅,对吧?同样的技术也可以应用到服务器端。这次我们先更新TimeServerHandler:

@Override

public void channelActive(ChannelHandlerContext ctx) {

ChannelFuture f = ctx.writeAndFlush(new UnixTime());

f.addListener(ChannelFutureListener.CLOSE);

}

现在,唯一缺少的是一个编码器,它是ChannelOutboundHandler的实现,它将UnixTime转换回ByteBuf。这比编写解码器要简单得多,因为在编码消息时不需要处理数据包碎片和组装。

package io.netty.example.time;

public class TimeEncoder extends ChannelOutboundHandlerAdapter {

@Override

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {

UnixTime m = (UnixTime) msg;

ByteBuf encoded = ctx.alloc().buffer(4);

encoded.writeInt((int)m.value());

ctx.write(encoded, promise); // (1)

}

}

  1. 这一行有很多重要的东西。

首先,我们将原始ChannelPromise按原样传递,以便Netty在编码数据实际写入到线路时将其标记为成功或失败。

第二,我们没有打电话ctx冲洗(). 有一个单独的处理程序方法void flush(ChannelHandlerContext ctx),用于覆盖flush()操作。

为了进一步简化,您可以使用MessageToByteEncoder:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ct msg, ChannelPromise promise) {

UnixTime m = (UnixTime) msg;

ByteBuf encoded = ctx.alloc().buffer(4);

encoded.writeInt((int)m.value());

ctx.write(encoded, promise); // (1)

}

}

  1. 这一行有很多重要的东西。

首先,我们将原始ChannelPromise按原样传递,以便Netty在编码数据实际写入到线路时将其标记为成功或失败。

第二,我们没有打电话ctx冲洗(). 有一个单独的处理程序方法void flush(ChannelHandlerContext ctx),用于覆盖flush()操作。

为了进一步简化,您可以使用MessageToByteEncoder:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-egEQv3V0-1715854211895)]

[外链图片转存中…(img-XHsnIMUV-1715854211896)]

[外链图片转存中…(img-nheASwTd-1715854211896)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个 Netty WebSocket 示例代码,它使用了 Netty 4.x 版本的 API: ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; public class WebSocketServer { private final int port; public WebSocketServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec(), new HttpObjectAggregator(65536), new WebSocketServerProtocolHandler("/websocket"), new WebSocketFrameHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; new WebSocketServer(port).run(); } } ``` 上述代码创建了一个 WebSocket 服务器,监听指定端口,使用了 Netty 的 API 实现,具体如下: 1. 创建了两个 `EventLoopGroup`,一个用于处理连接,另一个用于处理请求; 2. 创建了一个 `ServerBootstrap`,它是用于启动服务器的; 3. 配置了服务器参数,包括使用 NIO、设置连接和请求处理的线程组、设置 Channel 型为 `NioServerSocketChannel`、设置连接参数(backlog 和 keepalive)等; 4. 添加了一个 `ChannelInitializer`,它是用于初始化新连接的 Channel 的,它会添加一系列的 ChannelHandler,包括 HTTP 编解码器、HTTP 消息聚合器、WebSocket 协议处理器和自定义的 WebSocket 帧处理器; 5. 绑定了端口并启动服务器; 6. 最后,关闭线程组。 下面是自定义的 `WebSocketFrameHandler` ,它继承了 Netty 的 `SimpleChannelInboundHandler` ,用于处理 WebSocket 帧: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; public class WebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> { @Override protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof TextWebSocketFrame) { handleTextFrame(ctx, (TextWebSocketFrame) frame); } else if (frame instanceof BinaryWebSocketFrame) { handleBinaryFrame(ctx, (BinaryWebSocketFrame) frame); } else { throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName())); } } private void handleTextFrame(ChannelHandlerContext ctx, TextWebSocketFrame frame) { // 处理文本帧 String text = frame.text(); System.out.println("Received text: " + text); ctx.channel().writeAndFlush(new TextWebSocketFrame("Echo: " + text)); } private void handleBinaryFrame(ChannelHandlerContext ctx, BinaryWebSocketFrame frame) { // 处理二进制帧 byte[] data = frame.content().array(); System.out.println("Received binary data: " + data); ctx.channel().writeAndFlush(new BinaryWebSocketFrame(frame.content())); } } ``` 上述代码中的 `WebSocketFrameHandler` 重写了 Netty 的 `SimpleChannelInboundHandler` 的 `channelRead0` 方法,用于处理接收到的 WebSocket 帧。它通过判断帧的型,分别处理文本帧和二进制帧,最后将处理结果写回客户端。在这个示例中,处理结果是接收到的数据的回显。 需要注意的是,在使用 WebSocket 时,客户端和服务器之间的数据传输是基于帧(Frame)的,每个帧包含了帧头和帧体两部分。帧头包含了帧的型、长度、是否是结束帧等信息,而帧体则是实际的数据内容。在上述代码中,我们分别处理了文本帧和二进制帧,并将处理结果写回客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值