투케이2K

184. (TWOK/UTIL) [Android/Java] [기능 보완] Tcp Ip Socket 소켓 통신 클라이언트 모듈 - 데이터 송수신 중 서버 소켓 종료 대응 본문

투케이2K 유틸파일

184. (TWOK/UTIL) [Android/Java] [기능 보완] Tcp Ip Socket 소켓 통신 클라이언트 모듈 - 데이터 송수신 중 서버 소켓 종료 대응

투케이2K 2026. 3. 30. 19:12
728x90
반응형

[설 명]

프로그램 : Mobile / Android

설 명 : [Android/Java] [기능 보완] Tcp Ip Socket 소켓 통신 클라이언트 모듈 - 데이터 송수신 중 서버 소켓 종료 대응

 

[소스 코드]

 

-----------------------------------------------------------------------------------------
[사전 설명 및 설정 사항]
-----------------------------------------------------------------------------------------

- 개발 환경 : Mobile / Android


- 개발 기술 : Tcp Ip / Socket / Client


- 사전) 👉 TCP/IP 소켓 통신 간략 설명 : 

  >> TCP/IP 소켓 통신은 네트워크에서 프로그램 간에 데이터를 주고받기 위한 기본적인 통신 방식입니다.

    - TCP (Transmission Control Protocol) → 연결 기반, 신뢰성 보장, 순서 보장
    - IP (Internet Protocol) → 패킷을 목적지로 전달하는 역할

  >> 소켓은 쉽게 말하면 네트워크 통신을 하기 위한 문 (door) 입니다. (소켓 = 네트워크에서 데이터를 주고받기 위한 통신 채널)

  >> TCP 소켓 통신의 특징 : 

    - 연결 기반(Connection-oriented) → Handshake 필요
    - 신뢰성 보장 → 패킷 유실 시 재전송
    - 순서 보장 → 순서 틀리면 자동으로 재정렬
    - 스트림 기반 → 연속된 데이터 흐름

  >> TCP 소켓 통신의 구성 요소 : 

    - IP 주소 : 상대방이 있는 위치
    - Port 번호 : 상대방 프로그램의 번호
    - Socket : IP + Port로 연결된 통신 객체
    - Server Socket : 서버에서 클라이언트의 접속을 기다리는 소켓

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[소스 코드]
-----------------------------------------------------------------------------------------

package com.example.javaproject.C_Module;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;

import com.example.javaproject.S_FinalData;
import com.example.javaproject.S_Log;

import org.json.JSONObject;

import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.HashMap;

public class C_TcpIp_Client_Socket_Module {


    /**
     * // --------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // --------------------------------------------------------------------------------------
     * 1. [설명] : TCP/IP 통신 수행 클래스
     * // --------------------------------------------------------------------------------------
     * */





    /**
     * // --------------------------------------------------------------------------------------
     * TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // --------------------------------------------------------------------------------------
     * // [SEARCH FAST] : [기기 등록 요청 실시 >> 응답 결과 확인]
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     */






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

        // -------------------------------------------------
        // TODO [1]. 소켓 통신을 하기 위한 IP , PORT 선언
        // -------------------------------------------------
        String ip = "192.168.0.123";
        int port = 5000;


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


        // --------------------------------------
        // TODO [3]. [소켓 연결 수행]
        // --------------------------------------
        if(c_socket_module.connectHost(ip, port) == true) { // [소켓 연결 상태 확인]


            // --------------------------------------
            // TODO [4]. [기기 등록 요청 수행]
            // --------------------------------------
            HashMap<String, Object> deviceRegMap = c_socket_module.request_Device_Reg();

            S_Log._W_("소켓 통신 :: 디바이스 등록 요청 수행 결과 확인", new String[]{
                    "SUCCESS :: " + String.valueOf(deviceRegMap.get(c_socket_module.RETURN_SUCCESS_FLAG))
            });


            // --------------------------------------
            // TODO [5]. [소켓 연결 종료]
            // --------------------------------------
            c_socket_module.closeSocket();


            // --------------------------------------
            // TODO [6]. [응답 플래그에 맞게 팝업 표시 및 기기 등록 상태 조회 실시]
            // --------------------------------------

            if ((boolean)deviceRegMap.get(c_socket_module.RETURN_SUCCESS_FLAG) == true){

            }
            else {

              // [등록 실패]
              String errorMessage = "\n소켓 데이터 송수신에 실패 했습니다 ..\n\n["+String.valueOf(deviceRegMap.get(c_socket_module.RETURN_ERROR_MESSAGE))+"]";

            }

        }
        else {
            S_Log._E_("소켓 연결 실패", null);
        }

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





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

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

