2016-12-15 6 views
0

私はクライアントがアンドロイドで動作するクライアントサーバーモデルを持っています。次のコードを使用してソケットを確立します。 mksocketユーティリティ機能ビーイングと後で再確立するときにソケットが死ぬ

(すべては、クライアントがログインして再ログインしない)

public class LoginAsync extends AsyncTask<Boolean, String, Boolean> 
protected Boolean doInBackground(Boolean... params) 
{ 
    try 
    { 
     //only handle 1 login request at a time 
     synchronized(loginLock) 
     { 
      if(tryingLogin) 
      { 
       Utils.logcat(Const.LOGW, tag, "already trying a login. ignoring request"); 
       onPostExecute(false); 
       return false; 
      } 
      tryingLogin = true; 
     } 


     //http://stackoverflow.com/a/34228756 
     //check if server is available first before committing to anything 
     // otherwise this process will stall. host not available trips timeout exception 
     Socket diag = new Socket(); 
     diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT); 
     diag.close(); 

     //send login command 
     Vars.commandSocket = Utils.mkSocket(Vars.serverAddress, Vars.commandPort, Vars.expectedCertDump); 
     String login = Utils.currentTimeSeconds() + "|login|" + uname + "|" + passwd; 
     Vars.commandSocket.getOutputStream().write(login.getBytes()); 

     //read response 
     byte[] responseRaw = new byte[Const.BUFFERSIZE]; 
     int length = Vars.commandSocket.getInputStream().read(responseRaw); 

     //on the off chance the socket crapped out right from the get go, now you'll know 
     if(length < 0) 
     { 
      Utils.logcat(Const.LOGE, tag, "Socket closed before a response could be read"); 
      onPostExecute(false); 
      return false; 
     } 

     //there's actual stuff to process, process it! 
     String loginresp = new String(responseRaw, 0, length); 
     Utils.logcat(Const.LOGD, tag, loginresp); 

     //process login response 
     String[] respContents = loginresp.split("\\|"); 
     if(respContents.length != 4) 
     { 
      Utils.logcat(Const.LOGW, tag, "Server response imporoperly formatted"); 
      onPostExecute(false); //not a legitimate server response 
      return false; 
     } 
     if(!(respContents[1].equals("resp") && respContents[2].equals("login"))) 
     { 
      Utils.logcat(Const.LOGW, tag, "Server response CONTENTS imporperly formated"); 
      onPostExecute(false); //server response doesn't make sense 
      return false; 
     } 
     long ts = Long.valueOf(respContents[0]); 
     if(!Utils.validTS(ts)) 
     { 
      Utils.logcat(Const.LOGW, tag, "Server had an unacceptable timestamp"); 
      onPostExecute(false); 
      return false; 
     } 
     Vars.sessionid = Long.valueOf(respContents[3]); 

     //establish media socket 
     Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump); 
     String associateMedia = Utils.currentTimeSeconds() + "|" + Vars.sessionid; 
     Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes()); 

     Intent cmdListenerIntent = new Intent(Vars.applicationContext, CmdListener.class); 
     Vars.applicationContext.startService(cmdListenerIntent); 

     onPostExecute(true); 
     return true; 
    } 
    catch (CertificateException c) 
    { 
     Utils.logcat(Const.LOGE, tag, "server certificate didn't match the expected"); 
     onPostExecute(false); 
     return false; 
    } 
    catch (Exception i) 
    { 
     Utils.dumpException(tag, i); 
     onPostExecute(false); 
     return false; 
    } 
} 

:ここ

public static Socket mkSocket(String host, int port, final String expected64) throws CertificateException 
{ 
    TrustManager[] trustOnlyServerCert = new TrustManager[] 
    {new X509TrustManager() 
      { 
       @Override 
       public void checkClientTrusted(X509Certificate[] chain, String alg) 
       { 
       } 

       @Override 
       public void checkServerTrusted(X509Certificate[] chain, String alg) throws CertificateException 
       { 
        //Get the certificate encoded as ascii text. Normally a certificate can be opened 
        // by a text editor anyways. 
        byte[] serverCertDump = chain[0].getEncoded(); 
        String server64 = Base64.encodeToString(serverCertDump, Base64.NO_PADDING & Base64.NO_WRAP); 

        //Trim the expected and presented server ceritificate ascii representations to prevent false 
        // positive of not matching because of randomly appended new lines or tabs or both. 
        server64 = server64.trim(); 
        String expected64Trimmed = expected64.trim(); 
        if(!expected64Trimmed.equals(server64)) 
        { 
         throw new CertificateException("Server certificate does not match expected one."); 
        } 

       } 

       @Override 
       public X509Certificate[] getAcceptedIssuers() 
       { 
        return null; 
       } 

      } 
    }; 
    try 
    { 
     SSLContext context; 
     context = SSLContext.getInstance("TLSv1.2"); 
     context.init(new KeyManager[0], trustOnlyServerCert, new SecureRandom()); 
     SSLSocketFactory mkssl = context.getSocketFactory(); 
     Socket socket = mkssl.createSocket(host, port); 
     socket.setKeepAlive(true); 
     return socket; 
    } 
    catch (Exception e) 
    { 
     dumpException(tag, e); 
     return null; 
    } 
} 

が成功したログインに開始されるコマンド・リスナー・サービスである:

public class CmdListener extends IntentService 
protected void onHandleIntent(Intent workIntent) 
{ 
    // don't want this to catch the login resposne 
    Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started"); 

    while(inputValid) 
    { 
     String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener 
     try 
     {//the async magic here... it will patiently wait until something comes in 

      byte[] rawString = new byte[Const.BUFFERSIZE]; 
      int length = Vars.commandSocket.getInputStream().read(rawString); 
      if(length < 0) 
      { 
       throw new Exception("input stream read failed"); 
      } 
      String fromServer = new String(rawString, 0, length); 
      String[] respContents = fromServer.split("\\|"); 
      logd = logd + "Server response raw: " + fromServer + "\n"; 

      //check for properly formatted command 
      if(respContents.length != 4) 
      { 
       Utils.logcat(Const.LOGW, tag, "invalid server response"); 
       continue; 
      } 

      //verify timestamp 
      long ts = Long.valueOf(respContents[0]); 
      if(!Utils.validTS(ts)) 
      { 
       Utils.logcat(Const.LOGW, tag, "Rejecting server response for bad timestamp"); 
       continue; 
      } 

      //just parse and process commands here. not much to see 

     } 
     catch (IOException e) 
     { 
      Utils.logcat(Const.LOGE, tag, "Command socket closed..."); 
      Utils.dumpException(tag, e); 
      inputValid = false; 
     } 
     catch(NumberFormatException n) 
     { 
      Utils.logcat(Const.LOGE, tag, "string --> # error: "); 
     } 
     catch(NullPointerException n) 
     { 
      Utils.logcat(Const.LOGE, tag, "Command socket null pointer exception"); 
      inputValid = false; 
     } 
     catch(Exception e) 
     { 
      Utils.logcat(Const.LOGE, tag, "Other exception"); 
      inputValid = false; 
     } 
    } 
    //only 1 case where you don't want to restart the command listener: quitting the app. 
    //the utils.quit function disables BackgroundManager first before killing the sockets 
    //that way when this dies, nobody will answer the command listener dead broadcast 

    Utils.logcat(Const.LOGE, tag, "broadcasting dead command listner"); 
    try 
    { 
     Intent deadBroadcast = new Intent(Const.BROADCAST_BK_CMDDEAD); 
     sendBroadcast(deadBroadcast); 
    } 
    catch (Exception e) 
    { 
     Utils.logcat(Const.LOGE, tag, "couldn't broadcast dead command listener... leftover broadacast from java socket stupidities?"); 
     Utils.dumpException(tag, e); 
    } 
} 

そして、ここにバックグラウンドマネージャーがありますあなたは、LTEへの無線LANから無線LANにLTEを切り替えるか、何からLTEへの地下鉄から出てきたときにエン:

public class BackgroundManager extends BroadcastReceiver 
{ 
private static final String tag = "BackgroundManager"; 

@Override 
public void onReceive(final Context context, Intent intent) 
{ 
    if(Vars.applicationContext == null) 
    { 
     //sometimes intents come in when the app is in the process of shutting down so all the contexts won't work. 
     //it's shutting down anyways. no point of starting something 
     return; 
    } 

    AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 

    if(Vars.uname == null || Vars.passwd == null) 
    { 
     //if the person hasn't logged in then there's no way to start the command listener 
     // since you won't have a command socket to listen on 
     Utils.logcat(Const.LOGW, tag, "user name and password aren't available?"); 
    } 

    String action = intent.getAction(); 
    if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) 
    { 
     manager.cancel(Vars.pendingRetries); 
     new KillSocketsAsync().execute(); 

     if(Utils.hasInternet()) 
     { 
      //internet reconnected case 
      Utils.logcat(Const.LOGD, tag, "internet was reconnected"); 
      new LoginAsync(Vars.uname, Vars.passwd).execute(); 
     } 
     else 
     { 
      Utils.logcat(Const.LOGD, tag, "android detected internet loss"); 
     } 
     //command listener does a better of job of figuring when the internet died than android's connectivity manager. 
     //android's connectivity manager doesn't always get subway internet loss 
    } 
    else if (action.equals(Const.BROADCAST_BK_CMDDEAD)) 
    { 
     String loge = "command listener dead received\n"; 

     //cleanup the pending intents and make sure the old sockets are gone before making new ones 
     manager.cancel(Vars.pendingRetries); 
     new KillSocketsAsync().execute(); //make sure everything is good and dead 

     //all of this just to address the stupid java socket issue where it might just endlessly die/reconnect 
     //initialize the quick dead count and timestamp if this is the first time 
     long now = System.currentTimeMillis(); 
     long deadDiff = now - Vars.lastDead; 
     Vars.lastDead = now; 
     if(deadDiff < Const.QUICK_DEAD_THRESHOLD) 
     { 
      Vars.quickDeadCount++; 
      loge = loge + "Another quick death (java socket stupidity) occured. Current count: " + Vars.quickDeadCount + "\n"; 
     } 

     //with the latest quick death, was it 1 too many? if so restart the app 
     //https://stackoverflow.com/questions/6609414/how-to-programatically-restart-android-app 
     if(Vars.quickDeadCount == Const.QUICK_DEAD_MAX) 
     { 
      loge = loge + "Too many quick deaths (java socket stupidities). Restarting the app\n"; 
      Utils.logcat(Const.LOGE, tag, loge); 
      //self restart, give it a 5 seconds to quit 
      Intent selfStart = new Intent(Vars.applicationContext, InitialServer.class); 
      int pendingSelfId = 999; 
      PendingIntent selfStartPending = PendingIntent.getActivity(Vars.applicationContext, pendingSelfId, selfStart, PendingIntent.FLAG_CANCEL_CURRENT); 
      manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+Const.RESTART_DELAY, selfStartPending); 

      //hopefully 5 seconds will be enough to get out 
      Utils.quit(); 
      return; 
     } 
     else 
     { //app does not need to restart. still record the accumulated error messages 
      Utils.logcat(Const.LOGE, tag, loge); 
     } 

     //if the network is dead then don't bother 
     if(!Utils.hasInternet()) 
     { 
      Utils.logcat(Const.LOGD, tag, "No internet detected from commnad listener dead"); 
      return; 
     } 

     new LoginAsync(Vars.uname, Vars.passwd).execute(); 
    } 
    else if (action.equals(Const.ALARM_ACTION_RETRY)) 
    { 
     Utils.logcat(Const.LOGD, tag, "login retry received"); 

     //no point of a retry if there is no internet to try on 
     if(!Utils.hasInternet()) 
     { 
      Utils.logcat(Const.LOGD, tag, "no internet for sign in retry"); 
      manager.cancel(Vars.pendingRetries); 
      return; 
     } 

     new LoginAsync(Vars.uname, Vars.passwd).execute(); 

    } 
    else if(action.equals(Const.BROADCAST_LOGIN_BG)) 
    { 
     boolean ok = intent.getBooleanExtra(Const.BROADCAST_LOGIN_RESULT, false); 
     Utils.logcat(Const.LOGD, tag, "got login result of: " + ok); 

     Intent loginResult = new Intent(Const.BROADCAST_LOGIN_FG); 
     loginResult.putExtra(Const.BROADCAST_LOGIN_RESULT, ok); 
     context.sendBroadcast(loginResult); 

     if(!ok) 
     { 
      Utils.setExactWakeup(Const.RETRY_FREQ, Vars.pendingRetries); 
     } 
    } 
} 

}

