gpt4 book ai didi

network-programming - Netty OIO 客户端-服务器在 java.net.SocketOutputStream.write(byte[]) 中仍然被阻塞

转载 作者:行者123 更新时间:2023-12-04 08:14:39 30 4
gpt4 key购买 nike

编辑: 创建了 github 存储库:https://github.com/istiillyes/client-server-netty

我使用 netty 4.0.15.Final 创建了一个客户端-服务器,并使用 OIONIO 执行了一些测试。

我要发送一些大小不一的字符串 [1KB、10KB、100KB]。

我需要服务器和客户端能够并行发送消息,因此测试如下所示:

  1. 启动服务器(创建 channel 以接受连接)
  2. 启动客户端(创建连接到服务器的 channel )
  3. 当 channel 激活时,从客户端向服务器发送 100 条消息(反之亦然)。

使用NIO,消息被传输,一切正常。

使用 OIO,服务器和客户端在一段时间后在 java.net.SocketOutputStream.wirte(byte[]) 中保持阻塞状态,并且永远不会返回。

知道为什么会这样吗?我使用 netty 的方式有问题吗?

我使用普通 Java 套接字进行了同样的测试,并且成功了。所以,我猜要么我没有正确使用 netty,要么这是一个错误。

我在此处添加了创建 channel 和 channel 处理程序的代码。

这是来自客户端的堆栈跟踪,使用 YourKit 捕获:

