[アンドロイド]ソケットプログラム


ソケットプログラム


コンセント


HTTP通信プログラムは,サーバとのデータ交換に最もよく用いられるが,サーバとクライアント間の接続が継続できないため,リアルタイムサーバプッシュは実現できない.

前述したように,HTTP通信プログラムは,リアルタイムサーバプッシュを実施する必要がある場所では利用できない.これは,サーバとのデータ交換後,応答終了時に自動的に接続が切断されるためである.これを「HTTPの無状態」と呼ぶ.もちろん、クライアントは、必要に応じていつでも再接続してデータを受信することができます.ただし、リアルタイムサーバプッシュは、サーバ側でデータが発生した場合にクライアントにデータを送信する必要があり、クライアントは接続解除状態にあり、クライアントが再接続を要求しない限り転送できません.
リアルタイムサーバプッシュが必要な典型的な例は、チャットプログラムである.相手が送信したメッセージはサーバに送信し、その瞬間にクライアント(または)にリアルタイムで送信しなければならないが、HTTPは接続を継続できないため、サーバはクライアントにデータを送信できない.最終的には、チャットなどのリアルタイムサーバプッシュを実現するには、クライアントとサーバ間の接続を維持する必要があるため、ソケットプログラムが必要です.

ソケットプログラムは、クライアントがサーバに接続要求を発行し、接続が完了した後も実行を継続する構造であるため、クライアントまたはサーバでデータが発生するたびに、この接続を使用してリアルタイムでデータを転送することができる.

ソケットクラス


タスク#タスク#


HTTP通信と同様に,ソケットプログラムの作成にはINTERNETスーパータスクが必要である.
<uses-permission android:name="android.permission.INTERNET" />

カテゴリ


サーバとスロットの接続には、SocketクラスとInetSocketAddressクラスが使用されます.InteSocketAddressは、接続サーバ情報を表すクラスで、作成者にIPアドレスとポート番号を指定します.InetSocketAddressで表されるサーバの場合、接続要求はSocketクラスのconnect()関数呼び出しで完了し、connect()関数パラメータとしてInetSocketAddressオブジェクトとタイムアウト時間を指定できます.
// 소켓 객체 생성
socket = Socket()
// InetSocketAddress 생성자에 서버의 IP와 포트번호를 인자로 주고 객체 생성
val remoteAddr = InetSocketAddress(serverIp, serverPort)
// connect 메서드를 사용하고 InetSocketAddress 객체와 타임아웃 시간 지정
socket?.connect(remoteAddr, 10 * 1000)
Socketクラスに接続されているサーバにデータを送信するには、IOオブジェクトを作成する必要があります.
// getOutputStream() 메서드를 사용하여 소켓의 output stream을 획득하고
// 그것을 BufferedOutputStream에 연결
bout = BufferedOutputStream(socket?.getOutputStream())
// getInputStream() 메서드를 사용하여 소켓의 input stream을 획득하고
// 그것을 BufferedInputStream에 연결
bin = BufferedInputStream(socket?.getInputStream())

// 데이터를 송신하기 위해 BufferedOutputStream의 write 메서드를 사용
// 매개변수로 전송하고자 하는 데이터 지정
bout.write((msg.obj as String).toByteArray())
bout.flush()

// 데이터를 수신하기 위해 BufferedInputStream의 read 메서드를 사용
var message: String? = null
val size = bin.read(buffer)
if(size > 0) {
    message = String(buffer, 0, size, charset("utf-8"))
}
ソケットオブジェクトを使用して、接続されたサーバとデータを送信します.getOutputStream(), socket.getInputStream()構文でIOオブジェクトを取得します.このIOオブジェクトを使用して、必要に応じてデータを送信または受信します.送信データはwrite()メソッド,受信データはread()メソッドを用いる.

コンセントを作成する際の注意点


ソケット通信を行うには、サーバに接続し、接続されたサーバとデータを送信および受信する必要があります.しかし、これらはすべてスレッドで処理する必要があります.データの送受信と接続には、ネットワークがいつでもオフラインになっているため、完了するのに時間がかかる場合があります.したがって、スレッドとして処理しない場合、ANR(アクティブな場合の例)が発生する可能性があります.
もちろん、サービスコンポーネントで作成しても、接続とデータ送受信はスレッドで処理する必要があります.
ソケットプログラムの特性に応じて、サーバと長時間接続する必要があります.サービスが実行中にサーバとの接続に成功した場合、いつでも接続を切断する可能性があります.そのため、サーバの接続ステータスを特定し、接続に失敗したときに接続を維持するために、ある場所で接続を維持する必要があります.最終的には、サーバ接続部がスレッドとみなされ、接続されていない場合は、接続を続行する必要があります.

接続スレッド


簡単なサンプルコードは、isConnectedというboolean値を使用して、現在のサーバに接続するかどうかを示し、この値に基づいて再接続を試みます.
// 소켓 생성 스레드
inner class SocketThread: Thread() {
    override fun run() {
        while(flagConnection) {
            try {
                // 연결이 안된 경우
                if(!isConnected) {
                    // 소켓 객체 생성
                    socket = Socket()
                    // InetSocketAddress 생성자에 서버의 IP와 포트번호를 인자로 주고 객체 생성
                    val remoteAddr = InetSocketAddress(serverIp, serverPort)
                    // connect 메서드를 사용하고 InetSocketAddress 객체와 타임아웃 시간 지정
                    socket?.connect(remoteAddr, 10 * 1000)
                    
                    // 코드....
                } else { // 연결이 된 경우
                    SystemClock.sleep(10000)
                }
            } catch(e: Exception) {
                // 소켓 연결 시 오류는 여러 가지가 존재합니다.
            }
        }
    }
}

スレッドの読み込み


データはサーバから転送しなければ読み込めないため、read()メソッドを実行すると、サーバからデータを転送する前に下りは待機状態になり、実行されません.これらの読み込み動作は、次のようにスレッドとして扱われます.
// 읽기 스레드
inner class ReadThread: Thread() {
    override fun run() {
        // Byte 배열 생성
        var buffer = ByteArray(1024)
        try {
            // read 함수를 사용하여 데이터 수신
            val size = bin?.read(buffer)
            // 받아온 데이터의 크기가 0보다 크다면
            if(size != null && size > 0) {
                // read 후 업무 처리
            }
        } catch(e: IOException) {
            
        }
    }
}

書き込みスレッド


書き込み動作は、次のようにスレッドとして扱われます.次のコードは、アクティビティに書き込まれたコードです.書き込みデータは、メインスレッドによって管理されるビュー内のデータであるため、書き込みスレッドから直接ビューオブジェクトにアクセスすることはできません.最終的には、プライマリ・スレッドからビュー・データを取得し、書き込みスレッドのHandlerを使用してデータを転送し、サーバの構造記述プログラムに送信する必要があります.
// 쓰기 스레드
inner class WriteThread: Thread() {
    override fun run() {
        // 현재 스레드에 Looper 초기화
        Looper.prepare()
        // 핸들러 생성
        writeHandler = object: Handler(Looper.myLooper()!!) {
            // 메세지 처리하기
            override fun handleMessage(msg: Message) {
                try {
                    // 전달된 메세지 객체를 String으로 형변환 후 ByteArray로 변경
                    // 그 후 write 메서드를 통해 데이터 전송
                    bout?.write((msg.obj as String).toByteArray())
                    bout?.flush()
                    // ....
                } catch(e: Exception) {

                }
            }
        }
        // 메세지큐 작동
        Looper.loop()
    }
}
リファレンス
姜先生のAndroidプログラミング
間違った部分をコメントに書いたら修正します…!