【问题标题】:Moto G5 Plus can't connect via Socket to a local serverMoto G5 Plus 无法通过 Socket 连接到本地服务器
【发布时间】:2018-03-26 12:03:28
【问题描述】:

我有一个应用程序,智能手机必须通过 SSLSocket 连接到本地服务器。我在 5 款不同的智能手机上测试了我的应用程序:Moto G2 (6.0)、Redmi 3S (6.0.1)、LG K5 (6.0)、Moto G5 Plus (7.1.1) 和 OnePlus 5 (8.0)。 Moto G5 Plus 是唯一出现此问题的产品。

这是导致问题行为的行。所有测试均在同一网络上完成。

socket = (SSLSocket) sslContext.getSocketFactory().createSocket(serverAddress, serverPort);

Moto G5 Plus 或 Android 7+ 是否存在与此行为相关的任何已知问题?

编辑:更多测试导致 Android 系统在识别 WiFi 接口已连接但没有互联网时尝试强制 Socket 通过移动网络连接的想法。 有没有办法强制 Socket 使用 WiFi 而不是移动网络?

【问题讨论】:

    标签: android sockets android-6.0-marshmallow android-7.0-nougat motorola


    【解决方案1】:

    免责声明:我没有对此进行测试,所以我真的不确定它是否有效。

    Network 类有一个bind(Socket) 方法,也许你可以找到wifi 网络,然后将它绑定到你的socket。从文档看来,这就是您所需要的,它说:

    /**
     * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
     * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
     * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
     */
    

    Socket不应该在绑定到网络之前连接,所以我认为你应该用socketFactory.createSocket()创建它,并且只在绑定之后连接它。

    所以,你应该首先找到你的Network(Kotlin):

    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val wifiNetwork = connectivityManager.allNetworks.firstOrNull {
        val info = connectivityManager.getNetworkInfo(it)
        info.type == ConnectivityManager.TYPE_WIFI
    }
    

    或(Java)

    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
    Network wifiNetwork = null;
    for(Network network : connectivityManager.getAllNetworks()){
        NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
        if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI){
            wifiNetwork = network;
            break;
        }
    }
    

    然后将其绑定到Socket,最后连接(Kotlin):

    wifiNetwork?.bindSocket(socket)
    val socketAddress = InetSocketAddress(hostname, port)
    socket.connect(socketAddress)
    

    或(Java)

    if(wifiNetwork != null){
        wifiNetwork.bindSocket(socket);
    }
    InetSocketAddress socketAddress = InetSocketAddress(hostName, port);
    socket.connect(socketAddress);
    

    注意,需要ACCESS_NETWORK_STATE权限

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    

    【讨论】:

    • 您好!感谢您提供如此完整的答案。我会尝试并返回给你!
    • @Minoru 当然我很想知道它是否有效 xD 顺便说一句,我更新了答案,如果您还没有权限,则需要添加ACCESS_NETWORK_STATE 权限
    【解决方案2】:

    希望对您有所帮助,我刚刚在 github 上找到了您的解决方案。 有关详细信息和官方链接,请check this out,我认为这对您有帮助。 如果不是答案,请忽略这个答案。

    我们使用 AsyncTask 来避免网络访问的 StrictMode 致命错误(查看参考资料)。 StrictMode 策略只是禁止我们影响 UI 线程。

       /* AsyncTask class which manages connection with server app and is sending shutdown command.
       */
    public class ShutdownAsyncTask extends AsyncTask<String, String, TCPClient> {
    
        private static final String     COMMAND     = "shutdown -s"      ;
        private              TCPClient  tcpClient                        ;
        private              Handler    mHandler                         ;
        private static final String     TAG         = "ShutdownAsyncTask";
    
        /**
         * ShutdownAsyncTask constructor with handler passed as argument. The UI is updated via handler.
         * In doInBackground(...) method, the handler is passed to TCPClient object.
         * @param mHandler Handler object that is retrieved from MainActivity class and passed to TCPClient
         *                 class for sending messages and updating UI.
         */
        public ShutdownAsyncTask(Handler mHandler){
            this.mHandler = mHandler;
        }
    
        /**
         * Overriden method from AsyncTask class. There the TCPClient object is created.
         * @param params From MainActivity class empty string is passed.
         * @return TCPClient object for closing it in onPostExecute method.
         */
        @Override
        protected TCPClient doInBackground(String... params) {
            Log.d(TAG, "In do in background");
    
            try{
                tcpClient = new TCPClient(mHandler,
                                          COMMAND,
                                          "192.168.1.1",
                                          new TCPClient.MessageCallback() {
                    @Override
                    public void callbackMessageReceiver(String message) {
                        publishProgress(message);
                    }
                });
    
            }catch (NullPointerException e){
                Log.d(TAG, "Caught null pointer exception");
                e.printStackTrace();
            }
            tcpClient.run();
            return null;
        }   
    

    在这个 AsyncTask 中,我们正在创建 TCPClient 对象(解释如下)。在 TCPClient 构造函数中,我们传递了用于更改 UI 的 Handler 对象,COMMAND - 带有“shutdown -s”命令的字符串,用于关闭计算机,IP 号 - 服务器 IP 号;回调对象 - 当我们获得服务器响应时,回调方法 'messageCallbackReceiver' 正在启动 'publishProgress' 方法,它正在将进度发布到 'onProgressUpdate' AsyncTask 的方法。

            /**
             * Overriden method from AsyncTask class. Here we're checking if server answered properly.
             * @param values If "restart" message came, the client is stopped and computer should be restarted.
             *               Otherwise "wrong" message is sent and 'Error' message is shown in UI.
             */
            @Override
            protected void onProgressUpdate(String... values) {
                super.onProgressUpdate(values);
                Log.d(TAG, "In progress update, values: " + values.toString());
                if(values[0].equals("shutdown")){
                    tcpClient.sendMessage(COMMAND);
                    tcpClient.stopClient();
                    mHandler.sendEmptyMessageDelayed(MainActivity.SHUTDOWN, 2000);
    
                }else{
                    tcpClient.sendMessage("wrong");
                    mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
                    tcpClient.stopClient();
                }
            }
    

    收到正确的消息后,我们正在发送命令,或者如果我们收到错误的消息,我们正在发送消息“错误”并停止客户端。在此之后,我们将被转移到 'onPostExecute' 方法:

        @Override
            protected void onPostExecute(TCPClient result){
                super.onPostExecute(result);
                Log.d(TAG, "In on post execute");
                if(result != null && result.isRunning()){
                    result.stopClient();
                }
                mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 4000);
    
            }
        }
    

    所以一步一步来:

    ->AsyncTask 正在创建 TCPClient 对象。

    ->在 TCPClient 构造函数中,我们传递 Handler、Command、IP 号和回调对象。

    ->当 TCPClient 开始连接时,它会向服务器发送消息“shutdown”。

    ->当我们从服务器接收消息时,回调将其传递给'onProgressUpdate'。

    ->如果收到的消息(来自服务器的响应)等于“关闭”,我们正在向服务器发送命令。

    ->发送后我们停止客户端,将我们转移到'onPostExecute'方法。

    ->同时,处理程序正在接收带有在 MainActivity 中定义的“msg.what”整数的空消息,它们负责更新 GUI。

    小部件 UI 如何更新的示例:

           mHandler = new Handler(){
            public void handleMessage(Message msg) {
                switch(msg.what){
                    case SHUTDOWN:
                        Log.d(mTag, "In Handler's shutdown");
    
                         views     = new RemoteViews(context.getPackageName(), R.layout.activity_main);
                         widget    = new ComponentName(context, MainActivity.class);
                         awManager = AppWidgetManager.getInstance(context);
                                     views.setTextViewText(R.id.state, "Shutting PC...");
                                     awManager.updateAppWidget(widget,views);
                        break;
    

    TCP客户端

    这个类负责维护连接。我会一步一步解释:

    在第一步中,我们可以看到从 ShutdownAsyncTask 和其他传递的对象。此外,我们还可以看到 sendMessage 和 stopClient 方法。

        public class TCPClient {
    
            private static final String            TAG             = "TCPClient"     ;
            private final        Handler           mHandler                          ;
            private              String            ipNumber, incomingMessage, command;
                                 BufferedReader    in                                ;
                                 PrintWriter       out                               ;
            private              MessageCallback   listener        = null            ;
            private              boolean           mRun            = false           ;
    
    
            /**
             * TCPClient class constructor, which is created in AsyncTasks after the button click.
             * @param mHandler Handler passed as an argument for updating the UI with sent messages
             * @param command  Command passed as an argument, e.g. "shutdown -r" for restarting computer
             * @param ipNumber String retrieved from IpGetter class that is looking for ip number.
             * @param listener Callback interface object
             */
            public TCPClient(Handler mHandler, String command, String ipNumber, MessageCallback listener) {
                this.listener         = listener;
                this.ipNumber         = ipNumber;
                this.command          = command ;
                this.mHandler         = mHandler;
            }
    
            /**
             * Public method for sending the message via OutputStream object.
             * @param message Message passed as an argument and sent via OutputStream object.
             */
            public void sendMessage(String message){
                if (out != null && !out.checkError()) {
                    out.println(message);
                    out.flush();
                    mHandler.sendEmptyMessageDelayed(MainActivity.SENDING, 1000);
                    Log.d(TAG, "Sent Message: " + message);
    
                }
            }
    
            /**
             * Public method for stopping the TCPClient object ( and finalizing it after that ) from AsyncTask
             */
            public void stopClient(){
                Log.d(TAG, "Client stopped!");
                mRun = false;
            }
    

    神奇的地方发生在 'run()' 方法中。在这里,我们使用“try-catch”工具来处理异常(服务器未启用,ip 不正确等)。正如你在下面看到的,我们有无限的 while() 循环来监听传入的消息。我们可以简单地停止它并使用 'stopClient()' 方法完成(用于 ShutdownAsyncTask 的 'onProgressUpdate' 方法)

        public void run() {
    
                mRun = true;
    
                try {
                    // Creating InetAddress object from ipNumber passed via constructor from IpGetter class.
                    InetAddress serverAddress = InetAddress.getByName(ipNumber);
    
                    Log.d(TAG, "Connecting...");
    
                    /**
                     * Sending empty message with static int value from MainActivity
                     * to update UI ( 'Connecting...' ).
                     *
                     * @see com.example.turnmeoff.MainActivity.CONNECTING
                     */
                    mHandler.sendEmptyMessageDelayed(MainActivity.CONNECTING,1000);
    
                    /**
                     * Here the socket is created with hardcoded port.
                     * Also the port is given in IpGetter class.
                     *
                     * @see com.example.turnmeoff.IpGetter
                     */
                    Socket socket = new Socket(serverAddress, 4444);
    
    
                    try {
    
                        // Create PrintWriter object for sending messages to server.
                        out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
    
                        //Create BufferedReader object for receiving messages from server.
                        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    
                        Log.d(TAG, "In/Out created");
    
                        //Sending message with command specified by AsyncTask
                        this.sendMessage(command);
    
                        //
                        mHandler.sendEmptyMessageDelayed(MainActivity.SENDING,2000);
    
                        //Listen for the incoming messages while mRun = true
                        while (mRun) {
                            incomingMessage = in.readLine();
                            if (incomingMessage != null && listener != null) {
    
                                /**
                                 * Incoming message is passed to MessageCallback object.
                                 * Next it is retrieved by AsyncTask and passed to onPublishProgress method.
                                 *
                                 */
                                listener.callbackMessageReceiver(incomingMessage);
    
                            }
                            incomingMessage = null;
    
                        }
    
                        Log.d(TAG, "Received Message: " +incomingMessage);
    
                    } catch (Exception e) {
    
                        Log.d(TAG, "Error", e);
                        mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
    
                    } finally {
    
                        out.flush();
                        out.close();
                        in.close();
                        socket.close();
                        mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 3000);
                        Log.d(TAG, "Socket Closed");
                    }
    
                } catch (Exception e) {
    
                    Log.d(TAG, "Error", e);
                    mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
    
                }
    
            }
    

    客户端中的最后一件事是回调接口。我们把它放在最后的 TCPClient 类中:

        /**
             * Callback Interface for sending received messages to 'onPublishProgress' method in AsyncTask.
             *
             */
            public interface MessageCallback {
                /**
                 * Method overriden in AsyncTask 'doInBackground' method while creating the TCPClient object.
                 * @param message Received message from server app.
                 */
                public void callbackMessageReceiver(String message);
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-06
      • 2011-09-25
      • 1970-01-01
      • 1970-01-01
      • 2011-01-12
      • 2015-05-15
      相关资源
      最近更新 更多