@Configuration
public class WebSocketConfiguration {
@Autowired
private NettyServerProperties properties;
@Autowired
private ConfigConfiguration config;
/**
* 启动netty服务 在生成bean的时候调用该bean的start方法
* @return
*/
@Bean(initMethod = "start", destroyMethod = "stop")
@ConditionalOnMissingBean //用来检查容器中是否有该bean对象,如果有就不进行操作没有就初始化创建一个新的
@ConditionalOnProperty(prefix = "netty.server", value = "enabled", havingValue = "true")
NettyServer nettyServer() {
return new NettyServer(properties);
}
/**
* 后续的HTTP 访问完全依靠这个类生成 AsyncHttpClient 对象
*
* @param
* @return
*/
@Bean(destroyMethod = "close")
public AsyncHttpClient asyncHttpClient(AsyncHttpClientConfig config) {
return new DefaultAsyncHttpClient(config);
}
@Bean
public AsyncHttpClientConfig clientConfig() {
AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
.setWebSocketMaxFrameSize(properties.getMaxSize())
.build();
return config;
}
/**
* 工作线程池
* @return
*/
@Bean(destroyMethod = "shutdown")
public ExecutorService executorService() {
return new TraceExecutorService(new ThreadPoolExecutor(config.getCorePoolSize(),
config.getMaxPoolSize(),
config.getIdleTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(config.getBlockingDequeSize()),
new ThreadFactory() {
int threadId = 1;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(new MDCContinueRunableDecorator(r));
t.setName("worker thread " + (threadId++));
t.setDaemon(true);
if (threadId > 100000)
threadId = 1;
return t;
}
}));
}
//Netty服务配置
public class NettyServer implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private NettyServerProperties properties;
private Channel serverChannel;
private ApplicationContext applicationContext;
@Autowired
private ConfigConfiguration config;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public NettyServer(NettyServerProperties properties) {
this.properties = properties;
}
public void start() {
//需要开启一个新的线程来执行netty server 服务器
Thread t = new Thread(() -> startServer());
//t.setDaemon(true);
t.setName("netty thread");
t.start();
}
public void stop() {
if (serverChannel != null) {
try {
serverChannel.close().get(2L, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
logger.error("Interrupt connection abnormality... {}", e);
throw new InternalSystemException(Constants.BOT_VOICE_CORE, "Interrupt connection abnormality...", e);
}
serverChannel = null;
}
}
private void startServer() {
//服务端需要2个线程组 boss处理客户端连接 work进行服务端连接之后的处理
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup(config.getNettyWorkThreads());
try {
ServerBootstrap bootstrap = new ServerBootstrap();
//服务器 配置
bootstrap.group(boss,work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
// HttpServerCodec:将请求和应答消息解码为HTTP消息
socketChannel.pipeline().addLast("http-codec",new HttpServerCodec());
// HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
socketChannel.pipeline().addLast("aggregator",new HttpObjectAggregator(properties.getMaxSize()));
// ChunkedWriteHandler:向客户端发送HTML5文件
socketChannel.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
// 进行设置心跳检测
socketChannel.pipeline().addLast(new IdleStateHandler(properties.getIdleTime(),
properties.getIdleTime(), properties.getIdleTime()*10, TimeUnit.SECONDS));
// 配置通道处理 来进行业务处理增加ChannelPipeling
socketChannel.pipeline().addLast(applicationContext.getBean(WebSocketServerHandler.class));
}
//ChannelOption.SO_BACKLOG, 1024
BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的 请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
}).option(ChannelOption.SO_BACKLOG,1024).childOption(ChannelOption.SO_KEEPALIVE,true);
//绑定端口 开启事件驱动
logger.info("【服务器启动成功========端口:" + properties.getPort() + "】");
serverChannel = bootstrap.bind(properties.getPort()).sync().channel();
serverChannel.closeFuture().sync();
} catch (Exception e) {
logger.error("Running voice server exception...{}", e);
throw new InternalSystemException(Constants.BOT_VOICE_CORE, "Running voice server exception...", e);
} finally {
//关闭资源
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
在voice中通过websocke的入口将ChannelHandlyContext以及HttpFullRequest等放入线程局部变量中然后利用@ApplicationContextWarezhuru
@Component
@Scope("prototype") // 必须是prototype!!! 实现SimpleChannelInboundHandler<Object>的channelRead0(ChannelHandlerContext ctx, Object msg)是netty的入口先在newWebSocketSession中将连接的ChannelHandlerContext以及
newWebSocketSession以及handShark存入线程局部变量中
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class);
private ApplicationContext applicationContext;
/**
* 我们的 handler 是 prototype的,且处理的是websocket 因此不会有多个ChannelId
* 废除原来的map, 直接用 handler 保留当前的handler, 避免浪费内存
*/
// private Map<ChannelId, AbstractWebSocketSessionHandler> handlers = new ConcurrentHashMap<>();
AbstractWebSocketSessionHandler sessionHandler;
@Autowired
NettyServerProperties properties;
@Autowired
private ConfigConfiguration configConfig;
private WebSocketRequestAttributes attributes;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
// 处理http请求
handleHttpRequest(ctx, (FullHttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
// 处理websocket请求
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
try {
// Handle a bad request.
boolean iswebSocket = Constants.WEBSOCKET.equals(req.headers().get("Upgrade"));
if (!req.decoderResult().isSuccess() || !iswebSocket) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
return;
}
String uri = req.uri();
String queryArgs = URI.create(uri).getQuery();
if (!StringUtils.isEmpty(queryArgs)) {
List<NameValuePair> params = URLEncodedUtils.parse(queryArgs, Charset.forName("utf-8"));
NameValuePair imeiPair = params.stream()
.filter(param -> Objects.equals(param.getName(), "id")).findFirst().orElse(null);
String imei = Optional.ofNullable(imeiPair).map(NameValuePair::getValue).orElse(null);
MDC.put("imei", imei);
}
LOGGER.info("The uri form client is {}", uri);
if (iswebSocket) {
//构造握手响应返回
// WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false);
// handshaker = ws.newHandshaker(request);
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(uri, null, true, properties.getMaxSize());
WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
req.setUri(getShortUri(uri) + "?" + uri.substring(uri.indexOf("?") + 1, uri.length()));
ChannelFuture channelFuture = handshaker.handshake(ctx.channel(), req);
channelFuture.addListener(f -> {
// 握手成功之后,业务逻辑
if (f.isSuccess()) {
newWebSocketSession(ctx, req, handshaker);
} else {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
}
});
try {
channelFuture.await(0);
} catch (InterruptedException e) {
LOGGER.error("Connetioning to voice error {}",e);
throw new InternalSystemException(Constants.BOT_VOICE_CORE,"Connetioning to voice error ",e);
}
}
} else {
processHttp(ctx, req);
}
} finally {
MDC.clear();
}
}
private void newWebSocketSession(ChannelHandlerContext ctx, FullHttpRequest req,
WebSocketServerHandshaker handshaker) {
URI uri = null;
try {
uri = new URI(req.uri());
} catch (URISyntaxException e) {
LOGGER.error("新建一个webscket session 连接失败 {}", e);
throw new InternalSystemException(Constants.BOT_VOICE_CORE, "新建一个webscket session 连接", e);
}
//准备 websocket scope 的容器
attributes = buildRequestAttributes(ctx, req, handshaker);
WebSocketContextHolder.setRequestAttributes(attributes);
try {
LOGGER.info("The uri path is {}", uri.getPath());
//现在可以直接创建 bean, 无需复杂的手工注入逻辑
if (uri.getPath().startsWith(configConfig.getVoiceURI())) {
//先将channel信息存入线程局部变量之后然后利用Websocket生命周期在容器中生成一个对象,在生命周期中是在线程局部变量中取然后从工厂生产,然后在VoiceSessionHandler中利用@AutoWird注解生成对象将连接的Channel等信息实例化到对象中
每个连接都有一个对象,先把所有用到Channel的对象初始化返回数据的时候调用。
sessionHandler = applicationContext.getBean(VoiceSessionHandler.class);
} else {
sessionHandler = applicationContext.getBean(DefaultSessionHandler.class);
}
} finally {
//清除 websocket scope 的容器 初始化所有对象完成时清除线程局部变量中的内容
WebSocketContextHolder.clearRequestAttributes();
}
}
private WebSocketRequestAttributes buildRequestAttributes(ChannelHandlerContext ctx, FullHttpRequest httpRequest, WebSocketServerHandshaker handshaker) {
return new WebSocketRequestAttributes(ctx, httpRequest, handshaker);
}
private void processHttp(ChannelHandlerContext ctx, FullHttpRequest req) {
// 等待處理
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND));
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
if (frame instanceof CloseWebSocketFrame) {
if (sessionHandler != null) {
sessionHandler.closeSession((CloseWebSocketFrame) frame);
}
return;
}
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
} else {
if (sessionHandler != null) {
sessionHandler.processFrame(frame);
} else {
//非法的
ctx.channel().writeAndFlush(new TextWebSocketFrame("{}"));
}
}
}
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
if (res.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
HttpUtil.setContentLength(res, res.content().readableBytes());
}
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
sessionHandler = null;
if(attributes != null) {
LOGGER.info("客户端清除,handlerRemoved function runn,attributes is not null.");
WebSocketContextHolder.clean(attributes);
attributes = null;
}
定义一个webSocket的注解类似于Scope(“protype”)生成方式,但是这个是先从WebSocketContextHolder.currentRequestAttributes()的线程局部变量中取出来该线程的bean如果没有时由objectFactory工厂生成并且存入线程局部变量
public class WebSocketScope implements Scope {
public WebSocketScope() {
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
//获取当前websocket 会话的 RequestAttributes, 即beans 的容器
WebSocketRequestAttributes attributes = WebSocketContextHolder.currentRequestAttributes();
if(attributes == null) { //如果线程局部变量中没有则抛出一个异常
throw new IllegalStateException("No WebSocketRequestAttributes found: ");
}
Object scopedObject = attributes.getAttribute(name);
if (scopedObject == null) {
//如果这个对象还不存在,则通过objectFactory生成一个,然后存入RequestAttributes 中
scopedObject = objectFactory.getObject();
//如果bean 实现了 ScopedLifeCircle, 调用 initScope 实现init
if(scopedObject instanceof ScopedLifeCircle) {
ScopedLifeCircle circle = (ScopedLifeCircle)scopedObject;
circle.initScope();
}
attributes.setAttribute(name, scopedObject);
}
return scopedObject;
}
@Override
public Object remove(String name) {
WebSocketRequestAttributes attributes = WebSocketContextHolder.currentRequestAttributes();
return attributes == null ? null : attributes.remove(name);
}
@Override
public void registerDestructionCallback(String s, Runnable runnable) {
}
@Override
public Object resolveContextualObject(String s) {
return null;
}
@Override
@Nullable
public String getConversationId() {
return null;
}
}
定义WebSocketRequestAttributes 管理WebSocket回话的ChannelHandlerContext等beans存储这里面依赖的一些bean对象 websocket的范围相当于Prototype多例但是如果A依赖B,B依赖C,C依赖A的话容器中就有两个A为了避免这种情况
将对象存入bean中管理起来保证始终只有一个对象
public class WebSocketRequestAttributes {
private ChannelHandlerContext ctx;
private WebSocketServerHandshaker handshaker;
private FullHttpRequest httpRequest;
private Map<String, Object> beans;
public WebSocketRequestAttributes(ChannelHandlerContext ctx, FullHttpRequest httpRequest, WebSocketServerHandshaker handshaker) {
this.ctx = ctx;
this.httpRequest = httpRequest.copy();
this.handshaker = handshaker;
this.beans = new LinkedHashMap<>();
}
public Object getAttribute(String name) {
return beans.get(name);
}
public void setAttribute(String name, Object scopedObject) {
Object old = beans.put(name, scopedObject) ;
if(old != null && old instanceof ScopedLifeCircle) {
((ScopedLifeCircle)old).closeScope();
}
}
public ChannelHandlerContext getCtx() {
return ctx;
}
public WebSocketServerHandshaker getHandshaker() {
return handshaker;
}
public FullHttpRequest getHttpRequest() {
return httpRequest;
}
public void clearAll() {
this.ctx = null;
this.handshaker = null;
if (this.httpRequest != null) {
this.httpRequest.release();
this.httpRequest = null;
}
if(beans != null) {
beans.forEach((k, v) -> {
//如果bean 实现了 ScopedLifeCircle, 调用 closeScope 实现清除资源
if (v instanceof ScopedLifeCircle) {
((ScopedLifeCircle) v).closeScope();
}
});
beans.clear();
beans = null;
}
}
public Object remove(String name) {
return beans.remove(name);
}
}
将自定义的实现了Scope的生命周期对象注入Spring工厂中
**
* 本类 加入 spring, 目标是注册一个 名称为 websocket 的 scope
*/
@Component
public class WebSocketRegister implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope("websocket", new WebSocketScope());
}
}
@Component
@Scope("websocket") // 必须是websocket!!! 只有是webSocket时才能按照spring对象管理的方式生成。按照实现Scope接口的get获取对象
@Slf4j
public class VoiceSessionHandler extends AbstractWebSocketSessionHandler /*implements InitializingBean*/{
/**
* 从websocket 读取输入的服务
*/
@Autowired
private InteractInputService interactInputService;
/**
* 输出到 websocket 的服务
*/
@Autowired
private InteractOutputService interactOutputService;
/**
* 根据请求url取出后面的请求参数部分内容
* @return
*/
private String getQueryString() {
String queryString = getRequestUri();
int index = queryString.indexOf("?");
if (index != -1) {
queryString = queryString.substring(index + 1);
} else {
queryString = "";
}
return queryString;
}
@Override
public void initScope() {
super.initScope();
//我们使用@Scope("websocket"), 依赖的对象已经可以自动注入了
//不再需要复杂的手工注入逻辑了。
//主要注意的是,现在那些 依赖的 bean不能再有循环引用了!!!!
//握手
String queryString = getQueryString();
if (!interactInputService.processHandshake(queryString)) {
log.error("Authority authentication failed...{}", queryString);
close();
}
interactOutputService.processHandshake(queryString);
}
/**
* 处理 websocket 每帧数据
* @param frame
*/
@Override
public void processFrame(WebSocketFrame frame) {
if (frame instanceof TextWebSocketFrame) {
String text = ((TextWebSocketFrame) frame).text();
//处理文本信息
interactInputService.processTextMessage(text);
} else if (frame instanceof BinaryWebSocketFrame) {
ByteBuf buf = frame.content();
byte[] content = new byte[buf.capacity()];
buf.readBytes(content);
//处理二进制信息
interactInputService.processBinaryMessage(content);
} else {
log.error("客户端上传的不是文本信息,也不是二进制信息...");
}
}
@Override
public void clear() {
super.clear();
if(interactInputService != null) {
interactInputService.onClose();
interactInputService = null;
}
if(interactOutputService != null) {
interactOutputService.onClose();
interactOutputService = null;
}
}