22. diel - Java chat - Server - Chat plugin
V minulej lekcii, Java chat - Klient - Chat service , sme vytvorili základ chatu. V dnešnom Java tutoriálu si naimplementujeme chat plugin na serveri.
ChatPlugin
Na serveri založíme nový plugin, ktorý bude mať na starosti funkcie
chatu. Začneme vytvorením nového balíka chat
v balíčku
plugins
. Vytvoríme triedu ChatPlugin
:
package cz.stechy.chat.plugins.chat; @Singleton public class ChatPlugin implements IPlugin { public static final String PLUGIN_NAME = "chat"; private void loginEventHandler(IEvent event) {} private void logoutEventHandler(IEvent event) {} private void chatMessageHandler(IEvent event) {} @Override public String getName() { return PLUGIN_NAME; } @Override public void init() { System.out.println("Inicializuji chat plugin."); } @Override public void registerMessageHandlers(IEventBus eventBus) { eventBus.registerEventHandler(LoginEvent.EVENT_TYPE, this::loginEventHandler); eventBus.registerEventHandler(LogoutEvent.EVENT_TYPE, this::logoutEventHandler); eventBus.registerEventHandler(ChatMessage.MESSAGE_TYPE, this::chatMessageHandler); } }
Trieda implementuje štandardné rozhranie IPlugin
. V metóde
registerMessageHandlers()
registrujeme tri handlermi:
LoginEvent.EVENT_TYPE
- reakcia na prihlásenie užívateľaLogoutEvent.EVENT_TYPE
- reakcia na odhlásenia užívateľaChatMessage.MESSAGE_TYPE
- reakcia na samotnú chat správu
Telá handler vyplníme, až budeme mať implementovanú service. Plugin
rovno zaregistrujeme vo výpočte Plugin
:
CHAT(ChatPlugin.class);
ChatService
O všetku logiku sa bude starať trieda ChatService
. Založíme
teda nový balík service
, v ktorom vytvoríme rozhraní
IChatService
a triedu ChatService
národné
implementačné toto rozhranie:
package cz.stechy.chat.plugins.chat.service; @ImplementedBy(ChatService.class) public interface IChatService { void addClient(IClient client, String id, String name); void removeClient(String id); void sendMessage(String destinationClientId, String sourceClientId, byte[] rawMessage); Optional <String> findIdByClient(IClient client); void informClientIsTyping(String destinationClientId, String sourceClientId, boolean typing); }
Metódy addClient()
/ removeClient()
budú
spravovať záznamy klientov v chate. Môžete sa pýtať, prečo to robiť
takto zložito, keď by sme mohli využiť AuthService
, ktorá už
záznamy o klientoch obsahuje. Je dôležité si uvedomiť, že procesy
pripojenie a prihlásenie užívateľa sú na sebe nezávislé. Mohla by teda
nastať situácia, že pripojený užívateľ sa nebude chcieť prihlásiť.
Metóda sendMessage()
prijíma v parametroch
destinationClientId
a sourceClientId
. Tieto parametre
reprezentujú ID cieľového a zdrojového klienta, aby server poznal, od koho
je správa poslaná a komu správa patrí. Metódou
informClientIsTyping()
budeme informovať cieľového klienta, že
klient začal / prestal písať.
Implementácia rozhrania
Teraz postupne naimplementujeme všetky metódy. Trieda
ChatService()
implementuje rozhranie IChatService
:
@Singleton class ChatService implements IChatService {}
V tejto triede vytvoríme súkromnú statickú vnútorné triedu
ChatClient
, ktorá bude slúžiť len ako prepravka. Trieda bude
obsahovať inštanciu rozhrania IClient
a meno klienta:
private static final class ChatClient { final IClient client; final String name; private ChatClient(IClient client, String name) { this.client = client; this.name = name; } }
V triede ChatService
vytvoríme triedny konštantu, ktorá bude
obsahovať mapu všetkých klientov, ktorí budú v chate:
private final Map<String, ChatClient> clients = new HashMap<>();
Ďalej si vytvoríme pomocnú súkromnú metódu, pomocou ktorej budeme rozosielať správy všetkým pripojeným klientom:
private void broadcastMessage(IMessage message) { clients.values().forEach(chatClient -> chatClient.client.sendMessageAsync(message)); }
Začneme metódou addClient()
:
@Override public synchronized void addClient(IClient client, String id, String name) { final ChatClient chatClient = new ChatClient(client, name); clients.forEach((clientId, entry) -> client.sendMessageAsync(new ChatMessage( new ChatMessageAdministrationData( new ChatMessageAdministrationClientState( ChatAction.CLIENT_CONNECTED, clientId, entry.name))))); clients.put(id, chatClient); broadcastMessage(new ChatMessage( new ChatMessageAdministrationData( new ChatMessageAdministrationClientState( ChatAction.CLIENT_CONNECTED, id, name)))); }
Keď budeme pridávať nového klienta do mapy, tak najskôr tomuto klientovi odošleme zoznam všetkých pripojených klientov. Až potom ho pridáme do našej kolekcie. Na záver odošleme všetkým pripojeným klientom (i tomu novému), že sa pripojil nový klient. Týmto trikom si bude môcť každý užívateľ písať sám so sebou.
Metóda pre odobratie klienta removeClient()
bude
nasledujúce:
@Override public synchronized void removeClient(String id) { clients.remove(id); broadcastMessage(new ChatMessage( new ChatMessageAdministrationData( new ChatMessageAdministrationClientState( ChatAction.CLIENT_DISCONNECTED, id)))); }
Najskôr odoberieme klienta z mapy. Potom odošleme správu ostatným klientom, že sa niekto odhlásil.
Metóda pre odoslanie správy bude veľmi jednoduchá:
@Override public void sendMessage(String destinationClientId, String sourceClientId, byte[] rawMessage) { clients.get(destinationClientId).client.sendMessageAsync(new ChatMessage(new ChatMessageCommunicationData(sourceClientId, rawMessage))); }
Z mapy klientov zoberie cieľového klienta podľa jeho ID a odošle mu správu, ktorú dostane ako parameter.
Predposledný metóda, ktorú musíme implementovať, je metóda
informClientIsTyping()
:
@Override public void informClientIsTyping(String destinationClientId, String sourceClientId, boolean typing) { clients.get(destinationClientId).client.sendMessageAsync( new ChatMessage( new ChatMessageAdministrationData( new ChatMessageAdministrationClientTyping( typing ? ChatAction.CLIENT_TYPING : ChatAction.CLIENT_NOT_TYPING, sourceClientId )))); }
Opäť získame z mapy klientov cieľového klienta, ktorému pošleme správu s informáciou, či ak klient píše, alebo prestal písať.
Posledný metóda, ktorú implementujeme, je metóda
findIdByClient()
. Pomocou tejto metódy budeme hľadať ID klienta,
na základe inštancie rozhrania IClient
:
@Override public Optional <String> findIdByClient(IClient client) { final Optional <Entry <String, ChatClient>> entryOptional = clients.entrySet() .stream() .filter(entry -> entry.getValue().client == client) .findFirst(); return entryOptional.map(Entry::getKey); }
Vo filtri porovnávame pomocou ==
. To si môžeme dovoliť,
pretože máme istotu, že takáto inštancie sa vyskytuje.
Dokončenie pluginu
Teraz sa vrátime k triede ChatPlugin
, v ktorej doplníme tela
metód. Než sa do toho pustíme, vytvoríme novú inštančný konštantu typu
IChatService
a necháme si ju odovzdať v konstruktoru:
private final IChatService chatService; @Inject public ChatPlugin(IChatService chatService) { this.chatService = chatService; }
Telá metód loginEventHandler()
a
logoutEventHandler()
budú pridávať / odoberať klientov z
chatService
.
private void loginEventHandler(IEvent event) { final LoginEvent loginEvent = (LoginEvent) event; chatService.addClient(loginEvent.client, loginEvent.user.id, loginEvent.user.name); } private void logoutEventHandler(IEvent event) { final LogoutEvent logoutEvent = (LogoutEvent) event; chatService.removeClient(logoutEvent.user.id); }
Metódu chatMessageHandler()
si rozpíšeme podrobnejšie.
Najskôr získame dáta zo správy:
final MessageReceivedEvent messageReceivedEvent = (MessageReceivedEvent) event; final IClient client = messageReceivedEvent.getClient(); final ChatMessage chatMessage = (ChatMessage) messageReceivedEvent.getReceivedMessage(); final IChatMessageData chatMessageData = (IChatMessageData) chatMessage.getData();
Nasleduje veľmi podobný rozhodovací proces, ako tomu bolo na strane
klienta. Opäť získame typ správy metódou getDataType()
.
Pokiaľ bude správa administratívne, získame z nej potrebné dáta a
rozhodneme sa, aká akcia sa má stať. Pokiaľ pôjde o odoslanie správy,
odošleme správu správnemu príjemcovi. Celý rozhodovací proces je
nižšie:
switch (chatMessageData.getDataType()) { case DATA_ADMINISTRATION: IChatMessageAdministrationData administrationData = (IChatMessageAdministrationData) chatMessageData.getData(); switch (administrationData.getAction()) { case CLIENT_REQUEST_CONNECT: final ChatMessageAdministrationClientRequestConnect clientRequestConnect = (ChatMessageAdministrationClientRequestConnect) administrationData; final String clientId = clientRequestConnect.getId(); final String clientName = clientRequestConnect.getName(); chatService.addClient(client, clientId, clientName); break; case CLIENT_DISCONNECTED: final ChatMessageAdministrationClientState clientDisconnected = (ChatMessageAdministrationClientState) administrationData; final String disconnectedClientId = clientDisconnected.getId(); chatService.removeClient(disconnectedClientId); break; case CLIENT_TYPING: final ChatMessageAdministrationClientTyping clientIsTyping = (ChatMessageAdministrationClientTyping) administrationData; final String typingClientId = clientIsTyping.getId(); chatService.informClientIsTyping(typingClientId, chatService.findIdByClient(client).orElse(""), true); break; case CLIENT_NOT_TYPING: final ChatMessageAdministrationClientTyping clientIsNotTyping = (ChatMessageAdministrationClientTyping) administrationData; final String notTypingClientId = clientIsNotTyping.getId(); chatService.informClientIsTyping(notTypingClientId, chatService.findIdByClient(client).orElse(""), false); break; default: throw new IllegalArgumentException("Neplatný argument. " + administrationData.getAction()); } break; case DATA_COMMUNICATION: final ChatMessageCommunicationDataContent communicationDataContent = (ChatMessageCommunicationDataContent) chatMessageData.getData(); final String destinationClientId = communicationDataContent.getDestination(); final String sourceClientId = chatService.findIdByClient(client).orElse(""); final byte[] rawMessage = communicationDataContent.getData(); chatService.sendMessage(destinationClientId, sourceClientId, rawMessage); break; default: throw new IllegalArgumentException("Neplatný argument." + chatMessageData.getDataType()); }
Tým by sme mali kompletnú funkcionalitu servera. V budúcej lekcii, Java chat - Klient - Dokončenie 1. časť , začneme implementovať samotné funkcie na GUI klienta.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 15x (133.94 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java