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:
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)
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.