package com.taixin.dvbc2;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class LoadProgramServlet extends HttpServlet {
private static final String MCAST_IP = "236.78.78.1";
private static final int PORT = 59000;
private static final int MTU = 7 * 188;
private static final Gson GSON = new Gson();
private static final Map<Integer, Integer> progToPmtPid = new HashMap<Integer, Integer>();
private static final Map<Integer, String> programNames = new HashMap<Integer, String>();
private static final Map<Integer, Integer> pidToProgram = new HashMap<Integer, Integer>();
private static final Map<Integer, Long> pidBytesCounter = new HashMap<Integer, Long>();
private static final long REPORT_INTERVAL_NS = 3000000000L; // 3秒
private static long lastReportNs = System.nanoTime();
private static final Map<Integer, SectionAssembler> assemblers = new HashMap<Integer, SectionAssembler>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取参数
final String ip = request.getParameter("ip");
final String portStr = request.getParameter("port");
if (ip == null || portStr == null) {
response.setStatus(400);
response.getWriter().write("{\"error\":\"缺少参数 ip 或 port\"}");
return;
}
final int port = Integer.parseInt(portStr);
// SSE 响应头
response.setContentType("text/event-stream;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Connection", "keep-alive");
final PrintWriter writer = response.getWriter();
new Thread(new Runnable() {
@Override
public void run() {
try {
processInputChannelSSE(ip, port, writer);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void processInputChannelSSE(String ip, int port, PrintWriter writer) throws Exception {
MulticastSocket sock = new MulticastSocket(port);
InetAddress group = InetAddress.getByName(ip);
sock.joinGroup(group);
byte[] buf = new byte[MTU];
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
lastReportNs = System.nanoTime();
while (true) { // 一直接收数据
sock.receive(pkt);
int len = pkt.getLength();
int off = 0;
while (off + 188 <= len) {
parseTsPacket(buf, off);
off += 188;
}
long now = System.nanoTime();
if (now - lastReportNs > REPORT_INTERVAL_NS) {
String jsonResponse = buildProgramReportJson();
if (jsonResponse != null) {
writer.write("data: " + jsonResponse + "\n\n");
writer.flush();
}
lastReportNs = now;
}
}
}
private String buildProgramReportJson() {
Map<Integer, Long> programBytes = new HashMap<Integer, Long>();
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 oldVal = programBytes.containsKey(prog) ? programBytes.get(prog) : 0L;
programBytes.put(prog, oldVal + bytes);
}
}
pidBytesCounter.clear();
if (programBytes.isEmpty()) return null;
List<Map<String, Object>> programsData = new ArrayList<Map<String, Object>>();
for (Map.Entry<Integer, Long> e : programBytes.entrySet()) {
int program = e.getKey();
long bytes = e.getValue();
double bits = bytes * 8.0;
double mbps = bits / 3.0 / 1000000.0; // 3秒间隔
String name = programNames.containsKey(program) ? programNames.get(program) : "(未知节目)";
Map<String, Object> programData = new HashMap<String, Object>();
programData.put("name", name);
programData.put("no", program);
programData.put("rate", String.format("%.3f Mbit/s", mbps));
programsData.add(programData);
}
return GSON.toJson(programsData);
}
// --- 这里保留你原来的 parseTsPacket / parsePAT / parseSDT / parsePMT / SectionAssembler 等方法 ---
// (我不再重复全部代码,只保留修改重点)
private static 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;
if (afc == 3) {
int afl = buf[i] & 0xFF;
i += 1 + afl;
}
if (i >= off + 188) return;
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 static boolean containsValue(Map<Integer, Integer> map, int val) {
for (Integer v : map.values()) {
if (v != null && v == val) return true;
}
return false;
}
private static 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 static void parsePAT(byte[] sec) {
int pos = 8;
int 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 static void parsePMT(byte[] sec) {
int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF);
programNames.put(programNumber, "Program " + programNumber);
}
private static void parseSDT(byte[] sec) {
int pos = 11;
int 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 >= 3) {
int serviceProviderNameLen = sec[descPos + 3] & 0xFF;
int nameLenPos = descPos + 4 + serviceProviderNameLen;
if (nameLenPos + 1 <= descPos + 2 + len) {
int serviceNameLen = sec[nameLenPos] & 0xFF;
int nameStart = nameLenPos + 1;
int nameEnd = Math.min(nameStart + serviceNameLen, 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;
}
}
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) {
return "";
}
}
static class SectionAssembler {
private final ByteArrayOutputStream cur = new ByteArrayOutputStream();
private Integer expectedLen = null;
private final Queue<byte[]> ready = new LinkedList<byte[]>();
void push(byte[] src, int off, int len, boolean payloadStart, int continuityCounter) {
int i = off;
if (payloadStart) {
int pointer = src[i] & 0xFF;
i += 1 + pointer;
startNew();
}
while (i < off + len) {
if (expectedLen == null) {
if (off + len - i < 3) {
write(src, i, off + len - i);
return;
}
int sl = ((src[i + 1] & 0x0F) << 8) | (src[i + 2] & 0xFF);
expectedLen = sl + 3;
}
int remaining = expectedLen - cur.size();
int copy = Math.min(remaining, off + len - i);
write(src, i, copy);
i += copy;
if (cur.size() == expectedLen) {
ready.add(cur.toByteArray());
startNew();
}
}
}
boolean hasSection() { return !ready.isEmpty(); }
byte[] pollSection() { return ready.poll(); }
private void startNew() { cur.reset(); expectedLen = null; }
private void write(byte[] src, int off, int len) { cur.write(src, off, len); }
}
} document.getElementById('btnParse').addEventListener('click', () => {
const selectedGroup = groups.find(g => g.selected);
if (!selectedGroup) {
alert("请先选择一个输入通道");
return;
}
const { ip, port, name, id } = selectedGroup;
const es = new EventSource(`/loadProgramServlet?ip=${ip}&port=${port}&name=${name}&id=${id}`);
es.onmessage = function (event) {
try {
const data = JSON.parse(event.data);
console.log('收到节目列表:', data);
selectedGroup.programs = data;
renderTree();
} catch (e) {
console.error("解析 SSE 数据失败", e);
}
};
es.onerror = function (err) {
console.error("SSE 连接出错", err);
};
}); 报错 AdminServlet:1101 加载节目数据失败 405 <html><head><title>Apache Tomcat/6.0.29 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 405 - HTTP method POST is not supported by this URL</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>HTTP method POST is not supported by this URL</u></p><p><b>description</b> <u>The specified HTTP method is not allowed for the requested resource (HTTP method POST is not supported by this URL).</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.29</h3></body></htm