1. 程式人生 > >Android6.0使用工具篇----本地socket通訊使用詳解

Android6.0使用工具篇----本地socket通訊使用詳解

閱讀Android原始碼,可以發現init.rc裡面有很多利用socket通訊的例子,比如說zygote程序(Android6.0位於init.${ro.zygote}.rc),比如說installd程序,比如說vold程序。下面,我們參考installd來自己實現一個利用socket通訊的demo程式,以便我們更好理解系統socket的使用。

程式碼流程圖:

 

1. 首先,在init.rc裡面註冊一個socketserver服務端,宣告socket控制代碼為mysocket,許可權為666,system:system使用者組,最終會在/dev/socket生成一個mysocket的檔案,客戶端可以連線這個socket與服務端進行通訊。

service socketserver /system/bin/socketserver
    class main
    socket mysocket stream 666 system system

2. socketserver的實現,可以看到,與一般的socket通訊並無太大差異,注意的是socket描述符是通過android_get_control_socket("mysocket")獲取的,監聽客戶端連線,並且讀取兩次資料,第一次讀取傳過來的字串長度,第二次讀取傳過來的字串,然後將字串原封不動的傳送給客戶端。

#define LOG_TAG "SOCKET_SERVER"
#define SOCKET_PATH "mysocket"
#define BUFFER_MAX    1024

static int readx(int s, void *_buf, int count)
{
    char *buf = _buf;
    int n = 0, r;
    if (count < 0) return -1;
    while (n < count) {
        r = read(s, buf + n, count - n);
        if (r < 0) {
            if (errno == EINTR) continue;
            ALOGE("read error: %s\n", strerror(errno));
            return -1;
        }
        if (r == 0) {
            ALOGE("eof\n");
            return -1; /* EOF */
        }
        n += r;
    }
    return 0;
}

static int writex(int s, const void *_buf, int count)
{
    const char *buf = _buf;
    int n = 0, r;
    if (count < 0) return -1;
    while (n < count) {
        r = write(s, buf + n, count - n);
        if (r < 0) {
            if (errno == EINTR) continue;
            ALOGE("write error: %s\n", strerror(errno));
            return -1;
        }
        n += r;
    }
    return 0;
}

int main(const int argc, const char *argv[]) {
    char buf[BUFFER_MAX];
    struct sockaddr addr;
    socklen_t alen;
    int lsocket, s, count;
    ALOGI("socketserver firing up\n");
    lsocket = android_get_control_socket(SOCKET_PATH);
    if (lsocket < 0) {
        ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
        exit(1);
    }
    if (listen(lsocket, 5)) {
        ALOGE("Listen on socket failed: %s\n", strerror(errno));
        exit(1);
    }
    fcntl(lsocket, F_SETFD, FD_CLOEXEC);

    for (;;) {
        alen = sizeof(addr);
        s = accept(lsocket, &addr, &alen);
        if (s < 0) {
            ALOGE("Accept failed: %s\n", strerror(errno));
            continue;
        }
        fcntl(s, F_SETFD, FD_CLOEXEC);

        ALOGI("new connection\n");
        for (;;) {
            unsigned short count;
            if (readx(s, &count, sizeof(count))) {
                ALOGE("failed to read size\n");
                break;
            }
            if ((count < 1) || (count >= BUFFER_MAX)) {
                ALOGE("invalid size %d\n", count);
                break;
            }
            if (readx(s, buf, count)) {
                ALOGE("failed to read command\n");
                break;
            }
            buf[count] = 0;
            ALOGI("buf = %s  count = %d\n", buf, count);
			if (writex(s, &count, sizeof(count))) return -1;
			if (writex(s, buf, count)) return -1;
        }
		
        ALOGI("closing connection\n");
        close(s);
    }

    return 0;
}

3. SocketClient的實現,利用LocalSocket進行通訊,伺服器socket地址是具有相同的字串mysocket的address物件,LocalSocketAddress("mysocket",LocalSocketAddress.Namespace.RESERVED),首先通過connect方法進行連線,然後從EditText獲取字串,給server端傳送兩次資料,第一次是字串長度,第二次是字串,最後從server端讀取返回的字串,顯示在TextView控制元件上。

    private boolean connect() {
        if (mSocket != null) {
            return true;
        }
        Log.i(TAG, "connecting...");
        try {
            mSocket = new LocalSocket();

            LocalSocketAddress address = new LocalSocketAddress("mysocket",
                    LocalSocketAddress.Namespace.RESERVED);

            mSocket.connect(address);
            mIn = mSocket.getInputStream();
            mOut = mSocket.getOutputStream();
        } catch (IOException ex) {
            disconnect();
            return false;
        }
        return true;
    }
	
    private void disconnect() {
        Log.i(TAG, "disconnecting...");
        try {
            if (mSocket != null)
                mSocket.close();
        } catch (IOException ex) {
        }
        try {
            if (mIn != null)
                mIn.close();
        } catch (IOException ex) {
        }
        try {
            if (mOut != null)
                mOut.close();
        } catch (IOException ex) {
        }
        mSocket = null;
        mIn = null;
        mOut = null;
    }
	
    private boolean readBytes(byte buffer[], int len) {
        int off = 0, count;
        if (len < 0)
            return false;
        while (off != len) {
            try {
                count = mIn.read(buffer, off, len - off);
                if (count <= 0) {
                    Log.e(TAG, "read error " + count);
                    break;
                }
                off += count;
            } catch (IOException ex) {
                Log.e(TAG, "read exception");
                break;
            }
        }
        Log.i(TAG, "read " + len + " bytes");
        if (off == len)
            return true;
        disconnect();
        return false;
    }

    private boolean readReply() {
        int len;
        buflen = 0;
        if (!readBytes(buf, 2))
            return false;
        len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
        if ((len < 1) || (len > 1024)) {
            Log.e(TAG, "invalid reply length (" + len + ")");
            disconnect();
            return false;
        }
        if (!readBytes(buf, len))
            return false;
        buflen = len;
        return true;
    }

    private boolean writeCommand(String _cmd) {
        byte[] cmd = _cmd.getBytes();
        int len = cmd.length;
        if ((len < 1) || (len > 1024))
            return false;
        buf[0] = (byte) (len & 0xff);
        buf[1] = (byte) ((len >> 8) & 0xff);
        try {
            mOut.write(buf, 0, 2);
            mOut.write(cmd, 0, len);
        } catch (IOException ex) {
            Log.e(TAG, "write error");
            disconnect();
            return false;
        }
        return true;
    }

    private String doTransaction(String cmd){
        if (!connect()) {
            Log.e(TAG, "connection failed");
            return "-1";
        }
        if (!writeCommand(cmd)) {
            Log.e(TAG, "write command failed? reconnect!");
            if (!connect() || !writeCommand(cmd)) {
                return "-1";
            }
        }
        Log.i(TAG, "send: '" + cmd + "'");
        if (readReply()) {
            String s = new String(buf, 0, buflen);
            tv.setText(s);
            Log.i(TAG, "recv: '" + s + "'");
            return s;
        } else {
                Log.i(TAG, "fail");
            return "-1";
        }
    }

最後,重新燒錄kernel,將socketserver推送到/system/bin/下面,可以看到,在/dev/socket/下生成了一個mysocket的socket檔案

安裝執行SocketClient,輸入字串,點選send,client端能正常獲取server端返回的字串,通訊成功。


總結:理解本地的socket通訊,有助於我們閱讀原始碼,Android中很多service都是通過socket通訊間接完成功能的實現,把握好socket通訊的呼叫流程,即可抽絲剝繭,理清實際的業務邏輯。

點選下載本文程式碼