투케이2K

135. (TWOK/UTIL) [Android/Java] C_WebSocket_Okhttp_Client_Module : OKHttp 사용해 WebSocket 웹소켓 통신 클라이언트 본문

투케이2K 유틸파일

135. (TWOK/UTIL) [Android/Java] C_WebSocket_Okhttp_Client_Module : OKHttp 사용해 WebSocket 웹소켓 통신 클라이언트

투케이2K 2024. 10. 3. 08:15

[설 명]

프로그램 : Android / Java

설 명 : C_WebSocket_Okhttp_Client_Module : OKHttp 사용해 WebSocket 웹소켓 통신 클라이언트

 

[소스 코드]

 

package com.example.javaproject.C_Module;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.javaproject.C_StateCheck;
import com.example.javaproject.C_Util;
import com.example.javaproject.S_FileManager;
import com.example.javaproject.S_FinalData;
import com.example.javaproject.S_Log;

import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.ByteString;

public class C_WebSocket_Okhttp_Client_Module {



    /**
     * // --------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // --------------------------------------------------------------------------------------
     * 1. [설명] : 웹 소켓 통신 수행 클라이언트 모듈
     * // --------------------------------------------------------------------------------------
     * 2. 필요 퍼미션 :
     *
     * <uses-permission android:name="android.permission.INTERNET"/>
     * <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     * <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     * <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     * <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     * <uses-feature android:name="android.hardware.location.network"/>
     * // --------------------------------------------------------------------------------------
     * 3. Build.gradle 라이브러리 추가 :
     *
     * implementation("com.squareup.okhttp3:okhttp:4.9.0")
     * implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
     * // --------------------------------------------------------------------------------------
     * 4. 테스트 웹소켓 참고 사이트 :
     *
     * https://www.toolfk.com/ko/tools/online-runwebsocket.html
     * // --------------------------------------------------------------------------------------
     * */





    /**
     * // --------------------------------------------------------------------------------------
     * TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     */





    // ------------------------------------------------------------------------------------------
    // TODO [사용 방법]
    // ------------------------------------------------------------------------------------------
    /*
    try {

        // -------------------------------------------------
        // TODO [1]. 소켓 통신을 하기 위한 HOST 선언 : [ws = http / wss = https]
        // -------------------------------------------------
        //String host = "ws://192.168.0.15:8001"; // [로컬 서버 테스트]
        String host = "wss://javascript.info/article/websocket/demo/hello"; // [공개 테스트 웹소켓 사이트]


        // -------------------------------------------------
        // TODO [2]. [소켓 모듈 클래스 인스턴스 초기화]
        // -------------------------------------------------
        C_WebSocket_Okhttp_Client_Module c_socket_module = C_WebSocket_Okhttp_Client_Module.getInstance();
        c_socket_module.setContext(A_Test.this);


        // -------------------------------------------------
        // TODO [3]. [소켓 연결 수행]
        // -------------------------------------------------

        // [URL 주소 선언]
        String url = "wss://javascript.info/article/websocket/demo/hello";

        // [파라미터 생성]
        Map<String, Object> headers = new HashMap<>();
        //headers.put("x-api-key", "sample_key"); // [key 인증시 사용]

        // [http 요청 수행]
        c_socket_module.startWebsocket("WebSocket 통신 요청", url, headers) // [http 요청]
                .subscribeOn(AndroidSchedulers.mainThread()) // [Observable (생성자) 로직을 IO 스레드에서 실행 : 백그라운드]
                .observeOn(Schedulers.io()) // [Observer (관찰자) 로직을 메인 스레드에서 실행]
                .subscribe(new Observer<String>() { // [Observable.create 타입 지정]
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                    }

                    @Override
                    public void onNext(@NonNull String value) {

                        if (C_Util.stringNotNull(value) == true){ // TODO [널 값이 아닌 경우]
                            S_Log._W_(ACTIVITY_NAME + " :: onNext :: 실시간 메시지 수신 확인", new String[]{String.valueOf(value)});

                            if (String.valueOf(value).equals(C_WebSocket_Okhttp_Client_Module.WEB_SOCKET_OPEN_CONNECT_SUCCESS) == true){

                                // -------------------------------------------------
                                // TODO [웹 소켓 열기 및 연결 완료]
                                // -------------------------------------------------
                                c_socket_module.request_Hello(); // [hello 메시지 전송]
                            }
                            else {

                                // -------------------------------------------------
                                // TODO [실시간 메시지 수신 처리]
                                // -------------------------------------------------
                                c_socket_module.closeSocket();

                            }

                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        S_Log._E_(ACTIVITY_NAME + " :: onError :: " + String.valueOf(e.getMessage()), null);
                    }

                    @Override
                    public void onComplete() {
                    }
                });

    }
    catch (Exception e) {
        S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
    }
    */
    // ------------------------------------------------------------------------------------------





    // ------------------------------------------------------------------------------------------
    // TODO [전역 변수 선언]
    // ------------------------------------------------------------------------------------------
    private String ACTIVITY_NAME = "C_WebSocket_Okhttp_Client_Module";

    private Context mMainCtx; // [컨텍스트]

    private static final int TIME_OUT_SECOND = 30; // [타임 아웃 및 핑 체크 시간 정의]

    WebSocket webSockets = null; // [웹 소켓 객체]

    ObservableEmitter<String> webSocketSubscriber = null; // [구독 메시지 반환 처리]

    final int STATUS_CODE = 1000; // [웹 소켓 상태 코드]

    public static final String WEB_SOCKET_OPEN_CONNECT_SUCCESS = "WEB_SOCKET_OPEN_CONNECT_SUCCESS";
    public static final String WEB_SOCKET_CLOSE_MESSAGE_EXIT = "WEB_SOCKET_CLOSE_MESSAGE_EXIT";





    // ------------------------------------------------------------------------------------------
    // TODO [인스턴스 생성]
    // ------------------------------------------------------------------------------------------
    public static C_WebSocket_Okhttp_Client_Module getInstance() { return C_WebSocket_Okhttp_Client_Module.LazyHolder.INSTANCE; }
    private static class LazyHolder {
        private static final C_WebSocket_Okhttp_Client_Module INSTANCE = new C_WebSocket_Okhttp_Client_Module();
    }





    // ------------------------------------------------------------------------------------------
    // TODO [콘텍스트 지정]
    // ------------------------------------------------------------------------------------------
    public void setContext(Context ctx) {
        mMainCtx = ctx;
    }





    // ------------------------------------------------------------------------------------------
    // TODO [소켓 생성 실시]
    // -----------------------------------------------------------------------------------------
    // TODO [호출 방법 소스 코드]
    // -----------------------------------------------------------------------------------------
    /*
    try {

        // [URL 주소 선언]
        String url = "wss://javascript.info/article/websocket/demo/hello";

        // [파라미터 생성]
        Map<String, Object> headers = new HashMap<>();
        //headers.put("x-api-key", "sample_key"); // [key 인증시 사용]

        // [http 요청 수행]
        c_socket.startWebsocket("WebSocket 통신 요청", url, headers) // [http 요청]
                .subscribeOn(AndroidSchedulers.mainThread()) // [Observable (생성자) 로직을 IO 스레드에서 실행 : 백그라운드]
                .observeOn(Schedulers.io()) // [Observer (관찰자) 로직을 메인 스레드에서 실행]
                .subscribe(new Observer<String>() { // [Observable.create 타입 지정]
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                    }

                    @Override
                    public void onNext(@NonNull String value) {

                    }

                    @Override
                    public void onError(@NonNull Throwable e) {

                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }
    catch (Exception e){
        S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
    }
    // */
    // -----------------------------------------------------------------------------------------
    // TODO [샘플 로그]
    // -----------------------------------------------------------------------------------------
    /*
    W  ===================================================================
    [LOG :: CLASS PLACE :: com.example.javaproject.C_Module.C_WebSocket_Okhttp_Client_Module$2.onOpen(C_WebSocket_Okhttp_Client_Module.java:345)]
    ----------------------------------------------------
    [LOG :: NOW TIME :: 2024-09-05 17:20:11 목요일]
    ----------------------------------------------------
    [LOG :: DESCRIPTION :: C_WebSocket_Okhttp_Client_Module : webSocketListener : onOpen]
    ----------------------------------------------------
    [LOG :: Response{protocol=http/1.1, code=101, message=Switching Protocols, url=https://javascript.info/article/websocket/demo/hello}]
    W  ===================================================================
    */
    // -----------------------------------------------------------------------------------------
    public Observable<String> startWebsocket(String tag, String url, Map header){

        // [로직 처리 실시]
        return Observable.create(subscriber -> {

            try {

                if (mMainCtx != null){

                    // ------------------------------------------------------
                    // TODO [subscriber 전역 변수 지정]
                    // ------------------------------------------------------
                    this.webSocketSubscriber = subscriber;
                    // ------------------------------------------------------


                    // ------------------------------------------------------
                    // TODO [사전 방어 로직 : url 데이터 값 체크 실시]
                    // ------------------------------------------------------
                    if (C_Util.stringNotNull(url) == false){
                        try { this.webSocketSubscriber.onError(new Throwable("[FAIL] : [startWebsocket] : Input Url Is Null")); this.webSocketSubscriber.onComplete(); } catch (Exception ex){ ex.printStackTrace(); }
                        return;
                    }
                    // ------------------------------------------------------


                    // ------------------------------------------------------
                    // TODO [사전 방어 로직 : url 데이터 값 체크 실시]
                    // ------------------------------------------------------
                    if (String.valueOf(url).startsWith("ws://") == true || String.valueOf(url).startsWith("wss://") == true
                            || String.valueOf(url).startsWith("http://") == true || String.valueOf(url).startsWith("https://") == true){
                    }
                    else {
                        try { this.webSocketSubscriber.onError(new Throwable("[FAIL] : [startWebsocket] : Input Url Type Error")); this.webSocketSubscriber.onComplete(); } catch (Exception ex){ ex.printStackTrace(); }
                        return;
                    }
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [URL 변수 선언 실시]
                    // ------------------------------------------------------
                    String urlData = String.valueOf(url);
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [HTTP 통신 전문 로그 기록]
                    // ------------------------------------------------------

                    S_FileManager.appHttpLogSave(mMainCtx, "\n\n\n\n\n\n\n\n\n");
                    S_FileManager.appHttpLogSave(mMainCtx, "TAG :: " + String.valueOf(tag));
                    HttpLoggingInterceptor httpLogger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                        @Override
                        public void log(String message) {
                            if (mMainCtx != null){
                                S_FileManager.appHttpLogSave(mMainCtx, message);
                            }
                        }
                    });
                    httpLogger.setLevel(HttpLoggingInterceptor.Level.BODY);
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [OK HTTP 객체 선언 실시]
                    // ------------------------------------------------------
                    //OkHttpClient client = new OkHttpClient();
                    OkHttpClient client = new OkHttpClient.Builder()
                            .connectTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS) // [커넥션 제한 시간]
                            .readTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS)
                            .writeTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS)
                            .pingInterval(TIME_OUT_SECOND, TimeUnit.SECONDS) // [핑 요청 시간]
                            .addInterceptor(httpLogger) // [Http 통신 로그]
                            .retryOnConnectionFailure(false)
                            .build();

                    Request.Builder requestBuilder = new Request.Builder();
                    requestBuilder.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8;"); //TODO [헤더]
                    requestBuilder.addHeader("Cache-Control", "no-cache"); //TODO [헤더]

                    // [로그 출력 헤더 삽입]
                    Map<String, String> requestHeader = new HashMap<>();
                    requestHeader.put("Content-Type", "application/x-www-form-urlencoded; charset=utf-8;");
                    requestHeader.put("Cache-Control", "no-cache");

                    if (C_Util.mapNotNull(header) == true){
                        Set set = header.keySet();
                        Iterator iterator = set.iterator();
                        while(iterator.hasNext()){
                            String key = (String) iterator.next();
                            requestHeader.put(key, String.valueOf(header.get(key)));
                            requestBuilder.addHeader(key, String.valueOf(header.get(key))); //TODO [헤더 추가]
                        }
                    }

                    Request request = requestBuilder.url(String.valueOf(urlData)).build(); //TODO [requestBuilder 추가]
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [웹 소켓 통신 이벤트 리스너 지정]
                    // ------------------------------------------------------
                    WebSocketListener listener = new WebSocketListener() {
                        @Override
                        public void onOpen(WebSocket webSocket, Response response) {
                            S_Log._W_(ACTIVITY_NAME + " : webSocketListener : onOpen", new String[]{String.valueOf(response)});

                            // TODO [구독 된 콜백 으로 메시지 반환]
                            try {
                                if (webSocketSubscriber != null && webSocketSubscriber.isDisposed() == false){
                                    webSocketSubscriber.onNext(String.valueOf(WEB_SOCKET_OPEN_CONNECT_SUCCESS));
                                }
                            }
                            catch (Exception es){
                                es.printStackTrace();
                            }
                        }

                        @Override
                        public void onMessage(WebSocket webSocket, String text) {
                            S_Log._W_(ACTIVITY_NAME + " : webSocketListener : onMessage", new String[]{String.valueOf(text)});

                            // TODO [구독 된 콜백 으로 메시지 반환]
                            try {
                                if (webSocketSubscriber != null && webSocketSubscriber.isDisposed() == false){
                                    webSocketSubscriber.onNext(String.valueOf(text));
                                }
                            }
                            catch (Exception es){
                                es.printStackTrace();
                            }
                        }

                        @Override
                        public void onMessage(WebSocket webSocket, ByteString byteString) {
                        }

                        @Override
                        public void onClosing(WebSocket webSocket, int code, String reason) {
                            S_Log._E_(ACTIVITY_NAME + " : webSocketListener : onClosing", new String[]{"Code : " + String.valueOf(code), "Reason : " + String.valueOf(reason)});

                            // -----------------------------------------------
                            // TODO [웹 소켓 닫기 수행 수행]
                            // -----------------------------------------------
                            try { webSocket.close(code, reason); } catch (Exception es){ es.printStackTrace(); }
                            // -----------------------------------------------


                            // ------------------------------------------------------
                            // TODO [리턴 데이터 반환]
                            // ------------------------------------------------------
                            try {
                                if (webSocketSubscriber != null && webSocketSubscriber.isDisposed() == false){
                                    webSocketSubscriber.onNext("");
                                    webSocketSubscriber.onComplete();
                                }
                            } catch (Exception ex){
                                ex.printStackTrace();
                            }
                            // ------------------------------------------------------

                        }

                        @Override
                        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                            S_Log._E_(ACTIVITY_NAME + " : webSocketListener : onFailure", new String[]{"Error : " + String.valueOf(t.getMessage()), "Response : " + String.valueOf(response)});

                            // ------------------------------------------------------
                            // TODO [리턴 데이터 반환]
                            // ------------------------------------------------------
                            try {
                                if (webSocketSubscriber != null && webSocketSubscriber.isDisposed() == false){
                                    webSocketSubscriber.onError(new Throwable("[webSocketListener] : [onFailure] : " + String.valueOf(t.getMessage())));
                                    webSocketSubscriber.onComplete();
                                }
                            } catch (Exception ex){
                                ex.printStackTrace();
                            }
                            // ------------------------------------------------------
                        }
                    };
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [요청 로그 출력 실시]
                    // ------------------------------------------------------
                    S_Log._F_(mMainCtx, ACTIVITY_NAME + " :: startWebsocket :: WebSocket [연결] 실시", new String[] {
                            "TAG :: " + String.valueOf(tag),
                            "TYPE :: " + "GET >> REQUEST",
                            "URL :: " + String.valueOf(urlData),
                            "HEADER :: " + String.valueOf(requestHeader)
                    });
                    // ------------------------------------------------------



                    // ------------------------------------------------------
                    // TODO [웹 소켓 연결 수행 실시]
                    // ------------------------------------------------------
                    webSockets = client.newWebSocket(request, listener);
                    // ------------------------------------------------------

                }
                else {
                    try { subscriber.onError(new Throwable("[FAIL] : [startWebsocket] : mMainCtx Is Null")); subscriber.onComplete(); webSockets = null; } catch (Exception ex){ ex.printStackTrace(); }
                }

            } catch (final Exception e){
                // ------------------------------------------------------
                // [로그 출력]
                // ------------------------------------------------------
                S_Log._printStackTrace_(mMainCtx, S_FinalData.LOG_BUG_STATE, new String[] {
                        ACTIVITY_NAME + " :: startWebsocket :: Http WebSocket [EXCEPTION] 확인",
                        "TAG :: " + String.valueOf(tag),
                        "TYPE :: " + "GET >> EXCEPTION",
                        "EXCEPTION :: " + String.valueOf(e.getMessage())
                }, e);
                // ------------------------------------------------------


                // ------------------------------------------------------
                // TODO [리턴 데이터 반환]
                // ------------------------------------------------------

                try {

                    if (subscriber != null && subscriber.isDisposed() == false){
                        subscriber.onError(new Throwable("[EXCEPTION] : [startWebsocket] : " + String.valueOf(e.getMessage())));
                        subscriber.onComplete();
                    }

                    webSockets = null;

                } catch (Exception ex){
                    ex.printStackTrace();
                }
                // ------------------------------------------------------
            }

        });
    }





    // ------------------------------------------------------------------------------------------
    // TODO [소켓 연결 종료]
    // ------------------------------------------------------------------------------------------
    public synchronized void closeSocket() {
        S_Log._E_(ACTIVITY_NAME + " :: 웹소켓 연결 종료 수행", null);


        // [로직 처리 수행]
        try {

            // [구독 닫기 처리 수행]
            if(webSocketSubscriber != null && webSocketSubscriber.isDisposed() == false) {
                webSocketSubscriber.onNext("");
                webSocketSubscriber.onComplete();
                webSocketSubscriber = null;
            }

            // [생성된 소켓이 있는 경우 종료 및 스트림 닫기] : [이벤트 리스너 onFailure >> Socket is closed]
            if (webSockets != null){
                webSockets.close(STATUS_CODE, String.valueOf(WEB_SOCKET_CLOSE_MESSAGE_EXIT));
                webSockets.cancel();
                webSockets = null;
            }

        }
        catch (Exception e) {
            S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
        }

    }





    // ------------------------------------------------------------------------------------------
    // TODO [실시간 소켓 메시지 전송 부분] : [hello]
    // ------------------------------------------------------------------------------------------
    public synchronized void request_Hello() {

        // -----------------------------------------------
        // [로직 처리 수행]
        // -----------------------------------------------
        try {

            String sendMessage = "hello";

            if (webSockets != null){

                webSockets.send(sendMessage);

                S_Log._W_(ACTIVITY_NAME + " : Send Message Emit : " + String.valueOf(sendMessage), null);

            }
            else {
                S_Log._E_(ACTIVITY_NAME + " : Send Message Error : mSocket Is Null", null);
            }

        }
        catch (Exception e){
            S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
        }

    }





    // ------------------------------------------------------------------------------------------
    // TODO [실시간 소켓 메시지 수신 이벤트 리스너 부분] : [주석 처리]
    // ------------------------------------------------------------------------------------------
    /*
    private WebSocketListener webSocketListener = new WebSocketListener() {
        @Override
        public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            try { webSockets = webSocket; } catch (Exception ez){ ez.printStackTrace(); }
            super.onClosed(webSocket, code, reason);
            S_Log._E_(ACTIVITY_NAME + " : webSocketListener : onFailure", new String[]{"Code : " + String.valueOf(code), "Reason : " + String.valueOf(reason)});
        }

        @Override
        public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            try { webSockets = webSocket; } catch (Exception ez){ ez.printStackTrace(); }
            super.onClosing(webSocket, code, reason);
            //S_Log._E_(ACTIVITY_NAME + " : webSocketListener : onFailure", new String[]{"Code : " + String.valueOf(code), "Reason : " + String.valueOf(reason)});
        }

        @Override
        public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {
            try { webSockets = webSocket; } catch (Exception ez){ ez.printStackTrace(); }
            super.onFailure(webSocket, t, response);
            S_Log._E_(ACTIVITY_NAME + " : webSocketListener : onFailure", new String[]{"Error : " + String.valueOf(t.getMessage()), "Response : " + String.valueOf(response)});
        }

        @Override
        public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
            super.onMessage(webSocket, text);
            S_Log._W_(ACTIVITY_NAME + " : webSocketListener : onMessage", new String[]{String.valueOf(text)});

            // -----------------------------------------------
            // TODO [로직 분기 처리 수행]
            // -----------------------------------------------
            if (String.valueOf(text).contains("hello client") == true){
                closeSocket(); // [소켓 닫기]
            }
            // -----------------------------------------------
        }

        @Override
        public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
            super.onMessage(webSocket, bytes);
        }

        @Override
        public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
            try { webSockets = webSocket; } catch (Exception ez){ ez.printStackTrace(); }
            super.onOpen(webSocket, response);
            S_Log._W_(ACTIVITY_NAME + " : webSocketListener : onOpen", new String[]{String.valueOf(response)});

            // -----------------------------------------------
            // TODO [메시지 전송 수행]
            // -----------------------------------------------
            request_Hello();
            // -----------------------------------------------
        }
    };
    // */


} // TODO [클래스 종료]

 

반응형
Comments