pool-1-thread-1 [RUNNABLE, IN_NATIVE]
java.net.SocketOutputStream.write(byte[])
io.netty.buffer.UnpooledUnsafeDirectByteBuf.getBytes(int, OutputStream, int)
io.netty.buffer.AbstractByteBuf.readBytes(OutputStream, int)
io.netty.channel.oio.OioByteStreamChannel.doWriteBytes(ByteBuf)
io.netty.channel.oio.AbstractOioByteChannel.doWrite(ChannelOutboundBuffer)
io.netty.channel.AbstractChannel$AbstractUnsafe.flush0()
io.netty.channel.AbstractChannel$AbstractUnsafe.flush()
io.netty.channel.DefaultChannelPipeline$HeadHandler.flush(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeFlush()
io.netty.channel.DefaultChannelHandlerContext.flush()
io.netty.channel.ChannelOutboundHandlerAdapter.flush(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeFlush()
io.netty.channel.DefaultChannelHandlerContext.flush()
io.netty.channel.ChannelOutboundHandlerAdapter.flush(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeFlush()
io.netty.channel.DefaultChannelHandlerContext.flush()
io.netty.handler.logging.LoggingHandler.flush(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeFlush()
io.netty.channel.DefaultChannelHandlerContext.write(Object, boolean, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.writeAndFlush(Object, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.writeAndFlush(Object)
io.netty.channel.DefaultChannelPipeline.writeAndFlush(Object)
io.netty.channel.AbstractChannel.writeAndFlush(Object)
client.ClientHandler.channelActive(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeChannelActive()
io.netty.channel.DefaultChannelHandlerContext.fireChannelActive()
io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelHandlerContext)
io.netty.handler.logging.LoggingHandler.channelActive(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeChannelActive()
io.netty.channel.DefaultChannelHandlerContext.fireChannelActive()
io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeChannelActive()
io.netty.channel.DefaultChannelHandlerContext.fireChannelActive()
io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelHandlerContext)
io.netty.channel.DefaultChannelHandlerContext.invokeChannelActive()
io.netty.channel.DefaultChannelHandlerContext.fireChannelActive()
io.netty.channel.DefaultChannelPipeline.fireChannelActive()
io.netty.channel.oio.AbstractOioChannel$DefaultOioUnsafe.connect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelPipeline$HeadHandler.connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.invokeConnect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.ChannelOutboundHandlerAdapter.connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.invokeConnect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.ChannelOutboundHandlerAdapter.connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.invokeConnect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.ChannelDuplexHandler.connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
io.netty.handler.logging.LoggingHandler.connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.invokeConnect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelHandlerContext.connect(SocketAddress, ChannelPromise)
io.netty.channel.DefaultChannelPipeline.connect(SocketAddress, ChannelPromise)
io.netty.channel.AbstractChannel.connect(SocketAddress, ChannelPromise)
io.netty.bootstrap.Bootstrap$2.run()
io.netty.channel.ThreadPerChannelEventLoop.run()
io.netty.util.concurrent.SingleThreadEventExecutor$2.run()
java.lang.Thread.run()

创建接受器 channel 的代码:

final class ServerChannelFactory {

private static final Logger LOGGER = Logger.getLogger(ServerChannelFactory.class);

protected static Channel createAcceptorChannel(
final ChannelType channelType,
final InetSocketAddress localAddress,
final ServerHandler serverHandler
) {
final ServerBootstrap serverBootstrap = ServerBootstrapFactory.createServerBootstrap(channelType);

serverBootstrap
.childHandler(new ServerChannelInitializer(serverHandler))
.option(ChannelOption.SO_BACKLOG, Resources.SO_BACKLOG);

try {
ChannelFuture channelFuture = serverBootstrap.bind(
new InetSocketAddress(localAddress.getPort())).sync();
channelFuture.awaitUninterruptibly();
if (channelFuture.isSuccess()) {
return channelFuture.channel();

} else {
LOGGER.warn(String.format("Failed to open socket! Cannot bind to port: %d!",
localAddress.getPort()));
}
} catch (InterruptedException e) {
LOGGER.error("Failed to create acceptor socket.", e);
}
return null;
}

private static class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

private ChannelHandler serverHandler;

private ServerChannelInitializer(ChannelHandler serverHandler) {
this.serverHandler = serverHandler;
}

@Override
protected void initChannel(SocketChannel ch) throws Exception {
// Encoders
ch.pipeline().addLast(Resources.STRING_ENCODER_NAME, new StringEncoder(CharsetUtil.UTF_8));
ch.pipeline().addBefore(Resources.STRING_ENCODER_NAME, Resources.FRAME_ENCODER_NAME,
new LengthFieldPrepender(Resources.FRAME_LENGTH_FIELD_SIZE));

// Decoders
ch.pipeline().addLast(Resources.STRING_DECODER_NAME, new StringDecoder(CharsetUtil.UTF_8));
ch.pipeline().addBefore(Resources.STRING_DECODER_NAME, Resources.FRAME_DECODER_NAME,
new LengthFieldBasedFrameDecoder(Resources.MAX_FRAME_LENGTH,
Resources.FRAME_LENGTH_FIELD_OFFSET, Resources.FRAME_LENGTH_FIELD_SIZE,
Resources.FRAME_LENGTH_ADJUSTMENT, Resources.FRAME_LENGTH_FIELD_SIZE));

// Handlers
ch.pipeline().addLast(Resources.LOGGING_HANDLER_NAME, new LoggingHandler());
ch.pipeline().addLast(Resources.SERVER_HANDLER_NAME, serverHandler);
}
}
}

服务器处理程序:

final class ServerHandler extends ChannelInboundHandlerAdapter {

private static final Logger LOGGER = Logger.getLogger(ServerHandler.class);
int noMessagesReceived = 0;

@Override
public void channelActive(io.netty.channel.ChannelHandlerContext ctx) throws java.lang.Exception {
for(int i=0; i< Resources.NO_MESSAGES_TO_SEND; i++) {
ctx.channel().writeAndFlush(MessageStore.getMessage(i));
}
}

@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
noMessagesReceived++;
if(noMessagesReceived == Resources.NO_MESSAGES_TO_SEND) {
ctx.channel().writeAndFlush(MessageStore.getMessage(0));
}
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
FileUtils.write(Resources.SERVER_OUTPUT, String.format("Received %d messages", noMessagesReceived));
}

@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
LOGGER.error(String.format("Exception in %s", this.getClass().getName()), cause);
}
}

服务器引导工厂:

public class ServerBootstrapFactory {

private ServerBootstrapFactory() {
}

public static ServerBootstrap createServerBootstrap(final ChannelType channelType) throws UnsupportedOperationException {
ServerBootstrap serverBootstrap = new ServerBootstrap();

switch (channelType) {
case NIO:
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup());
serverBootstrap.channel(NioServerSocketChannel.class);
return serverBootstrap;

case OIO:
serverBootstrap.group(new OioEventLoopGroup(), new OioEventLoopGroup());
serverBootstrap.channel(OioServerSocketChannel.class);
return serverBootstrap;

default:
throw new UnsupportedOperationException("Failed to create ServerBootstrap, " + channelType + " not supported!");
}
}
}

创建连接器 channel 的代码:

final class ClientChannelFactory {

private static final Logger LOGGER = Logger.getLogger(ClientChannelFactory.class);

protected static Channel createConnectorChannel(
ChannelType channelType,
final ClientHandler clientHandler,
InetSocketAddress remoteAddress
) {
final Bootstrap bootstrap = BootstrapFactory.createBootstrap(channelType);

bootstrap.handler(new ClientChannelInitializer(clientHandler));

try {
final ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(remoteAddress.getAddress(), remoteAddress.getPort()))
.sync();
channelFuture.awaitUninterruptibly();
if (channelFuture.isSuccess()) {
return channelFuture.channel();

} else {
LOGGER.warn(String.format(
"Failed to open socket! Cannot connect to ip: %s port: %d!",
remoteAddress.getAddress(), remoteAddress.getPort())
);
}
} catch (InterruptedException e) {
LOGGER.error("Failed to open socket!", e);
}
return null;
}

private static class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {

private ChannelHandler clientHandler;

private ClientChannelInitializer(ChannelHandler clientHandler) {
this.clientHandler = clientHandler;
}

@Override
protected void initChannel(SocketChannel ch) throws Exception {
// Encoders
ch.pipeline().addLast(Resources.STRING_ENCODER_NAME, new StringEncoder(CharsetUtil.UTF_8));
ch.pipeline().addBefore(Resources.STRING_ENCODER_NAME, Resources.FRAME_ENCODER_NAME,
new LengthFieldPrepender(Resources.FRAME_LENGTH_FIELD_SIZE));

// Decoders
ch.pipeline().addLast(Resources.STRING_DECODER_NAME, new StringDecoder(CharsetUtil.UTF_8));
ch.pipeline().addBefore(Resources.STRING_DECODER_NAME, Resources.FRAME_DECODER_NAME,
new LengthFieldBasedFrameDecoder(Resources.MAX_FRAME_LENGTH,
Resources.FRAME_LENGTH_FIELD_OFFSET, Resources.FRAME_LENGTH_FIELD_SIZE,
Resources.FRAME_LENGTH_ADJUSTMENT, Resources.FRAME_LENGTH_FIELD_SIZE));

// Handlers
ch.pipeline().addLast(Resources.LOGGING_HANDLER_NAME, new LoggingHandler());
ch.pipeline().addLast(Resources.CLIENT_HANDLER_NAME, clientHandler);
}
}
}

客户端处理程序:

public final class ClientHandler extends ChannelInboundHandlerAdapter {

private static final Logger LOGGER = Logger.getLogger(ClientHandler.class);
private int noMessagesReceived = 0;

@Override
public void channelActive(io.netty.channel.ChannelHandlerContext ctx) throws java.lang.Exception {
for(int i=0; i< Resources.NO_MESSAGES_TO_SEND; i++) {
ctx.channel().writeAndFlush(MessageStore.getMessage(i));
}
}

@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
noMessagesReceived++;
if (noMessagesReceived > Resources.NO_MESSAGES_TO_SEND) {
ctx.channel().close();
}
}

@Override
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
FileUtils.write(Resources.CLIENT_OUTPUT, String.format("Received %d messages", noMessagesReceived));
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.error(String.format("Exception in %s", this.getClass().getName()), cause);
}
}

