Проблема синхронизации сокетов приложений Python и Kotlin

#python #kotlin #synchronization #client-server

Вопрос:

Я пишу приложение на Kotlin, которое использует серверный сервер Python, и, похоже, у меня возникли некоторые проблемы с их соединением.

На python я использую

 from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
def handle_connection(c):
    amount = int(c.recv(2048).decode("utf8"))
    for i in range(amount):
        print(c.recv(2048).decode("utf8"))
class FileReceiver:
    def __init__(self):
        self.socket = socket(AF_INET, SOCK_STREAM)

    def run(self):
        try:
            self.socket.bind(('0.0.0.0', 7557))
            self.socket.listen()
            while True:
                c, a = self.socket.accept()
                thr = Thread(target=handle_connection, args=(c,))
                thr.run()
        except KeyboardInterrupt:
            self.socket.detach()
            self.socket.close()

fr = FileReceiver()
fr.run()
 

для получения данных от клиента.
И на Котлине я использую

 val socket = Socket(server_ip, server_port)
val input = BufferedReader(InputStreamReader(socket.getInputStream()))
val out = PrintWriter(socket.getOutputStream())
out.write("10")
out.flush()
for (i in 1..10)
{
    out.write("A")
    out.flush()
}
 

чтобы отправить данные.
Использование этого кода приводит к тому, что сервер получает только одно сообщение со всеми буквами «А», но я пытаюсь заставить его получать их все в отдельных сообщениях в цикле.
Я не лучший программист, поэтому буду признателен за любую помощь в отношении этого кода!
Заранее благодарю вас!

Ответ №1:

Как SOCK_STREAM и следовало ожидать, на самом деле вы отправляете/получаете здесь не сообщения, а поток байтов. Протокол TCP, который вы используете здесь, не понимает сообщения. Отправка "AAAAA" один раз и отправка "A" пять раз-это почти одно и то же.

Если вам нужно отправлять сообщения и получать их в том виде, в каком они были отправлены, вы должны ввести какой-то протокол поверх TCP. Этот протокол будет кодировать сообщения с одной стороны и декодировать их на принимающей стороне.

Самый простой способ реализовать это вручную-отправить, например, 2 байта с размером сообщения, а затем само сообщение. На принимающей стороне вы сначала считываете 2 байта, а затем считываете необходимое количество байтов для декодирования полного сообщения. Это может быть реализовано следующим образом:

Котлин/клиент

 fun main() {
    ...

    val out = DataOutputStream(socket.getOutputStream())
    out.writeMsg("10".toByteArray())
    for (i in 1..10)
    {
        out.writeMsg("A".toByteArray())
    }
}

fun DataOutputStream.writeMsg(msg: ByteArray) {
    require(msg.size.toUShort() <= UShort.MAX_VALUE)
    writeShort(msg.size)
    write(msg)
}
 

Python/сервер

 def recvall(sock, size):
    result = bytearray()
    while len(result) < size:
        result.extend(sock.recv(size - len(result)))
    return result

def recvmsg(sock):
    size = struct.unpack('>H', recvall(sock, 2))[0]
    return recvall(sock, size)
    

def handle_connection(c):
    amount = int(recvmsg(c).decode("utf8"))
    for i in range(amount):
        print(recvmsg(c).decode("utf8"))
 

Обратите внимание, что этот код, вероятно, не готов к производству. Его следует протестировать на наличие ошибочных сценариев, таких как отключение сети и т.д.

Вы также можете попробовать использовать существующие протоколы/библиотеки, которые обеспечивают сеть на основе сообщений. Одной из таких библиотек является ZeroMQ — она многоязычна, относительно проста и удобна в использовании. Однако имейте в виду, что он действительно основан исключительно на сообщениях, поэтому иногда его использование может показаться странным. Например, он работает без подключения, он скрывает сетевые подключения от своего пользователя. Что хорошо, потому что нам не нужно принимать соединения, оно автоматически подключается, мы можем connect это сделать до bind / listen и т. Д. С другой стороны, он не предоставляет нам информацию о подключенных клиентах, не уведомляет нас об отключениях и так далее.

Комментарии:

1. Большое спасибо! Но не могли бы вы предложить библиотеку, которую я мог бы использовать как для Kotlin, так и для Python?

2. Я обновил свой ответ, чтобы привести несколько примеров. И да, вы можете использовать ZeroMQ как в Python, так и в Kotlin. Помните, что вы можете использовать любую библиотеку Java в Kotlin. Я предлагаю выбрать реализацию JeroMQ, если вы заинтересованы. Однако… если подумать, ZeroMQ может быть не лучшей идеей. Это действительно здорово и делает работу в сети очень простой, но, с другой стороны, она вводит несколько концепций, которые могут показаться странными и которые необходимо изучить. Это может быть не очень хорошо в качестве отправной точки, может быть лучше сначала понять саму сеть, а затем перейти к таким фреймворкам, как ZeroMQ.