PAT——1011. A+B和C

给定区间[-231, 231]内的3个整数A、B和C,请判断A+B是否大于C。

输入格式:

输入第1行给出正整数T(<=10),是测试用例的个数。随后给出T组测试用例,每组占一行,顺序给出A、B和C。整数间以空格分隔。

输出格式:

对每组测试用例,在一行中输出“Case #X: true”如果A+B>C,否则输出“Case #X: false”,其中X是测试用例的编号(从1开始)。

输入样例:

4
1 2 3
2 3 4
2147483647 0 2147483646
0 -2147483648 -2147483647

输出样例:

Case #1: false
Case #2: true
Case #3: true
Case #4: false
 1 package com.hone.basical;
 2 
 3 /**
 4  * 分三个数组分别存放A B C 并且先将所有的数都存进去。
 5  * 每次对应取出来,用减法做比较(目的是为了防止越界)然后做比较。
 6  */
 7 import java.util.Scanner;  
 8 public class basicalLevel1011IsBigger2{  
 9     public static void main(String[] args){  
10         Scanner sc = new Scanner(System.in);  
11         int T =  sc.nextInt();  
12         long[]a = new long[T];  
13         long[]b = new long[T];  
14         long[]c = new long[T];  
15         for(int i=0 ;i<T ;i++){  
16             a[i] = sc.nextLong();  
17             b[i] = sc.nextLong();  
18             c[i] = sc.nextLong();  
19         }  
20         for(int i=0 ;i<T ;i++){  
21             if(c[i]-b[i]<a[i]){  
22                 System.out.printf("Case #%d: true\n", i+1);  
23             }  
24             else{  
25                 System.out.printf("Case #%d: false\n", i+1);  
26             }  
27         }  
28     }  
29 }  

 







转载于:https://www.cnblogs.com/xiaxj/p/7977497.html