引导工厂:

public class BootstrapFactory {

private BootstrapFactory() {
}

public static Bootstrap createBootstrap(final ChannelType channelType) throws UnsupportedOperationException {
Bootstrap bootstrap = new Bootstrap();

switch (channelType) {
case NIO:
bootstrap.group(new NioEventLoopGroup());
bootstrap.channel(NioSocketChannel.class);
return bootstrap;

case OIO:
bootstrap.group(new OioEventLoopGroup());
bootstrap.channel(OioSocketChannel.class);
return bootstrap;

default:
throw new UnsupportedOperationException("Failed to create Bootstrap, " + channelType + " not supported!");
}
}
}

channel 类型:

public enum ChannelType {

// New IO - non-blocking
NIO,

// Old IO - blocking
OIO;
}

最佳答案

由于Netty的OIO transport是在同一个线程中进行读和写的,所以在写的过程中不会读。

问题是,如果客户端和服务器都使用 OIO 传输实现,它们最终可能会互相写入并等待对方读取它们正在写入的内容。

解决方法是 1) 至少在一侧使用 NIO,或者 2) 非常小心不要将对等方的套接字接收缓冲区填满到其最大大小。实际上,(2) 不是很容易实现,因此始终建议至少在服务器端使用 NIO 传输。

关于network-programming - Netty OIO 客户端-服务器在 java.net.SocketOutputStream.write(byte[]) 中仍然被阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21998670/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com