    private Socket mSocket; // [소켓]

    private OutputStream mOutputStream; // [스트림]
    private PrintWriter mPrintWriter; // [스트림]
    private InputStream mInputStream; // [스트림]
    private DataInputStream mDataDataInputStream; // [스트림]

    public static final String RETURN_SUCCESS_FLAG = "RETURN_SUCCESS_FLAG"; // [리턴 데이터 반환 키 값]
    public static final String RETURN_ERROR_MESSAGE = "RETURN_ERROR_MESSAGE"; // [리턴 데이터 반환 키 값]




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




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





    // ------------------------------------------------------------------------------------------
    // TODO [소켓 생성 실시]
    // ------------------------------------------------------------------------------------------
    private synchronized Socket createSocket(String host, int port) {
        S_Log._D_(ACTIVITY_NAME + " :: createSocket :: 소켓 생성 수행", new String[]{ "HOST :: " + String.valueOf(host), "PORT :: " + String.valueOf(port) });

        ConnectivityManager connectivity = (ConnectivityManager) mMainCtx.getSystemService(Context.CONNECTIVITY_SERVICE);
        Socket sock = null;

        try{
            if (connectivity != null) {

                for (Network network : connectivity.getAllNetworks()) { // TODO [연결된 네트워크 확인]

                    NetworkInfo networkInfo = connectivity.getNetworkInfo(network);

                    if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { // TODO [와이파이 연결 상태 체크]

                        if (networkInfo.isConnected()) {
                            S_Log._W_(ACTIVITY_NAME + " :: createSocket :: 소켓 생성 성공", new String[]{ "HOST :: " + String.valueOf(host), "PORT :: " + String.valueOf(port) });

                            // ✅ [소켓 지정 실시]
                            sock =  network.getSocketFactory().createSocket(host, port);
                        }

                    }
                }
            }
            else {
                S_Log._E_(ACTIVITY_NAME + " :: createSocket :: 소켓 생성 에러", new String[]{ "Error :: ConnectivityManager is null" });
            }
        } catch (Exception e) {
            S_Log._E_(ACTIVITY_NAME + " :: createSocket :: 소켓 생성 에러", new String[]{ "Exception :: " + String.valueOf(e.getMessage()) });
        }
        return sock;
    }