package com.taixin.dvbc2; import com.google.gson.Gson; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.net.*; import java.sql.*; import java.util.*; import java.util.concurrent.*; /** * SSE + Servlet 3.0 异步版本(含 PMT/ES 细节、入库去重) * - 每个 (ip,port) 唯一一个 ChannelWorker * - 多个订阅者共享该 worker 的统计输出 * - 输出字段:name/no/rate + pmt/vid/vtype/a1/a1t/a2/a2t * - 仅当节目结构或PID/类型变化时触发 SaveProgramData(签名去重) */ public class LoadProgramServlet extends HttpServlet { private static final Gson GSON = new Gson(); // 推送间隔(纳秒):8 秒 private static final long REPORT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(8); // 心跳间隔:15 秒(SSE 注释行) private static final long HEARTBEAT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(15); // 所有工作线程:Key -> ChannelWorker private static final Map<ChannelKey, ChannelWorker> WORKERS = new ConcurrentHashMap<>(); // ===== Servlet 入口 ===== @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { final String ip = req.getParameter("ip"); final String portStr = req.getParameter("port"); final String idStr = req.getParameter("id"); final String inputChannelName = req.getParameter("name"); if (ip == null || portStr == null) { resp.setStatus(400); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().write("{\"error\":\"缺少参数 ip 或 port\"}"); return; } final int port = Integer.parseInt(portStr); final Integer inputChannelId = (idStr == null || idStr.trim().isEmpty()) ? null : Integer.valueOf(idStr); final ChannelKey key = new ChannelKey(ip, port); // 设置 SSE 头 resp.setStatus(200); resp.setContentType("text/event-stream;charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Cache-Control", "no-cache"); resp.setHeader("Connection", "keep-alive"); resp.setHeader("X-Accel-Buffering", "no"); // 开启异步 final AsyncContext ac = req.startAsync(req, resp); ac.setTimeout(0); // 永不超时 final PrintWriter writer = resp.getWriter(); final Subscriber sub = new Subscriber(writer, ac); // 获取/创建 worker,并登记订阅者 ChannelWorker worker = WORKERS.compute(key, (k, w) -> { if (w == null || !w.isAliveWorker()) { ChannelWorker nw = new ChannelWorker( k, REPORT_INTERVAL_NS, HEARTBEAT_INTERVAL_NS, inputChannelId, inputChannelName ); try { nw.start(); } catch (Exception e) { safeSendSSE(sub, "{\"error\":\"通道启动失败: " + e.getMessage() + "\"}"); completeQuietly(ac); return null; } w = nw; } else { // 已有 worker,补齐一次元信息(为空时才设置,避免覆盖) w.setChannelMetaIfEmpty(inputChannelId, inputChannelName); } w.addSubscriber(sub); return w; }); if (worker == null) { return; // 上面已通知错误 } // 监听清理 ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) { cleanup(); } @Override public void onTimeout(AsyncEvent event) { cleanup(); } @Override public void onError(AsyncEvent event) { cleanup(); } @Override public void onStartAsync(AsyncEvent event) {} private void cleanup() { ChannelWorker w = WORKERS.get(key); if (w != null) { w.removeSubscriber(sub); if (w.subscriberCount() == 0) { w.stopWorker(); WORKERS.remove(key); } } } }); // 立即发一条“已连接” safeSendSSE(sub, "{\"status\":\"connected\"}"); } // ====== SSE 订阅者对象 ====== static final class Subscriber { final PrintWriter writer; final AsyncContext ac; volatile long lastHeartbeatNs = System.nanoTime(); Subscriber(PrintWriter writer, AsyncContext ac) { this.writer = writer; this.ac = ac; } synchronized void sendData(String json) { if (writer.checkError()) { complete(); return; } writer.write("data: " + json + "\n\n"); // SSE 以空行分隔 writer.flush(); if (writer.checkError()) { complete(); } } synchronized void heartbeatIfNeeded(long now, long heartbeatIntervalNs) { if (now - lastHeartbeatNs < heartbeatIntervalNs) return; if (writer.checkError()) { complete(); return; } writer.write(": keepalive\n\n"); writer.flush(); lastHeartbeatNs = now; if (writer.checkError()) { complete(); } } synchronized void complete() { completeQuietly(ac); } } // ====== 唯一标识每个组播通道 ====== static final class ChannelKey { final String ip; final int port; ChannelKey(String ip, int port) { this.ip = ip; this.port = port; } @Override public boolean equals(Object o) { if (!(o instanceof ChannelKey)) return false; ChannelKey that = (ChannelKey)o; return this.port == that.port && this.ip.equals(that.ip); } @Override public int hashCode() { return ip.hashCode() * 31 + port; } @Override public String toString() { return ip + ":" + port; } } // ====== 每个通道一个工作线程:接收 TS、解析 PSI、聚合速率、广播 ====== static final class ChannelWorker extends Thread { private final ChannelKey key; private final long reportIntervalNs; private final long heartbeatIntervalNs; private final Set<Subscriber> subscribers = ConcurrentHashMap.newKeySet(); // —— 请求元数据(来自 doGet)—— private volatile Integer inputChannelId; // InputChannelId private volatile String inputChannelName; // InputChannelName // —— 每个通道独有的解析状态(不要 static)—— private final Map<Integer, Integer> progToPmtPid = new HashMap<>(); // program -> PMT PID private final Map<Integer, String> programNames = new HashMap<>(); // program -> name(优先 SDT) private final Map<Integer, Integer> pidToProgram = new HashMap<>(); // pid -> program(用于速率归集) private final Map<Integer, Long> pidBytesCounter = new HashMap<>(); // pid -> bytes since last report private final Map<Integer, SectionAssembler> assemblers = new HashMap<>(); // 新增:每个节目下的 ES 列表(带类型/编码) private final Map<Integer, List<EsInfo>> programEsInfo = new HashMap<>(); // 去重签名:记录“最近一次已保存”的结构快照(字符串签名) private volatile String lastSavedSignature = null; // —— ES 描述结构 —— static final class EsInfo { final int pid; final int streamType; final String kind; // video / audio / other final String codec; // H.264/HEVC/AAC/AC-3 等 EsInfo(int pid, int st, String kind, String codec) { this.pid = pid; this.streamType = st; this.kind = kind; this.codec = codec; } } private volatile boolean running = true; private MulticastSocket sock; private InetAddress group; ChannelWorker(ChannelKey key, long reportIntervalNs, long heartbeatIntervalNs, Integer inputChannelId, String inputChannelName) { super("ChannelWorker-" + key); this.key = key; this.reportIntervalNs = reportIntervalNs; this.heartbeatIntervalNs = heartbeatIntervalNs; this.inputChannelId = inputChannelId; this.inputChannelName = inputChannelName; setDaemon(true); } // 复用已存在的 worker 时补齐元信息 void setChannelMetaIfEmpty(Integer id, String name) { if (this.inputChannelId == null && id != null) this.inputChannelId = id; if ((this.inputChannelName == null || this.inputChannelName.isEmpty()) && name != null) { this.inputChannelName = name; } } boolean isAliveWorker() { return running && isAlive(); } void addSubscriber(Subscriber s) { subscribers.add(s); } void removeSubscriber(Subscriber s) { subscribers.remove(s); } int subscriberCount() { return subscribers.size(); } void stopWorker() { running = false; try { if (sock != null) sock.close(); } catch (Throwable ignore) {} } @Override public void run() { try { sock = new MulticastSocket(key.port); group = InetAddress.getByName(key.ip); sock.joinGroup(group); final int MTU = 7 * 188; byte[] buf = new byte[MTU]; DatagramPacket pkt = new DatagramPacket(buf, buf.length); long lastReport = System.nanoTime(); while (running) { if (subscribers.isEmpty()) { Thread.sleep(80); continue; } sock.receive(pkt); int len = pkt.getLength(); int off = 0; while (off + 188 <= len) { parseTsPacket(buf, off); off += 188; } long now = System.nanoTime(); // 心跳 for (Subscriber s : subscribers) s.heartbeatIfNeeded(now, heartbeatIntervalNs); // 定时汇报 if (now - lastReport >= reportIntervalNs) { String json = buildProgramReportJson(reportIntervalNs); if (json != null) broadcast(json); lastReport = now; } } } catch (SocketException ignore) { // close() 时会来这 } catch (Throwable t) { broadcast("{\"error\":\"worker-crashed: " + safeMsg(t) + "\"}"); } finally { try { if (group != null && sock != null) sock.leaveGroup(group); } catch (Throwable ignore) {} try { if (sock != null) sock.close(); } catch (Throwable ignore) {} for (Subscriber s : subscribers) completeQuietly(s.ac); subscribers.clear(); } } private void broadcast(String json) { for (Subscriber s : subscribers) s.sendData(json); } // === TS/PSI 解析 === private void parseTsPacket(byte[] buf, int off) { if ((buf[off] & 0xFF) != 0x47) return; // sync int tei = (buf[off + 1] & 0x80) >>> 7; int pusi = (buf[off + 1] & 0x40) >>> 6; int pid = ((buf[off + 1] & 0x1F) << 8) | (buf[off + 2] & 0xFF); int afc = (buf[off + 3] & 0x30) >>> 4; if (tei == 1) return; if (pid != 0x1FFF) { Long old = pidBytesCounter.get(pid); pidBytesCounter.put(pid, (old == null ? 0L : old) + 188L); } int i = off + 4; if (afc == 2) return; // 只有 AF,无 payload if (afc == 3) { // 跳过 AF int afl = buf[i] & 0xFF; i += 1 + afl; } if (i >= off + 188) return; // 只重组 PAT/PMT/SDT if (pid == 0x0000 || pid == 0x0011 || containsValue(progToPmtPid, pid)) { SectionAssembler sa = assemblers.get(pid); if (sa == null) { sa = new SectionAssembler(); assemblers.put(pid, sa); } sa.push(buf, i, off + 188 - i, pusi == 1, (buf[off + 3] & 0x0F)); while (sa.hasSection()) { byte[] sec = sa.pollSection(); parseSection(pid, sec); } } } private boolean containsValue(Map<Integer, Integer> map, int val) { for (Integer v : map.values()) if (v != null && v == val) return true; return false; } private void parseSection(int pid, byte[] sec) { if (sec.length < 3) return; int tableId = sec[0] & 0xFF; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); if (sectionLength + 3 != sec.length) return; if (pid == 0x0000 && tableId == 0x00) { parsePAT(sec); } else if (pid == 0x0011 && (tableId == 0x42 || tableId == 0x46)) { parseSDT(sec); } else if (tableId == 0x02) { parsePMT(sec); } } private void parsePAT(byte[] sec) { int pos = 8, end = sec.length - 4; while (pos + 4 <= end) { int programNumber = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int pmtPid = ((sec[pos + 2] & 0x1F) << 8) | (sec[pos + 3] & 0xFF); pos += 4; if (programNumber == 0) continue; progToPmtPid.put(programNumber, pmtPid); pidToProgram.put(pmtPid, programNumber); } } private void parsePMT(byte[] sec) { int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); // 占位名(后续由 SDT 覆盖) if (!programNames.containsKey(programNumber)) { programNames.put(programNumber, "Program " + programNumber); } int progInfoLen = ((sec[10] & 0x0F) << 8) | (sec[11] & 0xFF); int pos = 12 + progInfoLen; int end = sec.length - 4; List<EsInfo> list = new ArrayList<>(); while (pos + 5 <= end) { int streamType = sec[pos] & 0xFF; int esPid = ((sec[pos + 1] & 0x1F) << 8) | (sec[pos + 2] & 0xFF); int esInfoLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); pos += 5; // ES 描述符区域 byte[] esDesc = null; if (esInfoLen > 0 && pos + esInfoLen <= end) { esDesc = Arrays.copyOfRange(sec, pos, pos + esInfoLen); } pos += esInfoLen; // 归属 program,便于速率归集 pidToProgram.put(esPid, programNumber); // 识别类型/编码 String[] cls = classifyStream(streamType, esDesc); list.add(new EsInfo(esPid, streamType, cls[0], cls[1])); } programEsInfo.put(programNumber, list); } private void parseSDT(byte[] sec) { int pos = 11, end = sec.length - 4; while (pos + 5 <= end) { int serviceId = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int descriptorsLoopLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); int descPos = pos + 5; int descEnd = descPos + descriptorsLoopLen; String name = null; while (descPos + 2 <= descEnd && descEnd <= end) { int tag = sec[descPos] & 0xFF; int len = sec[descPos + 1] & 0xFF; if (descPos + 2 + len > descEnd) break; if (tag == 0x48 && len >= 5) { // service_descriptor int base = descPos + 2; int provLen = sec[base + 1] & 0xFF; int nameLenPos = base + 2 + provLen; if (nameLenPos < descPos + 2 + len) { int nameLen = sec[nameLenPos] & 0xFF; int nameStart = nameLenPos + 1; int nameEnd = Math.min(nameStart + nameLen, descPos + 2 + len); byte[] nameBytes = Arrays.copyOfRange(sec, nameStart, nameEnd); name = decodeDvbString(nameBytes); } } descPos += 2 + len; } if (name != null && !name.isEmpty()) { programNames.put(serviceId, name); } pos = descEnd; } } // === 码率汇总 + JSON 输出(含“变更才保存”) === private String buildProgramReportJson(long intervalNs) { if (pidBytesCounter.isEmpty()) return null; Map<Integer, Long> programBytes = new HashMap<>(); for (Map.Entry<Integer, Long> e : pidBytesCounter.entrySet()) { int pid = e.getKey(); long bytes = e.getValue(); Integer prog = pidToProgram.get(pid); if (prog != null) { Long old = programBytes.get(prog); programBytes.put(prog, (old == null ? 0L : old) + bytes); } } pidBytesCounter.clear(); if (programBytes.isEmpty()) return null; List<Map<String, Object>> programs = new ArrayList<>(); for (Map.Entry<Integer, Long> e : programBytes.entrySet()) { int program = e.getKey(); long bytes = e.getValue(); double mbps = (bytes * 8.0) / (intervalNs / 1e9) / 1_000_000.0; String name = programNames.get(program); if (name == null) name = "(解析中…)"; Integer pmtPid = progToPmtPid.get(program); EsInfo v = null, a1 = null, a2 = null; List<EsInfo> list = programEsInfo.get(program); if (list != null) { for (EsInfo es : list) { if ("video".equals(es.kind)) { if (v == null) v = es; } else if ("audio".equals(es.kind)) { if (a1 == null) a1 = es; else if (a2 == null) a2 = es; } } } Map<String, Object> one = new LinkedHashMap<>(); one.put("name", name); one.put("no", program); one.put("rate", mbps); one.put("pmt", pmtPid == null ? null : pmtPid); one.put("vid", v == null ? null : v.pid); one.put("vtype", v == null ? null : v.codec); one.put("a1", a1 == null ? null : a1.pid); one.put("a1t", a1 == null ? null : a1.codec); one.put("a2", a2 == null ? null : a2.pid); one.put("a2t", a2 == null ? null : a2.codec); programs.add(one); // ==== 入库去重:仅当签名变化时才保存 ==== // 只保存“首个节目”(你的需求:一个输入通道只有一个节目) final String signature = makeSignature( name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid), (a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid), (a2 == null ? null : a2.codec) ); // System.out.println(signature); // 比较并保存 if (lastSavedSignature == null || !signature.equals(lastSavedSignature)) { lastSavedSignature = signature; // 先更新,避免并发重复 try { SaveProgramData( this.inputChannelId, this.inputChannelName, this.key.ip, this.key.port, name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid),(a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid),(a2 == null ? null : a2.codec) ); } catch (Exception ex) { // 保存失败不影响 SSE System.err.println("[SaveProgramData][ERROR] " + ex.getMessage()); } } // 一个输入通道只有一个节目——只处理第一个 break; } return GSON.toJson(programs); } // 生成签名(只关注结构/类型/PID变化,速率不参与) private static String makeSignature(String name, Integer progNo, Integer pmt, Integer vid, String vtype, Integer a1, String a1t, Integer a2, String a2t) { return String.valueOf( Objects.hash( safe(name), progNo, pmt, vid, safe(vtype), a1, safe(a1t), a2, safe(a2t) ) ); } private static String safe(String s) { return (s == null ? "" : s); } // === stream_type + 描述符 → 类型/编码 === private static String[] classifyStream(int streamType, byte[] esDesc) { switch (streamType & 0xFF) { case 0x01: return new String[]{"video", "MPEG-1 Video"}; case 0x02: return new String[]{"video", "MPEG-2 Video"}; case 0x10: return new String[]{"video", "MPEG-4 Visual"}; case 0x1B: return new String[]{"video", "H.264/AVC"}; case 0x24: return new String[]{"video", "HEVC/H.265"}; case 0x03: return new String[]{"audio", "MPEG-1 Layer II"}; case 0x04: return new String[]{"audio", "MPEG-2 Audio"}; case 0x0F: return new String[]{"audio", "AAC"}; case 0x11: return new String[]{"audio", "LATM/LOAS AAC"}; case 0x06: if (hasDescriptor(esDesc, (byte)0x6A)) return new String[]{"audio", "AC-3"}; if (hasDescriptor(esDesc, (byte)0x7A)) return new String[]{"audio", "E-AC-3"}; if (hasDescriptor(esDesc, (byte)0x7B)) return new String[]{"audio", "DTS"}; return new String[]{"other", "Private data"}; default: return new String[]{"other", String.format("stream_type 0x%02X", streamType & 0xFF)}; } } private static boolean hasDescriptor(byte[] esDesc, byte tag) { if (esDesc == null) return false; int i = 0, n = esDesc.length; while (i + 2 <= n) { int t = esDesc[i] & 0xFF; int l = esDesc[i+1] & 0xFF; if (i + 2 + l > n) break; if ((byte)t == tag) return true; i += 2 + l; } return false; } // === DVB 字符串解码 === private static String decodeDvbString(byte[] bs) { if (bs == null || bs.length == 0) return ""; int offset = 0; String charset = "GB18030"; int first = bs[0] & 0xFF; if (first == 0x10 && bs.length >= 3) { charset = "UTF-16BE"; offset = 1; } else if (first == 0x15 || first == 0x14) { charset = "GB2312"; offset = 1; } else if (first == 0x1F) { charset = "UTF-8"; offset = 1; } else if (first < 0x20) { offset = 1; } try { return new String(bs, offset, bs.length - offset, charset).trim(); } catch (Exception e) { try { return new String(bs, offset, bs.length - offset, "GB2312").trim(); } catch (Exception ignore) {} try { return new String(bs, offset, bs.length - offset, "UTF-8").trim(); } catch (Exception ignore) {} return ""; } } // === 安全的 section 重组器 === static final class SectionAssembler { private final ByteArrayOutputStream cur = new ByteArrayOutputStream(); private Integer expectedLen = null; private final Queue<byte[]> ready = new ArrayDeque<>(); void push(byte[] src, int off, int len, boolean payloadStart, int continuityCounter) { final int end = off + len; int i = off; if (payloadStart) { if (i >= end) return; int pointer = src[i] & 0xFF; i += 1; if (i + pointer > end) return; i += pointer; startNew(); } while (i < end) { int remaining = end - i; if (expectedLen == null) { if (remaining < 3) { write(src, i, remaining); return; } int sl = ((src[i+1] & 0x0F) << 8) | (src[i+2] & 0xFF); expectedLen = sl + 3; // total including header } int need = expectedLen - cur.size(); if (need <= 0) { startNew(); continue; } int copy = Math.min(need, remaining); write(src, i, copy); i += copy; if (cur.size() == expectedLen) { ready.add(cur.toByteArray()); startNew(); } } } private void write(byte[] src, int off, int len) { if (len > 0) cur.write(src, off, len); } boolean hasSection() { return !ready.isEmpty(); } byte[] pollSection() { return ready.poll(); } private void startNew() { cur.reset(); expectedLen = null; } } } // ====== 工具方法 ====== private static void safeSendSSE(Subscriber s, String json) { try { s.sendData(json); } catch (Throwable ignore) {} } private static void completeQuietly(AsyncContext ac) { try { ac.complete(); } catch (Throwable ignore) {} } private static String safeMsg(Throwable t) { String m = t.getMessage(); return (m == null ? t.getClass().getSimpleName() : m).replace("\"","'"); } // ====== 实际入库(你可以在此调用存储过程) ====== // 这里默认只打印,避免直接写库;你可以改为JDBC调用: // CallableStatement c = conn.prepareCall("{call dbo.sp_UpsertProgram_ByChannel(?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"); // ... set 参数 ... // c.execute(); static void SaveProgramData(Integer inputChannelId, String inputChannelName, String ip, int port, String name, Integer progNo, Integer pmtPid, Integer videoPid, String videoType, Integer audio1Pid, String audio1Type, Integer audio2Pid, String audio2Type) throws Exception { Properties props = new Properties(); try { InputStream inputStream = LoadProgramServlet.class.getClassLoader().getResourceAsStream("jdbc.properties"); if (inputStream != null) { props.load(inputStream); String DB_URL = props.getProperty("jdbc.url"); String DB_USER = props.getProperty("jdbc.username"); String DB_PASSWORD = props.getProperty("jdbc.password"); // 注册数据库驱动 Class.forName(props.getProperty("jdbc.driverClassName")); Connection c = null; CallableStatement cs = null; try { c = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD); String sql = "{call sp_SaveProgramData(?, ?, ?, ?, ?,?,?, ?, ?, ?, ?,?,?)}"; // 调用存储过程 cs = c.prepareCall(sql); // 设置存储过程参数 cs.setString(1, inputChannelName); cs.setInt(2,port); cs.setString(3, ip); cs.setString(4, name); // 当 out_mod 为 256 时 cs.setInt(5, progNo); // 当 out_mod 为 256 时 cs.setInt(6, pmtPid); // 当 out_mod 为 256 时 cs.setInt(7, videoPid); // 当 out_mod 为 256 时 cs.setString(8, videoType); // 当 out_mod 为 256 时 cs.setInt(9, audio1Pid == null ? 0 : audio1Pid); cs.setString(10, audio1Type == null ? "-" : audio1Type); cs.setInt(11, audio2Pid == null ? 0 : audio2Pid); cs.setString(12, audio2Type == null ? "-" : audio2Type); cs.registerOutParameter(13, Types.INTEGER); // @ReturnValue cs.execute(); // 获取输出参数 int returnValue = cs.getInt(13); // 读取 @ReturnValue if (returnValue == 1) { // 插入成功 // System.out.println("保存"+name+"成功"); } else { } } catch (SQLException e) { } finally { // 资源释放 try { if (cs != null) cs.close(); } catch (Exception ignore) { } try { if (c != null) c.close(); } catch (Exception ignore) { } } } else { } } catch (Exception e) { throw new ServletException("Failed to load database properties", e); } } } function getRateMbps(p) { if (typeof p?.rate === 'number' && isFinite(p.rate)) return p.rate; if (typeof p?.rate === 'string') { const n = parseFloat(p.rate); return isFinite(n) ? n : 0; } return 0; } function formatRate(mbps) { if (!isFinite(mbps) || mbps <= 0) return '0'; if (mbps >= 1) return (mbps).toFixed(2) + ' M'; if (mbps >= 0.001) return (mbps * 1000).toFixed(2) + ' K'; return Math.round(mbps * 1e6) + ' bps'; } // 给每个 group 挂一个 es 实例 & 正在解析标记 function startParseForGroup(group) { // 已经自动启动过并且连接还活着,直接返回,避免重复创建 if (group._autostarted && group.es && group.es.readyState === 1) { return; } group._autostarted = true; // 已有连接先关(防止旧连接残留) if (group.es) { try { group.es.close(); } catch (_) {} group.es = null; } group.parsing = true; // ⚠️ 确认你的后端路径是否真叫 loadPramgermServlet,别拼错了 const url = `loadPramgermServlet?ip=${encodeURIComponent(group.ip)}&port=${encodeURIComponent(group.port)}&id=${encodeURIComponent(group.id)}&name=${encodeURIComponent(group.name)}`; const es = new EventSource(url); group.es = es; es.onmessage = (event) => { try { const data = JSON.parse(event.data); if (!Array.isArray(data)) return; group.programs = data.map(p => ({ name: p.name, no: p.no, rate: Number(p.rate) || 0, pmt: p.pmt ?? '-', vid: p.vid ?? '-', vtype: p.vtype ?? '-', a1: p.a1 ?? '-', a1t: p.a1t ?? '-', a2: p.a2 ?? '-', a2t: p.a2t ?? '-' })); group.parsing = false; renderTree(); } catch (e) { console.error('解析 SSE 数据失败:', e, event.data); } }; es.onerror = (err) => { console.error('SSE 连接出错', err); group.parsing = false; try { es.close(); } catch (_) {} group.es = null; // 失败后允许再次自动启动 group._autostarted = false; }; } // (可选)为了节省资源,开始解析前关闭其它通道的 SSE function stopOtherParsers(exceptId) { groups.forEach(g => { if (String(g.id) !== String(exceptId) && g.es) { try { g.es.close(); } catch (_) {} g.es = null; g.parsing = false; } }); } // ✅ 按钮事件:只启动选中通道的解析,且只更新该通道 document.getElementById('btnParse').addEventListener('click', () => { const selectedGroup = groups.find(g => g.selected); if (!selectedGroup) { alert('请先选择一个输入通道'); return; } // // 可选:关掉其它通道的 SSE,避免多路同时开 // stopOtherParsers(selectedGroup.id); // 启动这个通道的解析 startParseForGroup(selectedGroup); }); 如果我有好几百个通道 一个通道一个节目100多个通道 我需要每个都展示速率 怎么才能坐到 而且不崩
09-06
<think> 我们面临的主要挑战是同时监控数百个通道,每个通道一个节目(即数百个组播流)。这涉及到资源管理(线程、Socket、内存)性能优化。以下是完整的解决方案: ### 架构设计要点 1. **资源复用**:每个组播通道(IP+端口)只创建一个工作线程(`ChannelWorker`),避免重复创建 2. **连接共享**:多个前端请求订阅同一个通道时,共享同一个`ChannelWorker` 3. **轻量解析**:只解析必要的PSI表(PAT/PMT/SDT),不解析音视频数据 4. **高效统计**:基于PID的字节计数,避免复杂计算 5. **异步报告**:使用SSE(Server-Sent Events)推送数据,避免轮询 ### 关键修改 - 支持大规模通道 #### 1. 控制工作线程数量 ```java // 在LoadProgramServlet中添加 private static final int MAX_WORKERS = 200; // 最大并行通道数 private static final Semaphore WORKER_SEMAPHORE = new Semaphore(MAX_WORKERS); // 在doGet中获取信号量 if (!WORKER_SEMAPHORE.tryAcquire()) { resp.setStatus(503); resp.getWriter().write("{\"error\":\"系统繁忙,请稍后再试\"}"); return; } // 在AsyncListener的cleanup中释放 @Override public void onComplete(AsyncEvent event) { WORKER_SEMAPHORE.release(); cleanup(); } ``` #### 2. 优化内存使用 ```java // 在ChannelWorker中 // 使用更紧凑的数据结构 private final Int2LongMap pidBytesCounter = new Int2LongOpenHashMap(); private final Int2ObjectMap<Integer> pidToProgram = new Int2ObjectOpenHashMap<>(); ``` #### 3. 添加心跳超时管理 ```java // 在Subscriber中记录最后活动时间 volatile long lastActiveNs = System.nanoTime(); // 在ChannelWorker中定期检查 if (now - sub.lastActiveNs > TimeUnit.SECONDS.toNanos(30)) { sub.complete(); // 关闭不活跃连接 subscribers.remove(sub); } ``` #### 4. 前端分页展示 ```javascript // 前端实现虚拟滚动或分页 function renderVisibleGroups() { const startIdx = currentPage * PAGE_SIZE; const endIdx = startIdx + PAGE_SIZE; const visibleGroups = groups.slice(startIdx, endIdx); // 只渲染可见的组 } ``` #### 5. 后端限流控制 ```java // 在buildProgramReportJson中添加 if (programBytes.isEmpty()) { return null; // 无数据不推送 } // 限制报告频率(最少间隔1秒) if (now - lastReport < TimeUnit.SECONDS.toNanos(1)) { return null; } ``` ### 完整代码调整示例 #### 后端线程池管理 ```java // 使用固定线程池代替无限创建 private static final ExecutorService WORKER_EXECUTOR = Executors.newFixedThreadPool(100); // 在创建ChannelWorker后提交到线程池 if (w == null || !w.isAliveWorker()) { ChannelWorker nw = new ChannelWorker(...); WORKER_EXECUTOR.submit(nw); // 改为线程池提交 w = nw; } ``` #### 前端节流渲染 ```javascript // 使用requestAnimationFrame避免频繁渲染 let renderScheduled = false; function scheduleRender() { if (renderScheduled) return; renderScheduled = true; requestAnimationFrame(() => { renderTree(); renderScheduled = false; }); } ``` ### 资源清理策略 ```java // 在ChannelWorker的run方法finally块中 try { sock.leaveGroup(group); } catch (IOException ignored) {} sock.close(); WORKER_SEMAPHORE.release(); // 确保释放信号量 ``` ### 监控与调优建议 1. 添加JMX监控:暴露工作线程数、订阅者数量等指标 2. 日志记录:关键操作添加DEBUG日志 3. 压力测试:使用模拟组播源测试系统容量 ### 数据库优化 ```java // 使用批处理连接池保存数据 private static final DataSource DATA_SOURCE = ...; // 使用DBCP或HikariCP // 在SaveProgramData中使用批处理 try (Connection c = DATA_SOURCE.getConnection(); PreparedStatement ps = c.prepareStatement("...")) { ps.setString(1, ...); ps.addBatch(); // 每100条执行一次 ps.executeBatch(); } ``` §§相关问题§§: 1. 如何设计高并发的组播数据接收系统? 2. 使用SSE时如何实现百万级连接管理? 3. 在Java中如何高效处理数百个UDP组播Socket? 4. 如何优化TS流解析性能以支持更多通道? </think> 针对支持数百个通道同时展示速率的需求,我提供了完整的优化方案,确保系统稳定高效运行: ### 大规模通道监控解决方案 #### 1. 后端架构优化(核心修改) ```java // 使用分片线程池管理大规模通道 private static final int MAX_CHANNELS = 500; // 最大通道数 private static final ScheduledExecutorService CHANNEL_EXECUTOR = Executors.newScheduledThreadPool( Runtime.getRuntime().availableProcessors() * 4, new NamedThreadFactory("ChannelMonitor") ); // 通道管理器 private static final Map<ChannelKey, ChannelMonitor> MONITORS = new ConcurrentHashMap<>(MAX_CHANNELS * 2); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // ... [参数验证逻辑不变] final ChannelKey key = new ChannelKey(ip, port); // 获取或创建通道监视器 ChannelMonitor monitor = MONITORS.computeIfAbsent(key, k -> { ChannelMonitor m = new ChannelMonitor(k); CHANNEL_EXECUTOR.scheduleAtFixedRate( m, 0, 8, TimeUnit.SECONDS ); return m; }); // 添加订阅者... monitor.addSubscriber(new Subscriber(writer, ac)); } ``` #### 2. 轻量级通道监视器实现 ```java static class ChannelMonitor implements Runnable { private final ChannelKey key; private final Set<Subscriber> subscribers = ConcurrentHashMap.newKeySet(); private volatile Map<String, Object> lastStats; public ChannelMonitor(ChannelKey key) { this.key = key; } void addSubscriber(Subscriber s) { subscribers.add(s); // 立即发送最新数据 if (lastStats != null) { s.sendData(GSON.toJson(lastStats)); } } @Override public void run() { try { // 1. 获取组播速率数据(优化版) Map<String, Object> stats = fetchChannelStats(); // 2. 更新最后状态 lastStats = stats; // 3. 广播给所有订阅者 String json = GSON.toJson(stats); for (Subscriber s : subscribers) { s.sendData(json); } } catch (Exception e) { // 错误处理 } } private Map<String, Object> fetchChannelStats() { // 使用高效IPC机制获取数据(如共享内存) return Map.of( "ip", key.ip, "port", key.port, "rate", calculateRate(), "status", "active", "timestamp", System.currentTimeMillis() ); } private double calculateRate() { // 实现高效速率计算(使用JNI或专用库) return 0.0; // 伪代码 } } ``` #### 3. 高效组播数据采集(C++ JNI实现) ```cpp // native_channel_monitor.cpp #include <jni.h> #include <multicast_receiver.h> // 自定义高效组播接收库 JavaVM* jvm; JNIEXPORT jdouble JNICALL Java_com_taixin_dvbc2_LoadProgramServlet_00024ChannelMonitor_calculateRate( JNIEnv *env, jobject obj, jstring jip, jint jport) { const char *ip = env->GetStringUTFChars(jip, 0); int port = static_cast<int>(jport); MulticastReceiver receiver(ip, port); return receiver.calculateBitrate(8000); // 8秒窗口 } ``` #### 4. 前端优化(虚拟滚动 + 增量更新) ```javascript // 使用虚拟滚动容器 const container = document.getElementById('channel-container'); const virtualScroll = new VirtualScroll(container, { itemHeight: 50, bufferSize: 20, renderItem: (index) => { const group = groups[index]; return ` <div class="channel-row"> <span>${group.name}</span> <span class="rate">${formatRate(group.rate)}</span> </div> `; } }); // 更新单个通道数据(避免全量刷新) function updateChannelRate(ip, port, rate) { const group = groups.find(g => g.ip === ip && g.port === port); if (group) { group.rate = rate; virtualScroll.updateItem(groups.indexOf(group)); } } // SSE 事件处理 es.onmessage = event => { const data = JSON.parse(event.data); updateChannelRate(data.ip, data.port, data.rate); }; ``` ### 关键优化点说明 1. **分层架构**: - 监控层:轻量级`ChannelMonitor`只负责状态收集广播 - 采集层:通过JNI调用高效C++组播接收库 - 展示层:前端使用虚拟滚动技术 2. **资源控制**: ```java // 添加通道数限制 if (MONITORS.size() >= MAX_CHANNELS) { resp.setStatus(429); resp.getWriter().write("{\"error\":\"达到最大通道监控上限\"}"); return; } ``` 3. **心跳优化**: ```java // 合并心跳数据包 private static final long HEARTBEAT_INTERVAL_MS = 15000; void broadcastWithHeartbeat(String json) { long now = System.currentTimeMillis(); if (now - lastBroadcast > HEARTBEAT_INTERVAL_MS || !json.isEmpty()) { // 发送数据包同时作为心跳 for (Subscriber s : subscribers) s.sendData(json); lastBroadcast = now; } } ``` 4. **数据采集优化**: - 使用共享内存存储统计数据 - 采用滑动窗口计算速率 - 避免重复解析TS流 ### 部署建议 1. **硬件加速**: ```bash # 启用网卡组播过滤 sudo ethtool -K eth2 rx-udp_tunnel-port-offload on ``` 2. **内核优化**: ```bash # 增加UDP缓冲区 sysctl -w net.core.rmem_max=16777216 sysctl -w net.core.wmem_max=16777216 ``` 3. **JVM调优**: ```bash java -XX:+UseG1GC -Xmx4g -Xms4g \ -XX:MaxDirectMemorySize=1g \ -Djava.library.path=/path/to/native/libs \ -jar your-app.jar ``` ### 监控与维护 添加监控端点: ```java @WebServlet("/monitor") public class MonitorServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { Map<String, Object> stats = Map.of( "channels", MONITORS.size(), "subscribers", MONITORS.values().stream() .mapToInt(m -> m.subscribers.size()).sum(), "memory", Runtime.getRuntime().totalMemory() ); resp.getWriter().write(GSON.toJson(stats)); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值