LocalSocket其通訊方式與Socket差很少,只是LocalSocket沒有跨越網絡邊界。對於*nix系統來講,「一切皆爲文件」,Socket也不例外,Socket按照收發雙方的媒介來講有三種類型:html
1,經過網絡端口: 即經過本地迴環接口(即LoopBack)127.0.0.1來收發數據;java
2,經過文件系統: 經過文件做爲收發數據的中轉站;linux
3,經過內存映射文件:在內存中開闢一塊區域做爲收發數據的中轉站,此區域仍然使用文件讀寫API進行訪問;android
LocalSocket支持方式2和方式3。ios
如下經過HAL層(c)做爲server,App端(java)做爲client,進行LocalSocket通訊演示(核心部分代碼):服務器
C代碼:網絡
J_U8* sName="nano_server_socket"; J_Int server_sockfd, client_sockfd; J_Int server_len, client_len; J_Int reuse = 1; J_Int err; struct sockaddr_un server_address; /*聲明一個UNIX域套接字結構*/ struct sockaddr_un client_address; unlink (sName); /*刪除原有server_socket對象*/ /*建立 socket, 通訊協議爲AF_LOCAL, SOCK_STREAM 數據方式*/ server_sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); if(server_sockfd < 0){ ALOGE("server_sockfd error : %s\n",strerror(errno)); return; } /*配置服務器信息(socket對象路徑)*/ // Check with length +1 for the *initial* '\0'. if ((strlen(sName) + 1) > sizeof(server_address.sun_path)) { ALOGE("name too long\n"); goto EXIT; } /* * Note: The path in this case is *not* supposed to be * '\0'-terminated. ("man 7 unix" for the gory details.) */ server_address.sun_path[0] = 0; memcpy(server_address.sun_path + 1, sName, strlen(sName)); /*配置服務器信息(通訊協議)*/ server_address.sun_family = AF_LOCAL; /*默認設置resue FLAG*/ if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { ALOGE("reuse error\n"); goto EXIT; } /*配置服務器信息(服務器地址長度)*/ client_len = server_len = strlen(sName) + offsetof(struct sockaddr_un, sun_path) + 1; /*綁定 socket 對象*/ bind (server_sockfd, (struct sockaddr *)&server_address, server_len); /*監聽網絡,隊列數爲1*/ if(listen(server_sockfd,1)<0){ ALOGE("listen error : %s\n",strerror(errno)); goto EXIT; }
/*接受客戶端請求; 第2個參數用來存儲客戶端地址; 第3個參數用來存儲客戶端地址的大小*/ /*創建(返回)一個到客戶端的文件描述符,用以對客戶端的讀寫操做*/ client_sockfd = accept (server_sockfd, (struct sockaddr *)&client_address, (socklen_t *)&client_len);
if (client_sockfd == -1) { ALOGE("accept error : %s\n",strerror(errno)); goto EXIT; } else if (client_sockfd> FD_SETSIZE){ ALOGE("accept reach max\n"); close(client_sockfd); goto EXIT; }
/*start read loop*/
while(1) {
char buf[1024]={0};
ssize_t res=read(client_socketfd,buf,sizeof(buf));app
ALOGD("server get data : %s", buf);socket
if(res <=0){
if(errno == EAGAIN)
continue;
ALOGE("errno %s\n",strerror(errno));
close(client_socketfd);
return;
}ide
}
EXIT: close(server_sockfd);
HAL層可具體參考Android源碼的 system\bt\osi\src\socket_utils\ 目錄下的 socket_local_client.cc 和 socket_local_server.cc 代碼(Android 8.0),
直接調用以下封裝好的接口便可:
/***********/ /***服務端***/ /**********/ /*(1)建立server socket接收client端數據*/ server_sockfd = osi_socket_local_server("server_socket",ANDROID_SOCKET_NAMESPACE_ABSTRACT,SOCK_STREAM); /*(2)等待client端鏈接*/ client_sockfd = accept (server_sockfd, NULL, NULL); /*(3)數據讀取線程*/ while(1){ ssize_t res=read(client_sockfd ,buf,sizeof(buf)); } /***********/ /***客戶端***/ /**********/ /*(1)建立client socket */ client_sockfd = osi_socket_local_client("client_socket",ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); /*(2)發送數據到server端 */ write(client_sockfd, "hello", strlen("hello"));
JAVA代碼:
package com.example.administrator.localsocket; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Bundle; import android.os.RemoteException; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import java.io.IOException; import static android.widget.Toast.LENGTH_SHORT; public class MainActivity extends AppCompatActivity { private static final String SOCKET_ADDRESS = "nano_server_socket"; //和HAL層統一地址名稱 LocalSocket client_socket = null; LocalSocketAddress addr; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); client_socket = new LocalSocket(); //建立socket對象 addr = new LocalSocketAddress(SOCKET_ADDRESS,LocalSocketAddress.Namespace.ABSTRACT); //使用虛空間地址 try { client_socket.connect(addr); //請求鏈接 } catch (IOException e) { e.printStackTrace(); } findViewById(R.id.sendMsg).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { //發送數據 String data="hello"; client_socket.getOutputStream().write(data.getBytes()); } catch (IOException e) { e.printStackTrace(); } } }); }
@Override protected void onStop() { super.onStop(); try { client_socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
注:Android8.0版本驗證app端要用LocalSocket,須要兩個條件:
1.apk須要簽名成platform_app; (方法可參考: https://www.cnblogs.com/blogs-of-lxl/p/9233285.html )
2.須要給platform_app增長selinux權限,修改xxx\sepolicy\platform_app.te,添加以下兩條規則:
typeattribute platform_app mlstrustedsubject;
allow platform_app audioserver:unix_stream_socket connectto;
測試結果: