2016-11-30 65 views
4

,我在下面設置一個簡單的socket服務器編寫代碼,我感到困惑添加下面期貨到我的socket服務器:春天TCP套接字,授權客戶和處理Spring框架支持TCP連接以及未決響應

    基於唯一的標識符
  • 授權客戶端(例如,從客戶端接收的客戶端密鑰,也許使用TCP Connection Events
  • 直接將消息發送到特定客戶端(基於標識符)
  • 廣播消息

UPDATE:

  • Config.sendMessage添加要發送消息給單個客戶端

  • Config.broadCast加入到廣播消息

  • authorizeIncomingConnection授權客戶端,接受或拒絕連接

  • tcpConnections靜態提起加入,以保持tcpEvent來源

問題!

  • 使用tcpConnections HashMap的好主意?!

  • 是我實施了一個好的授權方法嗎?!

Main.java

@SpringBootApplication 
public class Main { 

    public static void main(final String[] args) { 
     SpringApplication.run(Main.class, args); 
    } 

} 

Config.java

@EnableIntegration 
@IntegrationComponentScan 
@Configuration 
public class Config implements ApplicationListener<TcpConnectionEvent> { 

    private static final Logger LOGGER = Logger.getLogger(Config.class.getName()); 

    @Bean 
    public AbstractServerConnectionFactory AbstractServerConnectionFactory() { 
     return new TcpNetServerConnectionFactory(8181); 
    } 

    @Bean 
    public TcpInboundGateway TcpInboundGateway(AbstractServerConnectionFactory connectionFactory) { 
     TcpInboundGateway inGate = new TcpInboundGateway(); 
     inGate.setConnectionFactory(connectionFactory); 
     inGate.setRequestChannel(getMessageChannel()); 
     return inGate; 
    } 

    @Bean 
    public MessageChannel getMessageChannel() { 
     return new DirectChannel(); 
    } 

    @MessageEndpoint 
    public class Echo { 

     @Transformer(inputChannel = "getMessageChannel") 
     public String convert(byte[] bytes) throws Exception { 
      return new String(bytes); 
     } 

    } 

    private static ConcurrentHashMap<String, TcpConnection> tcpConnections = new ConcurrentHashMap<>(); 

    @Override 
    public void onApplicationEvent(TcpConnectionEvent tcpEvent) { 
     TcpConnection source = (TcpConnection) tcpEvent.getSource(); 
     if (tcpEvent instanceof TcpConnectionOpenEvent) { 

      LOGGER.info("Socket Opened " + source.getConnectionId()); 
      tcpConnections.put(tcpEvent.getConnectionId(), source); 

      if (!authorizeIncomingConnection(source.getSocketInfo())) { 
       LOGGER.warn("Socket Rejected " + source.getConnectionId()); 
       source.close(); 
      } 

     } else if (tcpEvent instanceof TcpConnectionCloseEvent) { 
      LOGGER.info("Socket Closed " + source.getConnectionId()); 
      tcpConnections.remove(source.getConnectionId()); 
     } 
    } 

    private boolean authorizeIncomingConnection(SocketInfo socketInfo) { 
     //Authorization Logic , Like Ip,Mac Address WhiteList or anyThing else ! 
     return (System.currentTimeMillis()/1000) % 2 == 0; 
    } 

    public static String broadCast(String message) { 
     Set<String> connectionIds = tcpConnections.keySet(); 
     int successCounter = 0; 
     int FailureCounter = 0; 
     for (String connectionId : connectionIds) { 
      try { 
       sendMessage(connectionId, message); 
       successCounter++; 
      } catch (Exception e) { 
       FailureCounter++; 
      } 
     } 
     return "BroadCast Result , Success : " + successCounter + " Failure : " + FailureCounter; 
    } 

    public static void sendMessage(String connectionId, final String message) throws Exception { 
     tcpConnections.get(connectionId).send(new Message<String>() { 
      @Override 
      public String getPayload() { 
       return message; 
      } 

      @Override 
      public MessageHeaders getHeaders() { 
       return null; 
      } 
     }); 
    } 
} 

MainController.java

@Controller 
public class MainController { 

    @RequestMapping("/notify/{connectionId}/{message}") 
    @ResponseBody 
    public String home(@PathVariable String connectionId, @PathVariable String message) { 
     try { 
      Config.sendMessage(connectionId, message); 
      return "Client Notified !"; 
     } catch (Exception e) { 
      return "Failed To Notify Client , cause : \n " + e.toString(); 
     } 
    } 


    @RequestMapping("/broadCast/{message}") 
    @ResponseBody 
    public String home(@PathVariable String message) { 
     return Config.broadCast(message); 
    } 

} 

用法:

  1. 插座請求/響應模式
  2. 通知單個客戶端

    http://localhost:8080/notify/{connectionId}/{message}

  3. 廣播

    http://localhost:8080/broadCast/{message}

回答

2

TcpConnectionOpenEvent包含connectionId屬性。每個來自該客戶端的消息在IpHeaders.CONNECTION_ID消息頭中都具有相同的屬性。

  1. 添加一個自定義路由器,用於跟蹤每個連接的登錄狀態。
  2. 查找連接標識,如果未通過身份驗證,則路由到質詢/響應子流。
  3. 通過身份驗證後,路由到正常流程。

要使用任意消息傳遞(而不是請求/響應),請使用TcpReceivingChannelAdapterTcpSendingMessageHandler而不是入站網關。兩者都配置爲使用相同的連接工廠。對於發送到消息處理程序的每條消息,添加IpHeaders.CONNECTION_ID標頭以定位特定客戶端。

要進行廣播,請爲每個連接ID發送消息。

+0

謝謝你,我更新了我的問題,你可以看看嗎? – Mojtabye

+0

有趣的技術;哈希映射是好的(雖然你應該在'sendMessage'中檢查'null',因爲有競爭條件)。但是,您無法在發佈連接打開事件的線程(即「接受」線程(服務器套接字))上執行身份驗證。數據將在不同的線程上發送。這就是爲什麼我說你需要一個路由器通過認證邏輯在套接字上路由第一個請求。 –

+0

你能解釋一下嗎?或者給我一個關於使用路由器的代碼片段? – Mojtabye