2020年8月25日 星期二

android 用 kotlin coroutine 語法測試 LocalSocket

Java 有內建 LocalSocket 及 LocalServerSocket 兩種類型的程式庫, 是作為內部進程溝通管道(IPC), 進程甚至包括用 c 語言寫的外部 JNI 程式都可以, 一般的 client 就是 LocalSocket 類型 , 若要當伺服器時用 LocalServerSocket , 延續上一篇, 同樣採用 coroutine 方式:
    import kotlinx.coroutines.*
    import android.net.LocalSocket
    import android.net.LocalServerSocket
    import android.net.LocalSocketAddress
    import java.io.IOException 

    private val localSocketName = "com.example.testLocalSocket"   
    private suspend fun localServerListen() {
        val localServerSocket = LocalServerSocket(localSocketName) 
        while(true) {
            try {
                println("server listen...")
                val localSocket = localServerSocket.accept()// will block to listen
                println("server accept $localSocket")
                val array32 = ByteArray(32)
                val len = localSocket.getInputStream().read(array32)// will block to read
                println("server receive:")
                for (x in 0 until len) print("${array32.get(x)},")           
                println("\nserver finish: $len")
                localSocket.close()
            } catch (e: IOException) {
                e.printStackTrace();
                break;
            }
        }
        localServerSocket.close();
    }
    private suspend fun testLocalSocket(start:Int = 100) {
        try {
            val localSocket = LocalSocket()
            val endpointServer = LocalSocketAddress(
                localSocketName,
                LocalSocketAddress.Namespace.ABSTRACT
            )
            println("client connect ...")
            localSocket.connect(endpointServer)
            println("client send: $localSocket")
            val len = 32
            val array32 = ByteArray(len)
            for (x in 0 until len) array32.set(x, (start + x).toByte())
            localSocket.getOutputStream().write(array32, 0, len)
            localSocket.close()
        } catch (e: IOException) {
            e.printStackTrace();
        }
    }
在主程序用 GlobalScope.launch 同時發動三條 coroutines 一起執行:
    GlobalScope.launch(Dispatchers.IO) {
        launch { localServerListen() }
        launch { testLocalSocket(90) }
        launch { testLocalSocket(80) }
    }


後記: 用 c 寫 LocalSocket 通信程式, 使用 Unix Socket_Stream,並採用"虛根"的命名空間 (abstract namespace):
1. // localSocket.h   used  in  client  and  server
  char localSocketName[ ] = "com.example.testLocalSocket";// strlen < 106

2. // localSocket.c    for   client
  #include <stdlib.h>
  #include <stdio.h>
  #include <stddef.h>
  #include <sys/socket.h>
  #include <sys/un.h>
  #include <string.h>
  #include <unistd.h>
  #include "localSocket.h"
  int main() {
        int localSocket = socket(AF_UNIX, SOCK_STREAM, 0);
        if (localSocket < 0) exit(1);           
        struct sockaddr_un endpointServer; // socket for loopback addr
        memset(&endpointServer, 0, sizeof(endpointServer));// init  
        endpointServer.sun_family = AF_UNIX;
        int sunOffset = offsetof(struct sockaddr_un, sun_path);
        endpointServer.sun_path[0]  = '\0';
        strcpy(endpointServer.sun_path + 1, localSocketName);     
        socklen_t len = 1 + strlen(localSocketName) + sunOffset;

        if(connect(localSocket, (struct sockaddr *)&endpointServer, len) >= 0) {
            char msg[] = "Hello";
            send(localSocket, msg, strlen(msg), 0);
        }
        close(localSocket);
    }

3. // localServerSocket.c   for   server
    #include <stdlib.h> 
    #include <stdio.h> 
    #include <stddef.h> 
    #include <sys/socket.h> 
    #include <sys/un.h> 
    #include <string.h> 
    #include <unistd.h> 
    #include "localSocket.h"
    int main() {
        int localSocket = socket(AF_UNIX, SOCK_STREAM, 0);
        if (localSocket < 0) exit(1);                   
        struct sockaddr_un endpointServer; // socket for loopback addr
        memset(&endpointServer, 0, sizeof(endpointServer));// init   
        endpointServer.sun_family = AF_UNIX;
        int sunOffset = offsetof(struct sockaddr_un, sun_path);          
        endpointServer.sun_path[0]  = '\0';
        strcpy(endpointServer.sun_path + 1, localSocketName);    
        socklen_t len = 1 + strlen(localSocketName) + sunOffset;
       
        if (bind(localSocket, (struct sockaddr *)&endpointServer, len) >= 0 && listen(localSocket, 5) >= 0) {
            int socketfd = accept(localSocket, (struct sockaddr *)&endpointServer, &len);
            if(socketfd >= 0) {
                char buf[1024];
                int n = read(socketfd, buf, sizeof(buf));
                buf[n] = 0;
                printf("server receive: %s\n", buf);
                close(socketfd);
            }
        }
        close(localSocket); 
    }

編譯上述兩個程式並執行看看:
    g++  localServerSocket.c   -o   server   &&   ./server  &
    g++  localSocket.c   -o  client   &&   ./client

理解"虛根"(abstract root)的用意:上述 endpointServer.sun_path[0] = '\0'一開頭處填入  0 (零, 也是字串結尾 End Of String), 會讓 bind localSoket 時, 不用在檔案系統內產生檔案,否則執行的程式將在檔案系統目錄內建構出一個 domain name(長度為 0 的檔案 com.example.testLocalSocket),導致不同目錄下執行的程式將有不同的 domain name(整個 domain name 包含前置目錄,除非在同根的目錄用相同名稱,才會有相同 domain name), 執行 domain name 各異的 LocalSocket 程式,除非在命名上取巧,基本上是無法相互溝通的.LocalSocket 採用"虛根"是一個很聰明的方式,可想像 0 就是一個虛根,當執行的程式在虛根上長出 domain name,也就不需在檔案系統下留下任何軌跡,LocalSocket 通信程式只要在虛根上採用了相同名稱,自然就能相互通訊,解決了 domain name 命名的麻煩.

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識...