Een gids voor de Java API voor WebSocket

1. Overzicht

WebSocket biedt een alternatief voor de beperking van efficiënte communicatie tussen de server en de webbrowser door bidirectionele, full-duplex, real-time client / server-communicatie te bieden. De server kan op elk moment gegevens naar de klant sturen. Omdat het via TCP werkt, biedt het ook een low-latency communicatie op laag niveau en vermindert het de overhead van elk bericht.

In dit artikel zullen we de Java API voor WebSockets bekijken door een chat-achtige applicatie te maken.

2. JSR 356

JSR 356 of de Java API voor WebSocket, specificeert een API die Java-ontwikkelaars kunnen gebruiken om WebSockets in hun applicaties te integreren - zowel aan de serverzijde als aan de Java-clientzijde.

Deze Java API biedt componenten aan zowel server- als clientzijde:

  • Server: alles in de javax.websocket.server pakket.
  • Cliënt: de inhoud van javax.websocket pakket, dat bestaat uit API's aan de clientzijde en ook gemeenschappelijke bibliotheken voor zowel server als client.

3. Een chat opbouwen met WebSockets

We zullen een heel eenvoudige chat-achtige applicatie bouwen. Elke gebruiker kan de chat vanuit elke browser openen, zijn naam typen, inloggen op de chat en beginnen te communiceren met iedereen die met de chat is verbonden.

We beginnen met het toevoegen van de nieuwste afhankelijkheid aan het pom.xml het dossier:

 javax.websocket javax.websocket-api 1.1 

De laatste versie is hier te vinden.

Om Java te converteren Voorwerpen in hun JSON-representaties en vice versa, we gebruiken Gson:

 com.google.code.gson gson 2.8.0 

De nieuwste versie is beschikbaar in de Maven Central-repository.

3.1. Eindpuntconfiguratie

Er zijn twee manieren om eindpunten te configureren: annotatiegebaseerd en extensie-gebaseerd. U kunt het javax.websocket.Endpoint class of gebruik speciale annotaties op methode-niveau. Aangezien het annotatiemodel leidt tot schonere code in vergelijking met het programmatische model, is de annotatie de conventionele coderingskeuze geworden. In dit geval worden WebSocket-eindpuntlevenscyclusgebeurtenissen afgehandeld door de volgende annotaties:

  • @ServerEndpoint: Indien versierd met @ServerEndpoint, de container zorgt ervoor dat de klasse beschikbaar is als WebSocket server luistert naar een specifieke URI-ruimte
  • @ClientEndpoint: Een klas die met deze annotatie is versierd, wordt behandeld als een WebSocket cliënt
  • @OnOpen: Een Java-methode met @OnOpen wordt aangeroepen door de container wanneer een nieuw WebSocket verbinding is gestart
  • @OnMessage: Een Java-methode, geannoteerd met @OnMessage, ontvangt de informatie van de WebSocket container wanneer een bericht naar het eindpunt wordt verzonden
  • @OnError: Een methode met @OnError wordt aangeroepen als er een probleem is met de communicatie
  • @OnClose: Wordt gebruikt om een ​​Java-methode te versieren die wordt aangeroepen door de container wanneer de WebSocket verbinding wordt gesloten

3.2. Het servereindpunt schrijven

We declareren een Java-klasse WebSocket server-eindpunt door het te annoteren met @ServerEndpoint. We specificeren ook de URI waar het eindpunt wordt geïmplementeerd. De URI is relatief ten opzichte van de root van de servercontainer gedefinieerd en moet beginnen met een schuine streep:

@ServerEndpoint (value = "/ chat / {gebruikersnaam}") openbare klasse ChatEndpoint {@OnOpen public void onOpen (sessiesessie) gooit IOException {// Get sessie en WebSocket-verbinding} @OnMessage public void onMessage (sessiesessie, bericht) gooit IOException {// Behandel nieuwe berichten} @OnClose public void onClose (sessie sessie) gooit IOException {// WebSocket verbinding sluit} @OnError public void onError (sessie sessie, Throwable throwable) {// Voer hier foutafhandeling uit}}

De bovenstaande code is het skelet van het server-eindpunt voor onze chat-achtige applicatie. Zoals u kunt zien, hebben we 4 annotaties die zijn toegewezen aan hun respectieve methoden. Hieronder ziet u de implementatie van dergelijke methoden:

@ServerEndpoint (value = "/ chat / {gebruikersnaam}") openbare klasse ChatEndpoint {privésessiesessie; privé statisch Set chatEndpoints = nieuwe CopyOnWriteArraySet (); privé statische HashMap-gebruikers = nieuwe HashMap (); @OnOpen public void onOpen (sessie sessie, @PathParam ("gebruikersnaam") String gebruikersnaam) genereert IOException {this.session = sessie; chatEndpoints.add (dit); users.put (session.getId (), gebruikersnaam); Bericht bericht = nieuw bericht (); message.setFrom (gebruikersnaam); message.setContent ("Verbonden!"); bericht uitzenden); } @OnMessage public void onMessage (sessie sessie, bericht bericht) gooit IOException {message.setFrom (users.get (session.getId ())); bericht uitzenden); } @OnClose public void onClose (sessie sessie) gooit IOException {chatEndpoints.remove (this); Bericht bericht = nieuw bericht (); message.setFrom (users.get (session.getId ())); message.setContent ("Verbinding verbroken!"); bericht uitzenden); } @OnError public void onError (Session session, Throwable throwable) {// Voer hier foutafhandeling uit} private static void broadcast (Message message) gooit IOException, EncodeException {chatEndpoints.forEach (endpoint -> {synchronized (endpoint) {try {endpoint .session.getBasicRemote (). sendObject (bericht);} catch (IOException | EncodeException e) {e.printStackTrace ();}}}); }}

Wanneer een nieuwe gebruiker inlogt (@OnOpen) wordt onmiddellijk toegewezen aan een datastructuur van actieve gebruikers. Vervolgens wordt een bericht gemaakt en naar alle eindpunten verzonden met behulp van de uitzending methode.

Deze methode wordt ook gebruikt wanneer een nieuw bericht wordt verzonden (@OnMessage) door een van de aangesloten gebruikers - dit is het hoofddoel van de chat.

Als er op een bepaald moment een fout optreedt, wordt de methode met de annotatie @OnError behandelt het. U kunt deze methode gebruiken om de informatie over de fout te loggen en de eindpunten te wissen.

Ten slotte, wanneer een gebruiker niet langer verbonden is met de chat, kan de methode @OnClose wist het eindpunt en verzendt naar alle gebruikers dat de verbinding met een gebruiker is verbroken.

4. Berichttypen

De WebSocket-specificatie ondersteunt twee on-wire gegevensindelingen: tekst en binair. De API ondersteunt beide formaten, voegt mogelijkheden toe om met Java-objecten te werken en health check-berichten (ping-pong) zoals gedefinieerd in de specificatie:

  • Tekst: Alle tekstuele gegevens (java.lang.String, primitieven of hun gelijkwaardige wrapper-klassen)
  • Binair: Binaire gegevens (bijv. Audio, afbeelding enz.) Weergegeven door een java.nio.ByteBuffer of een byte[] (byte-array)
  • Java-objecten: De API maakt het mogelijk om te werken met native (Java-object) representaties in uw code en aangepaste transformatoren (encoders / decoders) te gebruiken om ze om te zetten in compatibele on-wire formaten (tekst, binair) toegestaan ​​door het WebSocket-protocol
  • Pingpong: EEN javax.websocket.PongMessage is een bevestiging verzonden door een WebSocket-peer in reactie op een statuscheck (ping) -verzoek

Voor onze applicatie gebruiken we Java-objecten. We zullen de klassen maken voor het coderen en decoderen van berichten.

4.1. Encoder

Een encoder neemt een Java-object en produceert een typische weergave die geschikt is voor verzending als een bericht, zoals JSON, XML of binaire weergave. Encoders kunnen worden gebruikt door het Encoder.Text of Encoder.Binair interfaces.

In de onderstaande code definiëren we de klasse Bericht worden gecodeerd en in de methode coderen we gebruiken Gson voor het coderen van het Java-object naar JSON:

openbare klasse Bericht {privé String van; private String naar; privé String-inhoud; // standaard constructeurs, getters, setters}
openbare klasse MessageEncoder implementeert Encoder.Text {privé statische Gson gson = nieuwe Gson (); @Override public String encode (berichtbericht) gooit EncodeException {return gson.toJson (bericht); } @Override public void init (EndpointConfig endpointConfig) {// Aangepaste initialisatielogica} @Override public void destroy () {// Sluit bronnen}}

4.2. Decoder

Een decoder is het tegenovergestelde van een encoder en wordt gebruikt om gegevens weer om te zetten in een Java-object. Decoders kunnen worden geïmplementeerd met behulp van de Decoder.Tekst of Decoder. Binair interfaces.

Zoals we zagen met de encoder, is de decoderen methode is waar we de JSON nemen die is opgehaald in het bericht dat naar het eindpunt is verzonden en Gson gebruiken om het te transformeren naar een Java-klasse met de naam Bericht:

openbare klasse MessageDecoder implementeert Decoder.Text {privé statische Gson gson = nieuwe Gson (); @Override public Message decode (String s) gooit DecodeException {return gson.fromJson (s, Message.class); } @Override openbare boolean willDecode (String s) {return (s! = Null); } @Override public void init (EndpointConfig endpointConfig) {// Aangepaste initialisatielogica} @Override public void destroy () {// Sluit bronnen}}

4.3. Encoder en decoder instellen in server-eindpunt

Laten we alles samenvoegen door de klassen toe te voegen die zijn gemaakt voor het coderen en decoderen van de gegevens op annotatie op klasniveau @ServerEndpoint:

@ServerEndpoint (waarde = "/ chat / {gebruikersnaam}", decoders = MessageDecoder.class, encoders = MessageEncoder.class)

Elke keer dat berichten naar het eindpunt worden verzonden, worden ze automatisch geconverteerd naar JSON- of Java-objecten.

5. Conclusie

In dit artikel hebben we gekeken naar wat de Java API voor WebSockets is en hoe deze ons kan helpen bij het bouwen van applicaties zoals deze real-time chat.

We zagen de twee programmeermodellen voor het maken van een eindpunt: annotaties en programmatisch. We hebben een eindpunt gedefinieerd met behulp van het annotatiemodel voor onze applicatie samen met de levenscyclusmethoden.

Om ook heen en weer te kunnen communiceren tussen de server en de client, hebben we gezien dat we encoders en decoders nodig hebben om Java-objecten naar JSON te converteren en vice versa.

De JSR 356 API is heel eenvoudig en het op annotaties gebaseerde programmeermodel maakt het heel gemakkelijk om WebSocket-applicaties te bouwen.

Om de applicatie uit te voeren die we in het voorbeeld hebben gebouwd, hoeven we alleen maar het WAR-bestand op een webserver te plaatsen en naar de URL te gaan: // localhost: 8080 / java-websocket /. De link naar de repository vind je hier.