Location>code7788 >text

Android Spingboot implementation of SSE communication cases

Popularity:688 ℃/2024-07-24 15:38:08

SSE

SSE (Server-Sent Events) is a technology used to realize the server actively push data to the client, it is based on the HTTP protocol, using the characteristics of its long connection, to establish a persistent connection between the client and the server, and through this connection to realize the server to the client's real-time data push.

Server-Sent Events (SSE) and Sockets can both be used to implement real-time communication where the server pushes messages to the client, with contrasting differences:
SSE:

Pros.
Easy to use, just send HTTP streaming responses.
Automatically handles network outages and reconnections.
Supports events natively implemented by browsers, such as "error" and "message".

Disadvantages:
One-way communication, server can only send messages to the client.
Each connection requires a thread or process on the server side.

Socket:

Pros:
Two-way communication, both client and server can send or receive messages.
Can handle more complex application scenarios such as two-way conversations, multiplayer games, etc.
The server can manage connections more finely, such as using long or short connections.

Disadvantages:
Needs to handle network outages and reconnections, which is relatively complex.
Requires both client-side and server-side code to handle socket communication.
Higher requirements for developers, requiring in-depth knowledge of network programming.

SSE Usage Scenarios:

Usage scenarios mainly include application scenarios that require the server to actively push data to the client, such as AI Q&A chat, real-time news, stock quotes, and so on.

case (law)

The server side is implemented based on springboot and supports SSE by default;
The Android client is based on the OkHttp implementation, which also supports SSE;

server-side interface development

package ;

import org.;
import org.;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;
import ;


@RestController
@RequestMapping("/sse")
public class SSEController {
    Logger logger = ();
    public static Map<String, SseEmitter> sseEmitters = new ConcurrentHashMap<>();

    /**
     * reception (of transmitted signal)sserequesting,asynchronous processing,Returning results in batches,Then closeSseEmitter
     * @return SseEmitter
     */
    @GetMapping("/stream-sse")
    public SseEmitter handleSse() {
        SseEmitter emitter = new SseEmitter();
        // Send a message in a new thread,to avoid blocking the main thread
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Map<String, Object> event = new HashMap<>();
                    String mes = "Hello, SSE " + (i+1);
                    ("message", mes);
                    (": "+mes);
                    (event);
                    (200);
                }
                (); // Finish sending
            } catch (IOException | InterruptedException e) {
                (e); // Send Error
            }
        }).start();
        return emitter;
    }

    /**
     * reception (of transmitted signal)sserequesting,asynchronous processing,Returning results in batches,parallel storageSseEmitter,Can be called from outsidesendMsgconnector,Continue to return results
     * @param uid Customer Unique Identification
     * @return SseEmitter
     */
    @GetMapping("/stream-sse1")
    public SseEmitter handleSse1(@RequestParam("uid") String uid) {
        SseEmitter emitter = new SseEmitter();
        (uid, emitter);
        // Send a message in a new thread,to avoid blocking the main thread
        new Thread(() -> {
            try {
                for (int i = 10; i < 15; i++) {
                    Map<String, Object> event = new HashMap<>();
                    String mes = "Hello, SSE " + (i+1);
                    ("message", mes);
                    (": "+mes);
                    (event);
                    (200); // each2spike
                }
            } catch (IOException | InterruptedException e) {
                (e); // Send Error
            }
        }).start();
        return emitter;
    }

    /**
     * external callsendMsgconnector,Get the cached one based on the logo of theSseEmitter,Continue to return results
     * @param uid Customer Unique Identification
     */
    @GetMapping("/sendMsg")
    public void sendMsg(@RequestParam("uid") String uid) {
        ("The server side sends a message to " + uid);
        SseEmitter emitter = (uid);
        if(emitter != null){
            new Thread(() -> {
                try {
                    for (int i = 20; i < 30; i++) {
                        Map<String, Object> event = new HashMap<>();
                        String mes = "Hello, SSE " + (i+1);
                        ("message", mes);
                        (": "+mes);
                        (event);
                        (200); // each2spike
                    }
                    (().name("stop").data(""));
                    (); // close connection
                    ("The server closed the connection voluntarily to " + uid);
                } catch (IOException | InterruptedException e) {
                    (e); // error finish
                }
            }).start();
        }
    }
}

