init
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package com.tt.signaling;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SignalServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SignalServerApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tt.signaling.config;
|
||||
|
||||
import com.tt.signaling.handler.SignalWebSocketHandler;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
private final SignalWebSocketHandler signalWebSocketHandler;
|
||||
|
||||
public WebSocketConfig(SignalWebSocketHandler signalWebSocketHandler) {
|
||||
this.signalWebSocketHandler = signalWebSocketHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(signalWebSocketHandler, "/ws/signal")
|
||||
.setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.tt.signaling.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tt.signaling.manager.SessionManager;
|
||||
import com.tt.signaling.model.DeviceType;
|
||||
import com.tt.signaling.model.SignalMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class SignalWebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHandler.class);
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
|
||||
public SignalWebSocketHandler(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
logger.info("New WebSocket connection: {}", session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
String payload = message.getPayload();
|
||||
logger.debug("Received message: {}", payload);
|
||||
|
||||
try {
|
||||
SignalMessage signalMessage = objectMapper.readValue(payload, SignalMessage.class);
|
||||
String type = signalMessage.getType();
|
||||
|
||||
if (type == null) {
|
||||
logger.warn("Message type is null");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type.toUpperCase()) {
|
||||
case "REGISTER":
|
||||
handleRegister(session, signalMessage);
|
||||
break;
|
||||
case "DEVICE_LIST":
|
||||
handleDeviceList(session, signalMessage);
|
||||
break;
|
||||
case "OFFER":
|
||||
case "ANSWER":
|
||||
case "ICE_CANDIDATE":
|
||||
case "CONTROL_COMMAND":
|
||||
forwardMessage(signalMessage);
|
||||
break;
|
||||
default:
|
||||
// 其他消息类型直接转发
|
||||
forwardMessage(signalMessage);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling message: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
|
||||
logger.info("WebSocket connection closed: {} ({})", session.getId(), status);
|
||||
sessionManager.unregisterSession(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||
logger.error("Transport error on session {}: {}", session.getId(), exception.getMessage());
|
||||
sessionManager.unregisterSession(session);
|
||||
if (session.isOpen()) {
|
||||
session.close(CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRegister(WebSocketSession session, SignalMessage message) {
|
||||
String deviceId = message.getFromDeviceId();
|
||||
String deviceTypeStr = message.getDeviceType();
|
||||
|
||||
if (deviceId == null || deviceTypeStr == null) {
|
||||
logger.warn("Invalid REGISTER message: missing deviceId or deviceType");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DeviceType deviceType = DeviceType.valueOf(deviceTypeStr.toUpperCase());
|
||||
sessionManager.registerDevice(deviceId, deviceType, session);
|
||||
|
||||
// 回复注册成功
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("type", "REGISTER_SUCCESS");
|
||||
response.put("deviceId", deviceId);
|
||||
sendToSession(session, response);
|
||||
|
||||
logger.info("Device {} registered as {}", deviceId, deviceType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Invalid device type: {}", deviceTypeStr);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceList(WebSocketSession session, SignalMessage message) {
|
||||
List<String> controllers = sessionManager.getDevicesByType(DeviceType.CONTROLLER);
|
||||
List<String> controlled = sessionManager.getDevicesByType(DeviceType.CONTROLLED);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("type", "DEVICE_LIST");
|
||||
response.put("controllers", controllers);
|
||||
response.put("controlled", controlled);
|
||||
sendToSession(session, response);
|
||||
}
|
||||
|
||||
private void forwardMessage(SignalMessage message) {
|
||||
String toDeviceId = message.getToDeviceId();
|
||||
if (toDeviceId == null) {
|
||||
logger.warn("Cannot forward message: toDeviceId is null");
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketSession targetSession = sessionManager.getSession(toDeviceId);
|
||||
if (targetSession == null || !targetSession.isOpen()) {
|
||||
logger.warn("Target device {} is not online", toDeviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String jsonMessage = objectMapper.writeValueAsString(message);
|
||||
targetSession.sendMessage(new TextMessage(jsonMessage));
|
||||
logger.debug("Forwarded {} from {} to {}", message.getType(), message.getFromDeviceId(), toDeviceId);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error forwarding message to {}: {}", toDeviceId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToSession(WebSocketSession session, Object data) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(data);
|
||||
session.sendMessage(new TextMessage(json));
|
||||
} catch (IOException e) {
|
||||
logger.error("Error sending message to session {}: {}", session.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.tt.signaling.manager;
|
||||
|
||||
import com.tt.signaling.model.DeviceType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
public class SessionManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
// deviceId -> WebSocketSession
|
||||
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||
// deviceId -> deviceType
|
||||
private final Map<String, DeviceType> deviceTypes = new ConcurrentHashMap<>();
|
||||
// session.getId() -> deviceId (用于断开连接时清理)
|
||||
private final Map<String, String> sessionToDevice = new ConcurrentHashMap<>();
|
||||
|
||||
public void registerDevice(String deviceId, DeviceType deviceType, WebSocketSession session) {
|
||||
sessions.put(deviceId, session);
|
||||
deviceTypes.put(deviceId, deviceType);
|
||||
sessionToDevice.put(session.getId(), deviceId);
|
||||
logger.info("Device registered: {} ({})", deviceId, deviceType);
|
||||
}
|
||||
|
||||
public void unregisterSession(WebSocketSession session) {
|
||||
String deviceId = sessionToDevice.remove(session.getId());
|
||||
if (deviceId != null) {
|
||||
sessions.remove(deviceId);
|
||||
deviceTypes.remove(deviceId);
|
||||
logger.info("Device unregistered: {}", deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketSession getSession(String deviceId) {
|
||||
return sessions.get(deviceId);
|
||||
}
|
||||
|
||||
public boolean isDeviceOnline(String deviceId) {
|
||||
WebSocketSession session = sessions.get(deviceId);
|
||||
return session != null && session.isOpen();
|
||||
}
|
||||
|
||||
public List<String> getDevicesByType(DeviceType type) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Map.Entry<String, DeviceType> entry : deviceTypes.entrySet()) {
|
||||
if (entry.getValue() == type && isDeviceOnline(entry.getKey())) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> getAllOnlineDevices() {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Map.Entry<String, WebSocketSession> entry : sessions.entrySet()) {
|
||||
if (entry.getValue().isOpen()) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.tt.signaling.model;
|
||||
|
||||
public enum DeviceType {
|
||||
CONTROLLER,
|
||||
CONTROLLED
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.tt.signaling.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SignalMessage {
|
||||
|
||||
private String type; // OFFER, ANSWER, ICE_CANDIDATE, CONTROL_COMMAND, REGISTER, DEVICE_LIST
|
||||
private String fromDeviceId; // 发送方设备ID
|
||||
private String toDeviceId; // 目标设备ID
|
||||
private String deviceType; // CONTROLLER 或 CONTROLLED
|
||||
private String payload; // JSON格式的信令数据(SDP/ICE/控制指令)
|
||||
|
||||
public SignalMessage() {}
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
|
||||
public String getFromDeviceId() { return fromDeviceId; }
|
||||
public void setFromDeviceId(String fromDeviceId) { this.fromDeviceId = fromDeviceId; }
|
||||
|
||||
public String getToDeviceId() { return toDeviceId; }
|
||||
public void setToDeviceId(String toDeviceId) { this.toDeviceId = toDeviceId; }
|
||||
|
||||
public String getDeviceType() { return deviceType; }
|
||||
public void setDeviceType(String deviceType) { this.deviceType = deviceType; }
|
||||
|
||||
public String getPayload() { return payload; }
|
||||
public void setPayload(String payload) { this.payload = payload; }
|
||||
}
|
||||
11
WebRTCSignalServer/src/main/resources/application.yml
Normal file
11
WebRTCSignalServer/src/main/resources/application.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: webrtc-signal-server
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.tt.signaling: DEBUG
|
||||
org.springframework.web.socket: DEBUG
|
||||
Reference in New Issue
Block a user