サーバがその設立に耳を傾けるselectシステムコールでありますソケット。これは、クライアントが最近使用したIPアドレスからサーバーに再接続したときに私がいる問題があり、クライアントのソケットが常にいるようだ、このコードを使用して、新しいソケット(Linux上でC)

  incomingCmd = accept(cmdFD, (struct sockaddr *) &cli_addr, &clilen); 
     if(incomingCmd < 0) 
     { 
      string error = "accept system call error"; 
      postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, DONTKNOW, relatedKey)); 
      perror(error.c_str()); 
      goto skipNewCmd; 
     } 
     string ip = inet_ntoa(cli_addr.sin_addr); 

     //setup ssl connection 
     SSL *connssl = SSL_new(sslcontext); 
     SSL_set_fd(connssl, incomingCmd); 
     returnValue = SSL_accept(connssl); 

     //in case something happened before the incoming connection can be made ssl. 
     if(returnValue <= 0) 
     { 
      string error = "Problem initializing new command tls connection from " + ip; 
      postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, ip, relatedKey)); 
      SSL_shutdown(connssl); 
      SSL_free(connssl); 
      shutdown(incomingCmd, 2); 
      close(incomingCmd); 
     } 
     else 
     { 
      //add the new socket descriptor to the client self balancing tree 
      string message = "new command socket from " + ip; 
      postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, message, SELF, INBOUNDLOG, ip, relatedKey)); 
      clientssl[incomingCmd] = connssl; 
      sdinfo[incomingCmd] = SOCKCMD; 
      failCount[incomingCmd] = 0; 
     } 

を受け入れます創造後に死ぬ。私がもう一度やり直すと、もう一度死ぬ。接続するための唯一の方法は、アンドロイドアプリが強制終了して再起動することです。

例:アドレスが192.168.1.101の自宅の無線LANで。接続OK。アドレス24.157.18.90のLTEに切り替えてください。サーバーに再接続してもよろしいですか。家に帰って192.168.1.101を得る。アプリは自分自身を殺すまでソケットは常に死ぬ。それとも、私が外に出ている間に私が地下鉄に乗るのでLTEが緩んでも、出てきても同じ問題が発生します。新しいソケットが作成されるたびに注意してください。それは何とか古いものを救済しようとはしません。ソケットの作成も成功したようです。クライアントが読んでみたいと思うとすぐに、javaはソケットが閉じていると言います。

趣味のプロジェクトなので、すべての関連するコードをわかりにくいオリジナルの形式にしました。なぜこのようなことが起こるのか、私は考えていません。

+0

'ソケットダイ'を定義します。 – EJP

+0

サーバー側の接続を閉じますか?あなたは接続を閉じますか? –

+0

ソケットはそのままで、selectシステムコールは読み込み準備としてマークしますが、SSL_readを実行すると0を返します。クライアントがログインすると、サーバーは以前に開いたソケットがあるかどうかをチェックし、ソケットとそのopensslオブジェクトのssl_shutdown、ssl_free、free、closeのすべての手順を実行します。 – AAccount

答えて

0
// http://stackoverflow.com/a/34228756 
    //check if server is available first before committing to anything 
    // otherwise this process will stall. host not available trips timeout exception 
    Socket diag = new Socket(); 
    diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT); 
    diag.close(); 

これは、この3つの無意味なコード行によって引き起こされます。サーバーは接続を取得し、即時read()の結果がゼロになります。

接続を確立するためだけに値を設定しないと、接続を閉じて別の接続を開くことはできません。ちょうど確立した接続を使用する必要があります。一般に、リソースが利用可能かどうかを確認する正しい方法は、通常の方法でリソースを使用しようとすることです。上記のような技術は、未来を予測する試みと区別できません。

+0

私はその動作を変更しないでその部分を削除しようとしました。それが悪い場合は私はそれを永久に削除します。これは、初めて使用可能なクライアントサーバープログラムを作成しようとしています。これを作るために大学で何も学んでいませんでした。これのほとんどは独学です。 – AAccount

関連する問題