The code defines three interfaces that implement two main functions:
stream-sse interface

Used to simulate a request, batch return the result, and then end the SseEmitter;

stream-sse1 interface & sendMsg interface

Used to simulate a request, batch return results, cache SseEmitter, and subsequently can be sent through the sendMsg interface, notify the server to continue to return results;

Client-side functionality development

The Android client depends on OkHttp:

implementation '.okhttp3:okhttp:4.9.1'
implementation(".okhttp3:okhttp-sse:4.9.1")

Layout file: activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="/apk/res/android"
    xmlns:app="/apk/res-auto"
    xmlns:tools="/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_above="@id/btn"
        android:layout_centerHorizontal="true"
        android:text="--"
        android:lines="15"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"/>

    <Button
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:id="@+id/btn"
        android:text="Test Common Interface"
        android:layout_centerInParent="true"/>

    <Button
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:id="@+id/btn1"
        android:layout_below="@id/btn"
        android:text="ssegrout"
        android:layout_centerInParent="true"/>

    <Button
        android:layout_width="200dp"
        android:layout_height="50dp"
        android:id="@+id/btn2"
        android:layout_below="@id/btn1"
        android:text="ssegrout,carry-over parameter"
        android:layout_centerInParent="true"/>
</RelativeLayout>

package ;

import ;
import ;
import ;
import ;
import ;

import ;

import ;
import ;
import ;
import ;
import ;
import ;

public class MainActivity extends Activity {
    Button btn;
    Button btn1;
    Button btn2;
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        (savedInstanceState);
        setContentView(.activity_main);
        btn = findViewById();
        btn1 = findViewById(.btn1);
        btn2 = findViewById(.btn2);
        tv = findViewById();

        (new () {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        testDate();
                    }
                }).start();
            }
        });
        (new () {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        sse();
                    }
                }).start();
            }
        });
        (new () {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        sseWithParams();
                    }
                }).start();
            }
        });
    }

    private void testDate(){
        OkHttpClient client = new ()
                .connectTimeout(10, ) // Timeout for establishing a connection
                .readTimeout(10, ) // Timeout for reading data after establishing a connection
                .build();
        Request request = new ()
                .url("http://192.168.43.102:58888/common/getCurDate")
                .build();
         call = (request);
        try {
            Response response = (); // synchronization method
            if (()) {
                String responseBody = ().string(); // Getting the response body
                (responseBody);
                (responseBody);
            }
        } catch (Exception e) {
            ();
        }
    }

    void sse(){
        Request request = new ()
                .url("http://192.168.43.102:58888/sse/stream-sse")
                .addHeader("Authorization", "Bearer ")
                .addHeader("Accept", "text/event-stream")
                .build();

        OkHttpClient okHttpClient = new ()
                .connectTimeout(10, ) // Timeout for establishing a connection
                .readTimeout(10, ) // Timeout for reading data after establishing a connection
                .build();

        RealEventSource realEventSource = new RealEventSource(request, new EventSourceListener() {
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {
                (data); // Requested data
                String text = ().toString();
                (data+"\n"+text);
                if ("finish".equals(type)) { // Message Type,add incremental,finish close,error incorrect,interrupted disruptions

                }
            }
        });
        (okHttpClient);
    }

    void sseWithParams(){
        Request request = new ()
                .url("http://192.168.43.102:58888/sse/stream-sse1?uid=1")
                .addHeader("Authorization", "Bearer ")
                .addHeader("Accept", "text/event-stream")
                .build();

        OkHttpClient okHttpClient = new ()
                .connectTimeout(10, ) // Timeout for establishing a connection
                .readTimeout(10, ) // Timeout for reading data after establishing a connection
                .build();

        RealEventSource realEventSource = new RealEventSource(request, new EventSourceListener() {
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {
                (data); // Requested data
                String text = ().toString();
                (data+"\n"+text);
            }
        });
        (okHttpClient);
    }
}

Effectiveness Test

Calling the stream-sse interface

The server returned the results in batches:

Calling the stream-sse1 interface

The server returned the results in batches:

Call the sendMsg interface via h5 and the server side continues to return the result: