2023-01-19 431
在Android中,有两个类LocalserVersocket和Localsocket.我认为它们像Unix插座中的AF_LOCAL(我不确定是否正确).
我的问题是:
是否可以在Java中创建LocalServerSocket,并使用普通的Unix套接字客户端在本机或其他过程中连接到它?
如果可能的话,我应该在本机中设置什么” sockaddr_un.sun_path”?
我已经编写了一个示例项目来测试它,我尝试设置.sun_path与LocalServerSocket中使用的字符串名称相同,但是失败了,本机无法连接到Java LocalserServersocket.
.
我的Java代码:
package test.socket;
import java.io.IOException;
import java.io.InputStream;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class TestSocketActivity extends Activity {
public static String SOCKET_ADDRESS = "my.local.socket.address";
public String TAG = "Socket_Test";
static{System.loadLibrary("testSocket");}
private native void clientSocketThreadNative();
private native void setStopThreadNative();
localServerSocket mLocalServerSocket;
localClientSocket mLocalClientSocket;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLocalServerSocket = new localServerSocket();
mLocalClientSocket = new localClientSocket();
}
/* LocalServerSocket */
public class localServerSocket extends Thread {
int bufferSize = 32;
byte[] buffer;
int bytesRead;
int totalBytesRead;
int posOffset;
LocalServerSocket server;
LocalSocket receiver;
InputStream input;
private volatile boolean stopThread;
public localServerSocket() {
Log.d(TAG, " +++ Begin of localServerSocket() +++ ");
buffer = new byte[bufferSize];
bytesRead = 0;
totalBytesRead = 0;
posOffset = 0;
try {
server = new LocalServerSocket(SOCKET_ADDRESS);
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "The LocalServerSocket created failed !!!");
e.printStackTrace();
}
stopThread = false;
}
public void run() {
Log.d(TAG, " +++ Begin of run() +++ ");
while (!stopThread) {
if (null == server){
Log.d(TAG, "The LocalServerSocket is NULL !!!");
stopThread = true;
break;
}
try {
Log.d(TAG, "LocalServerSocket begins to accept()");
receiver = server.accept();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "LocalServerSocket accept() failed !!!");
e.printStackTrace();
continue;
}
try {
input = receiver.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "getInputStream() failed !!!");
e.printStackTrace();
continue;
}
Log.d(TAG, "The client connect to LocalServerSocket");
while (receiver != null) {
try {
bytesRead = input.read(buffer, posOffset,
(bufferSize - totalBytesRead));
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "There is an exception when reading socket");
e.printStackTrace();
break;
}
if (bytesRead >= 0) {
Log.d(TAG, "Receive data from socket, bytesRead = "
+ bytesRead);
posOffset += bytesRead;
totalBytesRead += bytesRead;
}
if (totalBytesRead == bufferSize) {
Log.d(TAG, "The buffer is full !!!");
String str = new String(buffer);
Log.d(TAG, "The context of buffer is : " + str);
bytesRead = 0;
totalBytesRead = 0;
posOffset = 0;
}
}
Log.d(TAG, "The client socket is NULL !!!");
}
Log.d(TAG, "The LocalSocketServer thread is going to stop !!!");
if (receiver != null){
try {
receiver.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (server != null){
try {
server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void setStopThread(boolean value){
stopThread = value;
Thread.currentThread().interrupt(); // TODO : Check
}
}
/* Client native socket */
public class localClientSocket extends Thread {
private volatile boolean stopThread;
public localClientSocket(){
Log.d(TAG, " +++ Begin of localClientSocket() +++ ");
stopThread = false;
}
public void run(){
Log.d(TAG, " +++ Begin of run() +++ ");
while(!stopThread){
clientSocketThreadNative();
}
}
public void setStopThread(boolean value){
stopThread = value;
setStopThreadNative();
Thread.currentThread().interrupt(); // TODO : Check
}
}
public void bt_startServerOnClick(View v) {
mLocalServerSocket.start();
}
public void bt_startClientOnClick(View v) {
mLocalClientSocket.start();
}
public void bt_stopOnClick(View v) {
mLocalClientSocket.setStopThread(true);
mLocalServerSocket.setStopThread(true);
}
}
我的本机代码:
#define SOCKET_NAME "my.local.socket.address"
JNIEXPORT void JNICALL Java_test_socket_TestSocketActivity_clientSocketThreadNative
(JNIEnv *env, jobject object){
LOGD("In clientSocketThreadNative() : Begin");
stopThread = 1;
int sk, result;
int count = 1;
int err;
char *buffer = malloc(8);
int i;
for(i = 0; i<8; i++){
buffer[i] = (i+1);
}
/*
struct sockaddr_un addr;
bzero((char *)&addr,sizeof(addr);
addr.sun_family = AF_UNIX;
addr.sun_path = SOCKET_NAME;
*/
struct sockaddr_un addr = {
AF_UNIX, SOCKET_NAME
};
LOGD("In clientSocketThreadNative() : Before creating socket");
sk = socket(PF_LOCAL, SOCK_STREAM, 0);
if (sk < 0) {
err = errno;
LOGD("%s: Cannot open socket: %s (%d)\n",
__FUNCTION__, strerror(err), err);
errno = err;
return;
}
LOGD("In clientSocketThreadNative() : Before connecting to Java LocalSocketServer");
if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
err = errno;
LOGD("%s: connect() failed: %s (%d)\n",
__FUNCTION__, strerror(err), err);
close(sk);
errno = err;
return;
}
LOGD("In clientSocketThreadNative() : Connecting to Java LocalSocketServer succeed");
while(!stopThread){
result = write(sk, buffer, 8);
LOGD("In clientSocketThreadNative() : Total write = %d", result);
count++;
if(4 == count){
sleep(1);
count = 0;
}
}
LOGD("In clientSocketThreadNative() : End");
}
任何建议都将不胜感激!!!
以下代码可能不是完美的,但它起作用了!!!感谢Mike.
Java Part(套接字服务器):
package test.socket;
import java.io.IOException;
import java.io.InputStream;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class TestSocketActivity extends Activity {
public static String SOCKET_ADDRESS = "/test/socket/localServer";
public String TAG = "Socket_Test";
static{System.loadLibrary("testSocket");}
private native void clientSocketThreadNative();
private native void setStopThreadNative();
localSocketServer mLocalSocketServer;
localSocketClient mLocalSocketClient;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mLocalSocketServer = new localSocketServer();
mLocalSocketClient = new localSocketClient();
}
/* LocalSocketServer */
public class localSocketServer extends Thread {
int bufferSize = 32;
byte[] buffer;
int bytesRead;
int totalBytesRead;
int posOffset;
LocalServerSocket server;
LocalSocket receiver;
InputStream input;
private volatile boolean stopThread;
public localSocketServer() {
Log.d(TAG, " +++ Begin of localSocketServer() +++ ");
buffer = new byte[bufferSize];
bytesRead = 0;
totalBytesRead = 0;
posOffset = 0;
try {
server = new LocalServerSocket(SOCKET_ADDRESS);
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "The localSocketServer created failed !!!");
e.printStackTrace();
}
LocalSocketAddress localSocketAddress;
localSocketAddress = server.getLocalSocketAddress();
String str = localSocketAddress.getName();
Log.d(TAG, "The LocalSocketAddress = " + str);
stopThread = false;
}
public void run() {
Log.d(TAG, " +++ Begin of run() +++ ");
while (!stopThread) {
if (null == server){
Log.d(TAG, "The localSocketServer is NULL !!!");
stopThread = true;
break;
}
try {
Log.d(TAG, "localSocketServer begins to accept()");
receiver = server.accept();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "localSocketServer accept() failed !!!");
e.printStackTrace();
continue;
}
try {
input = receiver.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "getInputStream() failed !!!");
e.printStackTrace();
continue;
}
Log.d(TAG, "The client connect to LocalServerSocket");
while (receiver != null) {
try {
bytesRead = input.read(buffer, posOffset,
(bufferSize - totalBytesRead));
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d(TAG, "There is an exception when reading socket");
e.printStackTrace();
break;
}
if (bytesRead >= 0) {
Log.d(TAG, "Receive data from socket, bytesRead = "
+ bytesRead);
posOffset += bytesRead;
totalBytesRead += bytesRead;
}
if (totalBytesRead == bufferSize) {
Log.d(TAG, "The buffer is full !!!");
String str = new String(buffer);
Log.d(TAG, "The context of buffer is : " + str);
bytesRead = 0;
totalBytesRead = 0;
posOffset = 0;
}
}
Log.d(TAG, "The client socket is NULL !!!");
}
Log.d(TAG, "The LocalSocketServer thread is going to stop !!!");
if (receiver != null){
try {
receiver.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (server != null){
try {
server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void setStopThread(boolean value){
stopThread = value;
Thread.currentThread().interrupt(); // TODO : Check
}
}
/* Client native socket */
public class localSocketClient extends Thread {
private volatile boolean stopThread;
public localSocketClient(){
Log.d(TAG, " +++ Begin of localSocketClient() +++ ");
stopThread = false;
}
public void run(){
Log.d(TAG, " +++ Begin of run() +++ ");
while(!stopThread){
clientSocketThreadNative();
}
}
public void setStopThread(boolean value){
stopThread = value;
setStopThreadNative();
Thread.currentThread().interrupt(); // TODO : Check
}
}
public void bt_startServerOnClick(View v) {
mLocalSocketServer.start();
}
public void bt_startClientOnClick(View v) {
mLocalSocketClient.start();
}
public void bt_stopOnClick(View v) {
mLocalSocketClient.setStopThread(true);
mLocalSocketServer.setStopThread(true);
}
}
本机C部分(客户端)
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/un.h>
#include "test_socket_TestSocketActivity.h"
#define LOCAL_SOCKET_SERVER_NAME "/test/socket/localServer"
volatile int stopThread;
#ifndef __JNILOGGER_H_
#define __JNILOGGER_H_
#include <android/log.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG "NativeSocket"
#endif
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
#define LOGS(...) __android_log_print(ANDROID_LOG_SILENT,LOG_TAG,__VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif /* __JNILOGGER_H_ */
JNIEXPORT void JNICALL Java_test_socket_TestSocketActivity_clientSocketThreadNative
(JNIEnv *env, jobject object){
LOGD("In clientSocketThreadNative() : Begin");
stopThread = 0;
int sk, result;
int count = 1;
int err;
char *buffer = malloc(8);
int i;
for(i = 0; i<8; i++){
buffer[i] = (i+1);
}
struct sockaddr_un addr;
socklen_t len;
addr.sun_family = AF_LOCAL;
/* use abstract namespace for socket path */
addr.sun_path[0] = '\0';
strcpy(&addr.sun_path[1], LOCAL_SOCKET_SERVER_NAME );
len = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&addr.sun_path[1]);
LOGD("In clientSocketThreadNative() : Before creating socket");
sk = socket(PF_LOCAL, SOCK_STREAM, 0);
if (sk < 0) {
err = errno;
LOGD("%s: Cannot open socket: %s (%d)\n",
__FUNCTION__, strerror(err), err);
errno = err;
return;
}
LOGD("In clientSocketThreadNative() : Before connecting to Java LocalSocketServer");
if (connect(sk, (struct sockaddr *) &addr, len) < 0) {
err = errno;
LOGD("%s: connect() failed: %s (%d)\n",
__FUNCTION__, strerror(err), err);
close(sk);
errno = err;
return;
}
LOGD("In clientSocketThreadNative() : Connecting to Java LocalSocketServer succeed");
while(!stopThread){
result = write(sk, buffer, 8);
LOGD("In clientSocketThreadNative() : Total write = %d", result);
count++;
if(4 == count){
sleep(1);
count = 0;
}
}
LOGD("In clientSocketThreadNative() : End");
}
JNIEXPORT void JNICALL Java_test_socket_TestSocketActivity_setStopThreadNative
(JNIEnv *env, jobject object){
stopThread = 1;
}
在Android源中查看local_socket_client.c,看起来他们这样做:
int socket_make_sockaddr_un(const char *name, int namespaceId,
struct sockaddr_un *p_addr, socklen_t *alen)
{
memset (p_addr, 0, sizeof (*p_addr));
size_t namelen;
switch (namespaceId) {
case ANDROID_SOCKET_NAMESPACE_ABSTRACT:
namelen = strlen(name);
// Test with length +1 for the *initial* '\0'.
if ((namelen + 1) > sizeof(p_addr->sun_path)) {
goto error;
}
/*
* Note: The path in this case is *not* supposed to be
* '\0'-terminated. ("man 7 unix" for the gory details.)
*/
p_addr->sun_path[0] = 0;
memcpy(p_addr->sun_path + 1, name, namelen);
...
p_addr->sun_family = AF_LOCAL;
*alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
似乎MEMSET()很重要,因为整个sun_path是相关的. (看起来您是用结构初始化覆盖的.)它不是” 0″加上原始名称,它是实际的零字节! (其值是所有二进制零,而不是ASCII ‘0’)
尝试更接近他们的工作,包括领先的” \ 0″字节和af_local家族.
如果您有更新的代码(无论是否有效),请发布!我对您的结果感兴趣.你有没有让这个工作?
如果它不起作用,请找出errno是什么,然后调用perror()将其打印到stderr,或调用strerror()并记录输出.让我们知道您遇到的错误.
编辑
我最近在一个自己的项目中解决了这个问题.我发现关键是在调用connect()和bind()时指定正确的长度.在我上面发布的代码中,它通过使用结构中的sun_path的偏移,加上名称的长度来计算长度,另外一个用于领先的’\0’字节.如果指定其他长度,Java代码可能无法连接到套接字.
确认上述工作正常.以下示例不仅可以一起使用,而且将与相应的Android Localsocket和LocalserSocket类一起使用:
客户端(Android是使用LocalserVersocket的服务器):
#include <stdio.h> /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), connect(), send(), and recv() */
#include <sys/un.h> /* struct sockaddr_un */
#include <stdlib.h> /* for atoi() and exit() */
#include <string.h> /* for memset() */
#include <unistd.h> /* for close() */
#include <errno.h>
#include <stddef.h>
#define RCVBUFSIZE 2048 /* Size of receive buffer */
void DieWithError(char *errorMessage) /* Error handling function */
{
fprintf(stderr, "Error: %s - %s\n", errorMessage, strerror(errno));
exit(errno);
}
int main(int argc, char *argv[])
{
int sock; /* Socket descriptor */
struct sockaddr_un echoServAddr; /* Echo server address */
unsigned char *localSocketName = "MyTestSocket";
static unsigned char echoString[] = {0x80, 0x00, 0x0e, 0x10, 0x00, 0x9c, 0x40, 0xc9, 0x20, 0x20, 0x20, 0x32, 0x00, 0x00};
static unsigned int echoStringLen = sizeof(echoString); /* Length of string to echo */
unsigned char echoBuffer[RCVBUFSIZE]; /* Buffer for echo string */
int bytesRcvd, totalBytesRcvd; /* Bytes read in single recv()
and total bytes read */
int size;
int i;
/* Create a reliable, stream socket using Local Sockets */
if ((sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0)
DieWithError("socket() failed");
/* Construct the server address structure */
memset(&echoServAddr, 0, sizeof(echoServAddr)); /* Zero out structure */
echoServAddr.sun_family = AF_LOCAL; /* Local socket address family */
/**
* Tricky and obscure! For a local socket to be in the "Android name space":
* - The name of the socket must have a 0-byte value as the first character
* - The linux man page is right in that 0 bytes are NOT treated as a null terminator.
* - The man page is not clear in its meaning when it states that "the rest of the bytes in
* sunpath" are used. "Rest of bytes" is determined by the length passed in for
* sockaddr_len and Android sets this per the recommended file-system sockets of
* sizeof(sa_family_t) + strlen(sun_path) + 1. This is important when making calls
* to bind, connect, etc!
* We have initialized the struct sockaddr_un to zero already, so all that is needed is to
* begin the name copy at sun_path[1] and restrict its length to sizeof(echoServAddr.sun_path)-2
**/
strncpy(echoServAddr.sun_path + 1, localSocketName, sizeof(echoServAddr.sun_path) - 2);
size = sizeof(echoServAddr) - sizeof(echoServAddr.sun_path) + strlen(echoServAddr.sun_path+1) + 1;
/* Establish the connection to the echo server */
if (connect(sock, (struct sockaddr *) &echoServAddr, size) < 0)
DieWithError("connect() failed");
/* Send the string to the server */
if (send(sock, echoString, echoStringLen, 0) != echoStringLen)
DieWithError("send() sent a different number of bytes than expected");
/* Receive the same string back from the server */
totalBytesRcvd = 0;
printf("Sent: ");
for (i = 0; i < echoStringLen; i++)
printf("%02X ", echoString[i]);
printf("\n"); /* Print a final linefeed */
printf("Received: "); /* Setup to print the echoed string */
if ((bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0)) <= 0)
DieWithError("recv() failed or connection closed prematurely");
for (i = 0; i < bytesRcvd; i++)
printf("%02X ", echoBuffer[i]);
printf("\n"); /* Print a final linefeed */
close(sock);
exit(0);
}
服务器端(Android是使用LocalSocket的客户端):
#include <stdio.h> /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), connect(), send(), and recv() */
#include <sys/un.h> /* struct sockaddr_un */
#include <stdlib.h> /* for atoi() and exit() */
#include <string.h> /* for memset() */
#include <unistd.h> /* for close() */
#include <errno.h>
#include <stddef.h>
#define RCVBUFSIZE 2048 /* Size of receive buffer */
void DieWithError(char *errorMessage) /* Error handling function */
{
fprintf(stderr, "Error: %s - %s\n", errorMessage, strerror(errno));
exit(errno);
}
void HandleLocalClient(int clntSocket)
{
char echoBuffer[RCVBUFSIZE]; /* Buffer for echo string */
int recvMsgSize; /* Size of received message */
/* Receive message from client */
if ((recvMsgSize = recv(clntSocket, echoBuffer, RCVBUFSIZE, 0)) < 0)
DieWithError("recv() failed");
/* Send received string and receive again until end of transmission */
while (recvMsgSize > 0) /* zero indicates end of transmission */
{
/* Echo message back to client */
if (send(clntSocket, echoBuffer, recvMsgSize, 0) != recvMsgSize)
DieWithError("send() failed");
/* See if there is more data to receive */
if ((recvMsgSize = recv(clntSocket, echoBuffer, RCVBUFSIZE, 0)) < 0)
DieWithError("recv() failed");
}
close(clntSocket); /* Close client socket */
}
#define MAXPENDING 5 /* Maximum outstanding connection requests */
void DieWithError(char *errorMessage); /* Error handling function */
void HandleLocalClient(int clntSocket); /* TCP client handling function */
int main(int argc, char *argv[])
{
int servSock; /* Socket descriptor for server */
int clntSock; /* Socket descriptor for client */
struct sockaddr_un echoClntAddr; /* Client address */
unsigned int clntLen; /* Length of client address data structure */
struct sockaddr_un echoServAddr; /* Echo server address */
unsigned char *localSocketName = "MyTestSocket";
static unsigned int echoStringLen = 14; /* Length of string to echo */
unsigned char echoBuffer[RCVBUFSIZE]; /* Buffer for echo string */
int bytesRcvd, totalBytesRcvd; /* Bytes read in single recv()
and total bytes read */
int size;
int i;
/* Create a reliable, stream socket using Local Sockets */
if ((servSock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0)
DieWithError("socket() failed");
/* Construct the server address structure */
memset(&echoServAddr, 0, sizeof(echoServAddr)); /* Zero out structure */
echoServAddr.sun_family = AF_LOCAL; /* Local socket address family */
/**
* Tricky and obscure! For a local socket to be in the "Android name space":
* - The name of the socket must have a 0-byte value as the first character
* - The linux man page is right in that 0 bytes are NOT treated as a null terminator.
* - The man page is not clear in its meaning when it states that "the rest of the bytes in
* sunpath" are used. "Rest of bytes" is determined by the length passed in for
* sockaddr_len and Android sets this per the recommended file-system sockets of
* sizeof(sa_family_t) + strlen(sun_path) + 1. This is important when making calls
* to bind, connect, etc!
* We have initialized the struct sockaddr_un to zero already, so all that is needed is to
* begin the name copy at sun_path[1] and restrict its length to sizeof(echoServAddr.sun_path)-2
**/
strncpy(echoServAddr.sun_path + 1, localSocketName, sizeof(echoServAddr.sun_path) - 2);
size = sizeof(echoServAddr) - sizeof(echoServAddr.sun_path) + strlen(echoServAddr.sun_path+1) + 1;
/* Bind to the local address */
if (bind(servSock, (struct sockaddr *) &echoServAddr, size) < 0)
DieWithError("bind() failed");
/* Mark the socket so it will listen for incoming connections */
if (listen(servSock, MAXPENDING) < 0)
DieWithError("listen() failed");
for (;;) /* Run forever */
{
/* Set the size of the in-out parameter */
clntLen = sizeof(echoClntAddr);
/* Wait for a client to connect */
if ((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen)) < 0)
DieWithError("accept() failed");
/* clntSock is connected to a client! */
printf("Handling client\n");
HandleLocalClient(clntSock);
}
/* NOT REACHED */
return 0;
}
使用Android 4.0.3 ICS 03/15
测试
以上所述是小编给大家介绍的安卓本地服务器插座,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对77isp云服务器技术网的支持!
原文链接:https://77isp.com/post/25821.html
=========================================
https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。
数据库技术 2022-03-28
网站技术 2022-11-26
网站技术 2023-01-07
网站技术 2022-11-17
Windows相关 2022-02-23
网站技术 2023-01-14
Windows相关 2022-02-16
Windows相关 2022-02-16
Linux相关 2022-02-27
数据库技术 2022-02-20
抠敌 2023年10月23日
嚼餐 2023年10月23日
男忌 2023年10月22日
瓮仆 2023年10月22日
簿偌 2023年10月22日
扫码二维码
获取最新动态