The Spring framework support tcp connection as well , i wrote code below to setup a simple socket server , i am confused about adding below futures to my socket server :
- authorizing clients based on a unique identifier ( for example a client secret received from client, maybe using TCP Connection Events )
- send a message directly to specific client (based on identifier)
- broadcast a message
UPDATE :
Config.sendMessage
added to send message to single clientConfig.broadCast
added to broadcast messageauthorizeIncomingConnection
to authorize clients , accept or reject connectionstcpConnections
static filed added to keep tcpEvent sources
Questions !
is using
tcpConnections
HashMap good idea ?!is the authorization method i implemented a good one ?!
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); } }
Usage :
- Socket Request/Response Mode
notify single client
http://localhost:8080/notify/{connectionId}/{message}
broadCast
http://localhost:8080/broadCast/{message}
Answer
The TcpConnectionOpenEvent
contains a connectionId
property. Each message coming from that client will have the same property in the IpHeaders.CONNECTION_ID
message header.
- Add a custom router that keeps track of the logged-on state of each connection.
- Lookup the connection id and if not authenticated, route to a challenge/response subflow.
- When authenticated, route to the normal flow.
To use arbitrary messaging (rather than request/response) use a TcpReceivingChannelAdapter
and TcpSendingMessageHandler
instead of an inbound gateway. Both configured to use the same connection factory. For each message sent to the message handler, add the IpHeaders.CONNECTION_ID
header to target the specific client.
To broadcast, send a message for each connection id.