Notice
Recent Posts
Recent Comments
Link
투케이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 [클래스 종료]
반응형
'투케이2K 유틸파일' 카테고리의 다른 글
Comments