    // ------------------------------------------------------------------------------------------
    // TODO [디바이스 호스트 연결 실시]
    // ------------------------------------------------------------------------------------------
    public synchronized boolean connectHost(String host, int port) {

        // [연결 상태 체크 변수]
        boolean isConnection = false;


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

            // [소켓 연결 수행]
            if(this.mSocket != null) { // TODO [소켓이 널이 아닌 경우]
                S_Log._E_(ACTIVITY_NAME + " :: connectHost :: 호스트 연결 실패", new String[]{ "설 명 :: 소켓 연결 된 상태 (Socket Not Null)" });
            }
            else { // TODO [소켓이 널인 경우]
                S_Log._D_(ACTIVITY_NAME + " :: connectHost :: 호스트 연결 수행", new String[]{ "HOST :: " + String.valueOf(host), "PORT :: " + String.valueOf(port) });

                this.mSocket = createSocket(host, port); // [소켓 생성]

                // -----------------------------------------
                // TODO [소켓 연결 끊김 버그로 설정 해제]

                // -----------------------------------------
                /**
                 * W/System.err: java.net.SocketException: Software caused connection abort
                 *     W/System.err:     at java.net.SocketInputStream.socketRead0(Native Method)
                 *     W/System.err:     at java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
                 *     W/System.err:     at java.net.SocketInputStream.read(SocketInputStream.java:173)
                 *     W/System.err:     at java.net.SocketInputStream.read(SocketInputStream.java:143)
                 *     W/System.err:     at java.net.SocketInputStream.read(SocketInputStream.java:129)
                 *     W/System.err:     at maincp.android.uis.MainCP.Main_SensorGuardView.SensorGuardClientSocketModule.request_Device_Reg(SensorGuardClientSocketModule.java:449)
                 *     W/System.err:     at maincp.android.uis.MainCP.Main_SensorGuardView.SensorGuardRegHubViewActivity$10$1.run(SensorGuardRegHubViewActivity.java:1352)
                 * */

                //this.mSocket.setTcpNoDelay(true);
                //this.mSocket.setSoTimeout(0);
                // -----------------------------------------

                isConnection = this.mSocket.isConnected(); // [소켓 연결 상태 확인]

                if (isConnection == true){

                    S_Log._W_(ACTIVITY_NAME + " :: connectHost :: 호스트 연결 성공", new String[]{ "설 명 :: 데이터 송수신을 위한 스트림 설정" });

                    // ✅ [데이터 송수신 설정]
                    this.mOutputStream = this.mSocket.getOutputStream(); // [읽기]
                    this.mPrintWriter = new PrintWriter(mOutputStream);

                    this.mInputStream = this.mSocket.getInputStream(); // [쓰기]
                    this.mDataDataInputStream = new DataInputStream(mInputStream);

                }
                else {
                    S_Log._E_(ACTIVITY_NAME + " :: connectHost :: 호스트 연결 실패", new String[]{ "설 명 :: mSocket.isConnected false" });
                }
            }
        }
        catch (Exception e){
            S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);

            // [Exception 발생 시 소켓 연결 종료]
            closeSocket();
        }


        // [리턴 반환 수행]
        return isConnection;
    }





    // ------------------------------------------------------------------------------------------
    // TODO [소켓 연결 종료]
    // ------------------------------------------------------------------------------------------
    public synchronized void closeSocket() {
        
        try {

            // [생성된 소켓이 있는 경우 종료 및 스트림 닫기]
            if(this.mPrintWriter != null) {
              try {
                this.mPrintWriter.close();
                this.mPrintWriter = null;
              }
              catch(Exception err){ }              

              S_Log.e("KWON_TWOK", "closeSocket >> mPrintWriter.close()");
            }

            if(this.mDataDataInputStream != null) {
              try {
                this.mDataDataInputStream.close();
                this.mDataDataInputStream = null;
              }
              catch(Exception err){ }              

              S_Log.e("KWON_TWOK", "closeSocket >> mDataDataInputStream.close()");
            }

            if(this.mOutputStream != null) {
              try {
                this.mOutputStream.close();
                this.mOutputStream = null;
              }
              catch(Exception err){ }              

              S_Log.e("KWON_TWOK", "closeSocket >> mOutputStream.close()");
            }

            if(this.mInputStream != null) {
              try {
                this.mInputStream.close();
                this.mInputStream = null;
              }
              catch(Exception err){ }              

              S_Log.e("KWON_TWOK", "closeSocket >> mInputStream.close()");
            }

            if(this.mSocket != null) {
              try {
                this.mSocket.close();
                boolean isClose = this.mSocket.isClosed();
                this.mSocket = null;

                S_Log.e("KWON_TWOK", "closeSocket >> mSocket.close() : " + String.valueOf(isClose));
              }
              catch(Exception err){
                S_Log.e("KWON_TWOK", "closeSocket >> mSocket.close() : Exception : " + String.valueOf(err.getMessage()));
              }
            }
        }
        catch (Exception e) {
            S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
        }

        S_Log._E_(ACTIVITY_NAME + " :: closeSocket :: 소켓 연결 종료 수행", null);

    }





    // ------------------------------------------------------------------------------------------
    // TODO [SEARCH FAST] : [기기 등록 요청 실시 >> 응답 결과 확인]
    // ------------------------------------------------------------------------------------------
    public synchronized HashMap request_Device_Reg() {
        S_Log._D_(ACTIVITY_NAME + " :: request_Device_Reg :: 기기 등록 요청 수행", null);

        /**
         * // ----------------------------------------
         * TODO [REQUEST] : [APP >> DEVICE]
         * // ----------------------------------------
         *
         * {
         *   "reg_name" : "TOWK"
         * }
         *
         * // ----------------------------------------
         * */


        /**
         * // ----------------------------------------
         * TODO [RESPONSE] : [DEVICE >> APP]
         * // ----------------------------------------
         *
         * {
         *   "result" : true
         * }
         *
         * // ----------------------------------------
         * */



        // -----------------------------------------------
        // [리턴 변수 선언]
        // -----------------------------------------------
        HashMap<String, Object> returnData = new HashMap<>();



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

            // -----------------------------------------
            // TODO [1]. [APP TO DEVICE : 디바이스 등록 요청 실시]
            // -----------------------------------------
            JSONObject reqJson = new JSONObject();
            reqJson.put("reg_name", "TWOK");

            String requestData = reqJson.toString();

            S_Log._D_(ACTIVITY_NAME + " :: request_Device_Reg :: [요청] :: [APP] >> [DEVICE] :: 기기 등록 요청 수행", new String[]{ "REQUEST :: " + requestData.toString() });

            mPrintWriter.write(requestData); // ✅ [데이터 전송]
            mPrintWriter.flush();
            // -----------------------------------------



            // -----------------------------------------
            // TODO [2]. [DEVICE TO APP : 디바이스 정보 응답 확인 실시]
            // -----------------------------------------
            int len = 0;
            byte[] buffer = new byte[6144]; // 응답 메시지 수신 버퍼

            if(mInputStream != null){ // ✅ 응답 메시지 확인
              len = mInputStream.read(buffer);
            }

            byte[] subBuffer = new byte[len];
            System.arraycopy(buffer, 0, subBuffer,0, len);


            // [byte to string 변환]
            String jsonString = new String(subBuffer);
            jsonString = jsonString.replaceAll(" ", "");

            S_Log._D_(ACTIVITY_NAME + " :: request_Device_Reg :: [응답] :: [DEVICE] >> [APP] :: 기기 등록 응답 확인", new String[]{ "RESPONSE :: " + jsonString.toString() });

            // ✅ [리턴 데이터 삽입]
            returnData.put(RETURN_SUCCESS_FLAG, true);
            returnData.put(RETURN_ERROR_MESSAGE, "");
            // -----------------------------------------

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

            // ---------------------------------------------------
            // TODO [Exception] : [예외 허용 처리]
            // ---------------------------------------------------
            // TODO 간헐적 디바이스 장비가 Response 응답을 주지 않고 서버 종료 시킨 경우 (앱에서 넘긴 데이터는 다받은 상태)
            // ---------------------------------------------------
            if(e instanceof SocketException && String.valueOf(e.getMessage()).contains("Software caused connection abort")){
              S_Log._W_(ACTIVITY_NAME + " :: request_Device_Reg :: 간헐적 디바이스 장비가 Response 응답을 주지 않고 서버 종료 시킨 경우 Exception 예외 허용 처리", null);

              // ✅ [리턴 데이터 삽입]
              returnData.put(RETURN_SUCCESS_FLAG, true);
              returnData.put(RETURN_ERROR_MESSAGE, "");

            }
            else {

              // ❌ [에러 발생 데이터 삽입]
              returnData.put(RETURN_SUCCESS_FLAG, false);
              returnData.put(RETURN_ERROR_MESSAGE, String.valueOf(e.getMessage()));

            }

        }


        // [리턴 반환 실시]
        return returnData;
    }


} // TODO [클래스 종료]
 
728x90
반응형
Comments