Java tutorial
/* * (C) Copyright 2015-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * ohun@live.cn () */ package com.mpush.core.push; import com.mpush.api.Message; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.spi.push.IPushMessage; import com.mpush.common.condition.AwaysPassCondition; import com.mpush.api.common.Condition; import com.mpush.common.message.PushMessage; import com.mpush.common.qps.FlowControl; import com.mpush.common.qps.OverFlowException; import com.mpush.core.router.LocalRouter; import com.mpush.core.router.RouterCenter; import com.mpush.tools.common.TimeLine; import com.mpush.tools.log.Logs; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; /** * Created by ohun on 16/10/24. * * @author ohun@live.cn () */ public final class BroadcastPushTask implements PushTask, ChannelFutureListener { private final long begin = System.currentTimeMillis(); private final AtomicInteger finishTasks = new AtomicInteger(0); private final FlowControl flowControl; private final IPushMessage message; private final Condition condition; private final TimeLine timeLine = new TimeLine(); //Iterator, ????????/ private final Iterator<Map.Entry<String, Map<Integer, LocalRouter>>> iterator; public BroadcastPushTask(IPushMessage message, FlowControl flowControl) { this.message = message; this.flowControl = flowControl; this.condition = message.getCondition(); this.iterator = RouterCenter.I.getLocalRouterManager().routers().entrySet().iterator(); this.timeLine.begin("push-center-begin"); } @Override public void run() { flowControl.reset(); boolean done = broadcast(); if (done) {//done ? if (finishTasks.addAndGet(flowControl.total()) == 0) { report(); } } else {//? TODO PushCenter.I.delayTask(flowControl.getDelay(), this); } flowControl.end(); } private boolean broadcast() { try { iterator.forEachRemaining(entry -> { String userId = entry.getKey(); entry.getValue().forEach((clientType, router) -> { Connection connection = router.getRouteValue(); if (checkCondition(condition, connection)) {//1.? if (connection.isConnected()) { if (connection.getChannel().isWritable()) { //TCP? PushMessage.build(connection).setContent(message.getContent()).send(this); //4. qps, ?????catch if (!flowControl.checkQps()) { throw new OverFlowException(false); } } } else { //2.?? Logs.PUSH.warn( "[Broadcast] find router in local but conn disconnect, message={}, conn={}", message, connection); //? RouterCenter.I.getLocalRouterManager().unRegister(userId, clientType); } } }); }); } catch (OverFlowException e) { //???? return e.isOverMaxLimit() || !iterator.hasNext(); } return !iterator.hasNext();//??, ? } private void report() { Logs.PUSH.info("[Broadcast] task finished, cost={}, message={}", (System.currentTimeMillis() - begin), message); PushCenter.I.getPushListener().onBroadcastComplete(message, timeLine.end().getTimePoints());//??? } private boolean checkCondition(Condition condition, Connection connection) { if (condition == AwaysPassCondition.I) return true; SessionContext sessionContext = connection.getSessionContext(); Map<String, Object> env = new HashMap<>(); env.put("userId", sessionContext.userId); env.put("tags", sessionContext.tags); env.put("clientVersion", sessionContext.clientVersion); env.put("osName", sessionContext.osName); env.put("osVersion", sessionContext.osVersion); return condition.test(env); } @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) {//?? Logs.PUSH.info("[Broadcast] push message to client success, userId={}, message={}", message.getUserId(), message); } else {//? Logs.PUSH.warn("[Broadcast] push message to client failure, userId={}, message={}, conn={}", message.getUserId(), message, future.channel()); } if (finishTasks.decrementAndGet() == 0) { report(); } } @Override public ScheduledExecutorService getExecutor() { return ((Message) message).getConnection().getChannel().eventLoop(); } }