Подключите приложение Android и скрипт Python с помощью сокетов

Я пытаюсь создать систему, в которой телефон подключается к серверу и отправляет ему данные, данные обрабатываются, сохраняются и возвращаются на телефон. На сервере также есть VPN, к которому подключен телефон.

Моя идея состоит в том, чтобы использовать сокеты, которые должны работать в такой ситуации, но я не могу заставить их работать. Кроме того, как сказано в заголовке, я использую Python на стороне сервера, а приложение находится на Kotlin.

Я начал с тестирования сокетов Python с настройкой эхо-клиент-сервер, и это работает, затем я попытался создать клиент на Kotlin, но он не подключается к серверу. Код сервера такой:

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)

Клиент имеет простой интерфейс:

Это код Котлина для этого:

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")))
        }
    }
}

И 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>

Наконец, я получаю сообщение об ошибке:

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) 

Вам следует проверить конфигурацию брандмауэра на сервере. Вы можете попробовать использовать telnet-клиент на устройстве Android или просто веб-браузер, посетив http://10.8.0.1:65432/, чтобы посмотреть, отреагирует ли сервер. Вы также можете попробовать установить (временно) 0.0.0.0 как HOST для сервера.

Michael Butscher 17.06.2024 12:10

какая система у тебя на компьютере? В некоторых системах брандмауэр может блокировать все порты, в других он может держать открытым 80 для собственного www-сервера (так что вы можете использовать его без изменений в брандмауэре), но в Linux порт 80 блокируется системой (все порты <1024 блокируются системой). ). Другой вопрос: если у вас на компьютере много сетевых устройств — Wi-Fi, проводная локальная сеть и т. д., потому что каждое устройство может иметь собственный IP-адрес, и если вы используете IP-адрес, назначенный проводной локальной сети, вы не сможете подключиться с помощью wifi — но если сервер использует 0.0.0.0 затем использовать все устройства для приема соединений.

furas 17.06.2024 14:00

Создание соединения с помощью сокета может быть интересной задачей, но проще и полезнее может быть www server с HTTP protocol. Вы можете использовать его с приложением на Android (и отправлять данные только в формате JSON без HTML), а также проверять его в браузере (и создавать веб-страницу с HTML) или использовать такие программы, как postman, для проверки соединения.

furas 17.06.2024 14:02

Мне интересно, проблема в VPN. Сокет Python и VPN - Qaru QaruSite

furas 17.06.2024 14:10

на сервере работает Windows Server 2022, и порт разрешен в брандмауэре. Я протестировал его, запустив клиент Python на другом компьютере, и он подключается и позволяет мне отправлять и получать данные. Этот компьютер подключается из той же сети, что и телефон, и использует ту же VPN, поэтому я не думаю, что это проблема брандмауэра или VPN, поэтому я очень расстроен, это не имеет особого смысла. Спасибо за советы, я их все проверил, вдруг что-то пропустил. И 0.0.0.0 тоже не заработала.

Alejandro 17.06.2024 18:43

Я могу попробовать решение, предложенное @furas, если не смогу найти другое решение, но сейчас мне просто любопытно узнать, почему оно не работает.

Alejandro 17.06.2024 18:44

Я нашел способ заставить его работать, но он еще не идеален, поэтому он не попадает в раздел ответов. Я использовал Chaquopy, чтобы создать сокет Python на устройстве Android и отправить с его помощью данные. Проблема в том, что я пока не знаю, как получить данные.

Alejandro 18.06.2024 12:37
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
7
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мне удалось заставить это работать, вот код:

Python-сервер:

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()

Котлин-клиент:

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
        }
    }
}

При этом у нас есть сервер Python, который позволяет использовать несколько соединений и сообщения разных размеров, а также клиент Kotlin для подключения к нему, отправки данных и их получения.

Другие вопросы по теме