Search code examples
pythonkotlinsockets

Connect Android app and python script with sockets


I am trying to make a system where a phone connects to a server and sends it data, the data is processed stored and back to the phone. The server also has a VPN to which the phone is connected.

My idea is to use sockets which should work for this kind of situation but I cant make it work. Also, as the tittle says I am using python on the server side and the app is in kotlin.

I started by testing the python sockets with an echo client-server setup and it works, then I tried to make the client in kotlin but it does not connect to the server. The server code is this:

import socket

HOST = "10.8.0.1"
PORT = 65432

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    print("Listening...")
    conn, addr = s.accept()
    with conn:
        print(f"Connected by {addr}")
        while True:
            data = conn.recv(1024)
            print(f"Recived: {data.decode("utf-8")}")
            if not data:
                break
            conn.sendall(data)

The client has a simple interface:

Interface

This is the kotlin code for it:

package com.example.testsockets

import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.textfield.TextInputEditText
import java.net.InetSocketAddress
import java.net.Socket
import java.util.concurrent.Executors


class MainActivity : AppCompatActivity() {
    private val socket = Socket()
    private val executorService = Executors.newSingleThreadExecutor();
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun send(v: View?){
        executorService.execute {
            var entrada = findViewById<TextInputEditText>(R.id.entrada)
            var salida = findViewById<TextView>(R.id.salida)
            val host = "10.8.0.1"
            val port = 65432
            socket.connect(InetSocketAddress(host, port), 3000)
            // This will be used to send to the server
            val out = socket.getOutputStream()
            out.write(entrada.text.toString().toByteArray(charset("UTF-8")))
        }
    }
}

And the AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
     <uses-permission android:name="android.permission.INTERNET"/>
     <application
     android:allowBackup="true"
     android:dataExtractionRules="@xml/data_extraction_rules"
     android:fullBackupContent="@xml/backup_rules"
     android:icon="@mipmap/ic_launcher"
     android:label="@string/app_name"
     android:roundIcon="@mipmap/ic_launcher_round"
     android:supportsRtl="true"
     android:theme="@style/Theme.TestSockets"
     tools:targetApi="31">
     <activity android:name=".MainActivity" android:exported="true">
          <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
  </application>
</manifest>

Finally, the error I get:

FATAL EXCEPTION: pool-1-thread-1
Process: com.example.testsockets, PID: 1897
java.lang.Error: java.net.SocketTimeoutException: failed to connect to /10.8.0.1 (port 65432) from /10.8.0.2 (port 45974) after 3000ms
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1173)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:764)
Caused by: java.net.SocketTimeoutException: failed to connect to /10.8.0.1 (port 65432) from /10.8.0.2 (port 45974) after 3000ms
    at libcore.io.IoBridge.connectErrno(IoBridge.java:185)
    at libcore.io.IoBridge.connect(IoBridge.java:129)
    at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:137)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
    at java.net.Socket.connect(Socket.java:621)
    at com.example.testsockets.MainActivity.send$lambda$0(MainActivity.kt:27)
    at com.example.testsockets.MainActivity.$r8$lambda$qyWxoPew67mcJ-GNbw1keB5dm7k(Unknown Source:0)
    at com.example.testsockets.MainActivity$$ExternalSyntheticLambda0.run(Unknown Source:2)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
    at java.lang.Thread.run(Thread.java:764) 

Solution

  • I managed to make it work, this is the code:

    Python server:

    import socket
    import threading
    import logging
    import struct
    
    HOST = "10.8.0.1"
    PORT = 65432
    # Formato del logging
    logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s')
    
    # Creación del servidor de sockets
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((HOST, PORT))
    server.listen(5)
    # Lista de clientes conectados
    clientes = []
    
    
    def client(cliente_socket):
        try:
            while True:
                #Receive the size of the data that is going to be sent
                tam_datos_bytes = cliente_socket.recv(1024)
                if not tam_datos_bytes:
                    break
                
                tam_datos = int(tam_datos_bytes.decode("utf-8"))
                #Receive the data
                data = b''
                while len(data) < tam_datos:
                    packet = cliente_socket.recv(tam_datos - len(data))
                    if not packet:
                        break
                    data += packet
                if len(data)<100:
                    if not data or data.decode("utf-8") == "EOC":
                        break
                #Send the response
                response = "Received"
                tam_response = len(response)
                cliente_socket.sendall(struct.pack('!I', tam_response))  # Envía el tamaño del mensaje como un entero de 4 bytes
                cliente_socket.sendall(response)
        except Exception as e:
            logging.error(f"Error receiving data: {e}")
        finally:
            # Close socket and remove it
            cliente_socket.close()
            if cliente_socket in clientes:
                clientes.remove(cliente_socket)
    # Listen for conecctions
    while True:
        # Accept Conection
        cliente_socket, address = server.accept()
        logging.info(f"Conectado el cliente {address}")
    
        # Add client to list
        clientes.append(cliente_socket)
    
        # Create a thread to manage the client
        hilo_cliente = threading.Thread(target=client, args=(cliente_socket,))
        hilo_cliente.start()
    

    Kotlin client:

    class Sockets {
        private  var text: TextView
        constructor(texto: TextView,message: String){
            text = texto
            sendMessage(message)
        }
        fun sendMessage(message: String) = CoroutineScope(Dispatchers.IO).launch {
            try {
                // Create the connection
                val socketChannel = SocketChannel.open()
                socketChannel.connect(InetSocketAddress("10.8.0.1", 65432))
    
                // Send the message length
                val tamMensaje = message.length
                val tamMensajeBuffer = ByteBuffer.wrap(tamMensaje.toString().toByteArray()) // 4 bytes para el tamaño (int)
                socketChannel.write(tamMensajeBuffer)
    
                // send the message
                val mensajeBuffer = ByteBuffer.wrap(message.toByteArray())
                while (mensajeBuffer.hasRemaining()) {
                    socketChannel.write(mensajeBuffer)
                }
                // Wait for a response and write it on a TextView
                val respuesta = recibirMensaje(socketChannel)
                withContext(Dispatchers.Main) {
                    if (respuesta != null) {
                        text.text = respuesta
                    }
                }
                // Close socket
                socketChannel.close()
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    e.printStackTrace()
                }
            }
        }
        private fun recibirMensaje(socketChannel: SocketChannel): String? {
            return try {
                // Recive the message from the server
                val tamMensajeBuffer = ByteBuffer.allocate(4)
                while (tamMensajeBuffer.hasRemaining()) {
                    if (socketChannel.read(tamMensajeBuffer) <= 0) {
                        return null
                    }
                }
                tamMensajeBuffer.flip()
                val tamMensaje = tamMensajeBuffer.int
    
                // Recive the data
                val dataBuffer = ByteBuffer.allocate(tamMensaje)
                while (dataBuffer.hasRemaining()) {
                    if (socketChannel.read(dataBuffer) <= 0) {
                        return null
                    }
                }
                dataBuffer.flip()
                var respuesta = String(dataBuffer.array(),0,dataBuffer.array().size)
                respuesta
            } catch (e: Exception) {
                Log.d("conexion",e.printStackTrace().toString())
                null
            }
        }
    }
    

    With that we have a python server that allows multiple connections and multiple message sizes and a kotlin client to connect to it, send data and receive it.