IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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ľa
  • LogoutEvent.EVENT_TYPE - reakcia na odhlásenia užívateľa
  • ChatMessage.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

 

Predchádzajúci článok
Java chat - Klient - Chat service
Všetky články v sekcii
Server pre klientskej aplikácie v Jave
Preskočiť článok
(neodporúčame)
Java chat - Klient - Dokončenie 1. časť
Článok pre vás napísal Petr Štechmüller
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje primárně programování v Javě, ale nebojí se ani webových technologií.
Aktivity