Kommunikation App - Server per Socket

  • Antworten:4
Wicki11
  • Forum-Beiträge: 103

23.06.2017, 12:14:06 via Website

Hallo !

Ich programmiere eine App, die als Client mit einem Server unter Verwendung von
Sockets kommunizieren soll.
Der Server ist eine reine Java-Anwendung, die mittels 'java ServerC' gestartet
wird. Zum Test habe ich das Smartphone per USB an den Laptop gebunden, der in
meinem lokalen Netzwerk ist und den Server in einer DOS-Box enthält.
Die App erzeugt automatisch Klicks auf Bilder der Camera und sendet diese dann
an den Server, der sie im aktuellen Verzeichnis abspeichert. Das funktioniert
zunächst. Nun will ich vom Server eine Rückmeldung an den Client schicken, wenn
das jeweilige Bild gespeichert wurde. Dabei bricht der Client (App) mit
"Socket closed" ab, wenn die Rückmeldung gelesen werden soll.
Wie kann das korrigiert werden ?
Client-Code:

package de.carpelibrum.camera;

import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class CameraActivityC extends Activity implements
        SurfaceHolder.Callback ,
        Camera.PictureCallback {
    private final String TAG = "carpelibrum";
    private Camera camera;
    private Camera.PictureCallback cameraCallbackVorschau;
    private Camera.ShutterCallback cameraCallbackVerschluss;
    private SurfaceHolder cameraViewHolder;
    SurfaceView cameraView;
    EditText ed;
    Timer t;
    String params;
    int beg = 1000; //begin-interval
    int repeat = 5000; //repeat-interval
    boolean edOkay = false;
    Socket clientSocket;
    boolean rueckMeld=true; //Rückmeldung vom Server
    boolean nextImg=true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //getWindow().setFormat(PixelFormat.TRANSLUCENT);
        setContentView(R.layout.main);
        ed = (EditText)findViewById(R.id.textView1);
        /*
        ed.setOnKeyListener(new View.OnKeyListener() {
          public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
              switch (keyCode) {
                case KeyEvent.KEYCODE_ENTER:
                  hideKeyboard(CameraActivityC.this); //remove softkeyboard
                  String sp=ed.getText().toString().substring(8);
                  String ca[]=sp.split(",");
                  beg = Integer.parseInt(ca[0]); //begin-interval
                  repeat = Integer.parseInt(ca[1]); //repeat-interval
                  Log.d("****** Parameter:",sp);
                  ed.setVisibility(View.GONE); //remove EditText
                  edOkay = true;
                  t = new Timer();
                  //automatisch wiederholter Start von ObtainMotionEvent
                  t.schedule(new Zeitgeber(),beg,repeat);//1000,5*1000);
                  return true;
                default:
                  break;
              }
            }
            return false;
          }
        });
        */
        test(); //vereinfachter Test
    }
    void test() {
        ed.setVisibility(View.GONE); //remove EditText
        edOkay = true;
        t = new Timer();
        //automatisch wiederholter Start von ObtainMotionEvent
        t.schedule(new Zeitgeber(),1000,5*1000);//1000,5*1000);
    }
    // Interface 1 SurfaceHolder.Callback
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                             int height) {
        camera.stopPreview();
        Camera.Parameters params = camera.getParameters();
        params.setPreviewSize(width, height);
        camera.setParameters(params);
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            Log.d(TAG, "surF: "+e.getMessage());
        }
        camera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
    //Ende Interface 1

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!edOkay) return true; //Bestätigung per EditText fehlt
        if(event.getAction() == MotionEvent.ACTION_UP) {
            camera.takePicture(this.cameraCallbackVerschluss, this.cameraCallbackVorschau
                    , this); //onPictureTaken
            return true;
        }
        else {
            return super.onTouchEvent(event);
        }
    }

    // Interface 2 Camera.PictureCallback
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        try {
            //Name oder IP-Adresse des Servers
            clientSocket = new Socket("acer_laptop",45678); //Server und Port
            Log.i("OnPic_client1",clientSocket.toString());
            OutputStream out = new BufferedOutputStream(clientSocket.getOutputStream());
            //für Rückmeldung vom Server
            InputStream in = new BufferedInputStream(clientSocket.getInputStream());

            byte[] b2=new byte[6];
            SimpleDateFormat df = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss");
            //Teil1 durch Nutzernamen ersetzen, der auch als Unterverzeichnis auf dem Server dient
            String fname = "foto_" + df.format(new Date())+".jpg";
            out.write(fname.length());
            out.write(fname.getBytes());
            out.write(data);
            out.flush();
            out.close();
            nextImg=false;
            //*** Rückmeldung vom Server
            if (rueckMeld) {
                Log.i("OnPic_client2 closed:", "" + clientSocket.isClosed());
                Log.i("OnPic_client3",clientSocket.toString());
                //clientSocket ist nicht geschlossen !
                //Versuch Rückmeldung vom Server zu lesen
                int rc = in.read(b2); //hier kommt der Fehler "Socket closed"
                Log.i(TAG, "Meldung vom Server:" + rc);
                nextImg=true;
            }
            nextImg=false;
            camera.startPreview();
        } catch (Exception ex) {
            Log.e("Ex_OnPicclient4:",ex.getMessage()); //socket closed
            //aber nicht clientSocket !
            Log.e("Ex_OnPic_client5:",clientSocket.isClosed()+","+clientSocket.toString());
            ex.printStackTrace();
            t.cancel();
            finish();
        }
    }
    //Ende Interface 2

    @Override
    protected void onPause() {
    super.onPause();
        if(camera != null) {
        //  camera.stopPreview();
            camera.release();
            Log.d(TAG,"Pause !");
            if (t != null) t.cancel(); //TimerTask beenden
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        cameraView = (SurfaceView) findViewById(R.id.surfaceview1);
        cameraViewHolder = cameraView.getHolder();
        cameraViewHolder.addCallback(this); //Interface 1
        cameraViewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        cameraCallbackVorschau = new Camera.PictureCallback() {
            public void onPictureTaken(byte[] data, Camera c) {
            }
        };
        cameraCallbackVerschluss = new Camera.ShutterCallback() {
            public void onShutter() {
            }
        };
    }
    //erzeuge ein MotionEvent
    void ObtainMotionEvent(SurfaceView sv) {
        if (false) { //nicht genutzt
        //  if (clientSocket != null) {
                boolean tc = clientSocket.isClosed();
                Log.e("***Obt_client closed:", "" + tc);
                if (!tc) { //server arbeitet noch am vorherigen Bild
                    return;
                }
            }
            long downTime = SystemClock.uptimeMillis();
            long eventTime = SystemClock.uptimeMillis() + 100;
            float x = 50.0f;
            float y = 100.0f;
            // List of meta states found here: developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
            int metaState = 0;  //key_down
            MotionEvent motionEvent = MotionEvent.obtain(
                    downTime,
                    eventTime,
                    MotionEvent.ACTION_UP,
                    x,
                    y,
                    metaState
            );
            sv.dispatchTouchEvent(motionEvent); //evtl. nicht notwendig
            onTouchEvent(motionEvent); //motionEvent senden
        }

        //zyklische Erzeugung und Auswertung eines Touch-Events
        class Zeitgeber extends TimerTask {
            public void run() {
                try {
                    ObtainMotionEvent(cameraView);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        //Soft-Keyboard für EditText entfernen nach KEYCODE_ENTER
        public static void hideKeyboard(Activity activity) {
            InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
            //Find the currently focused view, so we can grab the correct window token from it.
            View view = activity.getCurrentFocus();
            //If no view currently has focus, create a new one, just so we can grab a window token from it
            if (view == null) {
                view = new View(activity);
            }
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    }
}

Servercode:

import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DecimalFormat;

public class ServerC {
//  static int drl2 = 0;
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(45678);
        int clients = 0;
        System.out.println("... Server gestartet, warte auf Client-Verbindungen");
        while (true) {
          ServerThreadC serverThread = new ServerThreadC(serverSocket.accept(),++clients);
          serverThread.start();
          System.out.println("... warte auf weitere Client-Verbindungen");
        }
    }
}
class ServerThreadC extends Thread {
    static int drl2=0;
    Socket clientSocket;
    InputStream in;
    PrintStream os;
    OutputStream ots;
    int nr;
    boolean rueckMeld=true;
    //  int drl2 = 0;
    byte[] b = new byte[4096];
    public ServerThreadC(Socket clientSocket, int nr) throws IOException {
        this.clientSocket = clientSocket;
        System.out.println("OnPic_client:"+clientSocket.toString());
        this.nr = nr;
        in = new BufferedInputStream(clientSocket.getInputStream());
        ots = clientSocket.getOutputStream();
    //  os = new PrintStream(clientSocket.getOutputStream());

        System.out.println("... Client angemeldet:"+clientSocket.getInetAddress());
    }

    public void run() {
        FileOutputStream out=null;
        byte[] b=new byte[4096];
        String fname="";
        int drl = 0;
        int len;
        long start = System.currentTimeMillis();
        try {
            while ((len=in.read(b))>0) {
                if (drl++ == 0) { //Block 1:Laenge|Dateiname|Daten
                    int le = b[0];
                    byte[] tf=new byte[le];
                    System.arraycopy(b,1,tf,0,le);
                    fname = new String(tf); //Dateiname
                    out = new FileOutputStream(fname);
                    out.write(b,le+1,len-le-1); //Datenteil ausgeben
            /*
                if (++drl2 < 2) {
                    System.out.println("...vor sleep");
                    ServerThread.sleep(10000);
                    System.out.println("...nach sleep");
                }
            */
                continue;
                }
                out.write(b,0,len);
            }
            out.flush();
            out.close();
            System.out.println("1clientSocket closed???"+clientSocket.isClosed());
            if (rueckMeld) {
                //  os.println("Ausgabe beendet:" + fname); //*** Ausgabe zum Client
                ots.write(1);
                System.out.println("Ausgabe beendet:"+fname);
                System.out.println("1clientSocket closed???"+clientSocket.isClosed());
            } else {
                //  clientSocket.close();
                System.out.println("ohne Rueck Ausgabe beendet:"+fname);
                System.out.println("2clientSocket closed???"+clientSocket.isClosed());
            }
            //  OutputStream ots = clientSocket.getOutputStream();
            //  ots.write(1);
            double d = (System.currentTimeMillis()-start)/1000.0;
            DecimalFormat df = new DecimalFormat("##.##");
            //  System.out.println("clientSocket closed:"+df.format(d));
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

Edit by Mod Ludy: Code lesbarer gestaltet

— geändert am 23.06.2017, 18:47:40 durch Moderator

Antworten
swa00
  • Forum-Beiträge: 3.704

23.06.2017, 13:13:04 via Website

Hallo Wiki11,

Dein Code ist sehr schlecht zu lesen und auch nicht so gut nachzuvollziehen .

Ich kürze es ab : welche Exception bekommst du denn als Grund angegeben , wenn der Socket geschlossen wurde ..
I.d.R. liegt es daran , dass der Server den Socket zu macht, wenn er das von Accepted nicht mehr als länger gültig ansieht.

Warum der Umweg über Laptop ? Du kannst doch direkt ein Connect auf den Server innerhalb des LAN mit
einer festen IP machen ..

P.S ich konnte auf Anhieb auch kein Handshakes in deiner Mimik erkennen - Hat das einen Grund ?

— geändert am 23.06.2017, 13:15:31

Liebe Grüße - Stefan
[ App - Entwicklung ]

Antworten
Wicki11
  • Forum-Beiträge: 103

23.06.2017, 13:39:15 via Website

Ich frage mich auch, warum der Code so schlecht zu lesen ist, obwohl ich "Quellcode einfügen" gedrückt habe.
Der Laptop ist mein Server, Smartphone (mit der App) und Laptop sind im gleichen Netzwerk.
Die Exception lautet "Socket closed" und kommt bei der read-Anweisung in der App:

InputStream in = new BufferedInputStream(clientSocket.getInputStream());
...
//Versuch Rückmeldung vom Server zu lesen
//an dieser Stelle: Test ob clientSocket geschlossen ist: nicht geschlossen
int rc = in.read(b2); //hier kommt der Fehler "Socket closed"

Meinst Du mit Handshake nen Touch auf das Camera-Bild ?
Da wird per Timer die Funktion ObtainMotionEvent aufgerufen, die einen TouchEvent programmatisch erzeugt.

Zum Nachvollziehen hier noch AndroidManifest und main.xml:

xmlns:android="http://schemas.android.com/apk/res/android"
package="de.carpelibrum.camera"
android:versionCode="1"
android:versionName="1.0">
android:minSdkVersion="8" android:targetSdkVersion="8"/>
android:name="android.permission.CAMERA">
android:name="android.permission.INTERNET">
android:name="android.permission.CAMERA">
android:name="android.hardware.camera"
android:required="true" />

<application
    android:icon="@drawable/icon"
    android:label="@string/app_name">
    <activity
        android:name=".CameraActivityC"
        android:label="@string/app_name"
        android:screenOrientation="portrait">
        <intent-filter>
            <action
                android:name="android.intent.action.MAIN" />
            <category
                android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

</application>


xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:layout_height="wrap_content"
android:id="@+id/linearLayout1"
android:layout_width="match_parent">
android:text="beg,rep:1000,5000"
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<SurfaceView
    android:id="@+id/surfaceview1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
</SurfaceView>

Gruß Wicki

— geändert am 23.06.2017, 13:49:15

Antworten
swa00
  • Forum-Beiträge: 3.704

23.06.2017, 14:01:07 via Website

Dein Fehler ist natürlich keine eindeutige Fehlerursache im Quellcode, deshalb muss man hier ein wenig
nach dem Ausschlussverfahren vorgehen.

Ich würde als Erstes zwei dinge tun :

Der Laptop ist mein Server, Smartphone (mit der App) und Laptop sind im gleichen Netzwerk.
Die Exception lautet "Socket closed" und kommt bei der read-Anweisung in der App:

a) Dein Aufbau ist sehr ungewöhnlich :
Stöpsel das Ding mal ab, binde es in WLAN mit fester IP ein und und schau mal was dann passiert.
(Ist der Laptop auch der Rechner worauf AS läuft ??? )

b) Um sicher zu sein , nimm alles aus dem Thread heraus und schiebe nur mal ein paar Bytes durch die Gegend.
Es kann gut sein , dass der Socket sich durch die verschiedenen Threads verabschiedet (z.b. UI-Thread)

d) Mit Handshake meine ich das eigentlich rudimentäre (und auch wichtige) Response .
(Du schickst was, der server antwortet - und umgekehrt)

Liebe Grüße - Stefan
[ App - Entwicklung ]

Antworten
Wicki11
  • Forum-Beiträge: 103

23.06.2017, 15:14:36 via Website

Dank für Deine Mühe !
Auf dem Laptop läuft AS, habe Smartphone vom Laptop abgestöpselt, Ergebnis:
das Senden des 1. Bildes ist erfolgreich und wird vom Server (Laptop) richtig gespeichert, d.h. App und Server haben Verbindung zueinander.
Der Server sendet auch seine Antwort, aber beim Lesen im Smartphone kommt die Exception.
Wenn ich das Antwortschicken weglasse, werden weitere Bilder problemlos gesendet und vom Server gespeichert.

Hier mal das Serverprotokoll:
C:\Users\Ad\AndroidStudioProjects\CameraActivity\app\src\main\java>java ServerC
... Server gestartet, warte auf Client-Verbindungen
OnPic_client:Socket[addr=/192.168.2.103,port=38292,localport=45678]
... Client angemeldet:/192.168.2.103
... warte auf weitere Client-Verbindungen
1clientSocket closed???false
Ausgabe beendet:foto_2017_06_23_03_02_07.jpg
1clientSocket closed???false

Kann es sein dass der Server einen anderen Port benutzt als den zwischen beiden ursprünglich vereinbarten ?

Gruß Wicki

Antworten