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通訊的呼叫流程,即可抽絲剝繭,理清實際的業務邏輯。