说明
使用Netty框架构建的socket服务端在处理客户端请求时,每接到一个客户端的连接请求,服务端会分配一个channel处理跟该客户端的交互。如果处理该channel数据的ChannelHandler抛出异常没有捕获,那么该channel会关闭。但服务端和其它客户端的通信不受影响。
代码示例
该示例验证场景:
服务端和客户端建立了正常的连接,服务端给该客户端分配了一个channel。从该channel读取数据的ChannelHandler抛出异常,没有被捕获,导致该channel被关闭,服务端和该客户端的通信中断。但服务端和其它客户端的通信不受影响。
服务端代码片段
package com.thb.power.server;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* 服务端的主函数
* @author thb
*
*/
public class MainStation {
private static final Logger logger = LogManager.getLogger();
static final int PORT = Integer.parseInt(System.getProperty("port", "22335"));
public static void main(String[] args) throws Exception {
logger.traceEntry();
// 配置服务器
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new MainStationInitializer());
// 启动服务端
ChannelFuture f = b.bind(PORT).sync();
// 等待直到server socket关闭
f.channel().closeFuture().sync();
} finally {
// 关闭所有event loops以便终止所有的线程
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
logger.traceExit();
}
}
package com.thb.power.server;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.thb.power.constant.PowerConstants;
import com.thb.power.handler.VerifyChecksumHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class MainStationInitializer extends ChannelInitializer<SocketChannel> {
private static final Logger logger = LogManager.getLogger();
@Override
public void initChannel(SocketChannel ch) throws Exception {
logger.traceEntry();
ChannelPipeline p = ch.pipeline();
p.addLast(new LoggingHandler(LogLevel.INFO));
// 用报文开头的字符、结尾的字符作为分隔符,解析成完整的报文
p.addLast(new DelimiterBasedFrameDecoder(PowerConstants.SOCKET_MAX_BYTES_PER_PACKET, true,
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_END_MARK}),
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_START_MARK})));
p.addLast(new VerifyChecksumHandler());
logger.traceExit();
}
}
package com.thb.power.handler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.util.ReferenceCounted;
/**
* 验证接收的报文的校验和
* @author thb
*
*/
public class VerifyChecksumHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LogManager.getLogger();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.traceEntry();
if (msg instanceof ByteBuf) {
ByteBuf m = (ByteBuf)msg;
logger.info("msg reference count: {}", m.refCnt());
// 此处人为构造场景,以便验证抛出异常的情况。将引用计数减少1,执行后,引用计数变为0,读数据会抛异常
m.release();
m.readByte();
logger.info("readableBytes: " + m.readableBytes());
// 将收到的报文的每个字节转换为十六进制打印出来
logger.info(ByteBufUtil.prettyHexDump(m));
} else {
// 这个数据不需要向后传递,因为数据已经错误了
// 传入的对象msg如果实现了ReferenceCounted接口,那么显式就释放
if (msg instanceof ReferenceCounted) {
((ReferenceCounted)msg).release();
}
throw new CorruptedFrameException("received illegal data: not ByteBuf type");
}
logger.traceExit();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package com.thb.power.constant;
/**
* 保存了一些常数
* @author thb
*
*/
public final class PowerConstants {
/**
* 定义了私有构造函数,防止被外部实例化
*/
private PowerConstants() {
}
/**
* 通信报文的起始符
*/
public static final byte SOCKET_START_MARK = (byte)0x68;
/**
* 通信报文的的结束符
*/
public static final byte SOCKET_END_MARK = (byte)0x16;
/**
* 每个报文的最大字节数
*/
public static final short SOCKET_MAX_BYTES_PER_PACKET = 1024;
}
启动服务端
启动客户端,并向服务端发送数据
服务端的ChannelHandler处理数据时抛出了异常,断开了和该客户端的连接
从输出可以看出,服务端处理客户端channel数据的ChannelHandler抛出了异常,服务端终止了和该客户端的连接。
观察客户端的输出
从输出可以看出,客户端终止了和服务端的连接。
服务端可以继续接受新的客户端连接
从输出可以看出,服务端可以正常接受新的客户端的连接