Frage zu UDP-Socket-Programmierung

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
antaeus
User
Beiträge: 48
Registriert: Dienstag 19. September 2006, 10:10

Hallo zusammen

Ich habe eine Frage zur Client / Server Programmierung mit UDP Sockets.

In TCP ist es ja so, dass ich vom Serversocket (SS) nach accept() einen Socketdeskriptor für einen Clientsocket (CS) erhalten. Das ist völlig problemlos und ich kann senden und empfangen wie es mir beliebt.

Bei UDP ist alles ein wenig anderst, da ich ein verbindungsloses Protokoll habe. Es gibt weder listen noch accept; Dafür gibt es recvfrom, was mir Daten und Absenderadresse/Port liefert. Für mich stellt sich jetzt die Frage: wie kriege ich eine Antwort vom Server zurück zum Client gesendet?

Idee 1: Ich verwende den SS und connecte den zur Zieladresse meiner Antwort-Daten, also etwa so:

Code: Alles auswählen

serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serversocket.bind(("192.168.0.21", 80))

(data, addr) = serversocket.recvfrom(1024)
serversocket.connect(addr)
serversocket.send("Echo: " + data)
Das funktioniert. Danach funktioniert aber der SS nicht mehr und ich muss diesen erst schließen und einen neuen SS erzeugen, etwa so:

Code: Alles auswählen

def loop(self):
			
	while True:
	
		serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		serversocket.bind(("192.168.0.21", 80))

		(data, addr) = serversocket.recvfrom(1024)
		
		if not data:
			pass
		else:
			print data
			serversocket.connect(addr)
			serversocket.send("Echo: " + data)
			serversocket.close()
Ich finde das ziemlich unschön, da ich so z.B. nicht einen Thread für die Bearbeitung der Anfrage erzeugen kann, aber wie gesagt: es funktioniert wenigstens. Ein Trace im Wireshark sieht etwa so aus:

Client --> Server: Source Port: x, Destination Port: 80
Server --> Client: Source Port: 80, Destination Port: x

Idee 2: Ich erstelle pro Reqeust einen neuen CS (lasse den SS also unangetastet) und schicke über den CS die Antwort an den Client (siehe loop2()), also etwa so:

Code: Alles auswählen

def loop2(self):
	
	serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	serversocket.bind(("192.168.0.21", self.port))
	
	while True:
			   
		(data, addr) = serversocket.recvfrom(1024)
		
		if not data:
			pass
		else:
			clientsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
			clientsocket.connect(addr)
			clientsocket.send(data)
			clientsocket.close()
Das funktioniert so aber nicht... Etwas Tracen mit Wireshark zeigt den Grund:

Client --> Server: Source Port: x, Destination Port: 80
Server --> Client: Source Port: y, Destination Port: x

Der Unterschied zu Version 1 ist, dass der neue neue CS des Servers nicht mehr von Port 80 sendet, sondern von *irgendeinem* Port y. Der Client scheint aber fest davon auszugehen, dass Port 80 verwendet wird. Jetzt habe ich aber ein Problem, weil ich den CS des Servers nicht an Port 80 binden kann, weil dort ja schon der SS gebunden ist.

Bleibt also nur
Idee 3: Sowohl Server als auch Client verfügen über jeweils einen CS und SS. Der Client sendet an den SS (Port 80) des Servers den Request; der Server sendet seine Response an den SS (z.B. Port 81) des Clients. Das erscheint mir allerdings auch etwas hahnebüchen :D

Meine Frage ist also ganz konkret, wie sieht "saubere" Client / Server Programmierung mit UDP aus? Die ganzen Tutorials, die ich bisher im Web gefunden habe, zielen alle auf TCP - nutzen mir also nichts. Ich wäre euch sehr dankbar für Hilfe. Vielleicht mache ich oben auch irgendetwas falsch...!?

A.
Gromit
User
Beiträge: 51
Registriert: Montag 8. Mai 2006, 19:07

Der letzte Ansatz mit 2 Socket-Instanzen ist der richtige. Man muß nur die Paketerl mit sendto verschicken.

HTH,
Gerald
antaeus
User
Beiträge: 48
Registriert: Dienstag 19. September 2006, 10:10

Ok, danke schon mal für die Auskunft. Mir erscheint das allerdings wirklich hahnebüchen ;-)

In meinem Beispiel (Echo Server / Client) würde das ja folgendes bedeuten

Der Server wartet auf Port 80 UDP auf eingehende Verbindungen und schickt diese über einen Client Socket an IP/Host des Absenders

Der Client sendet eine Nachricht von Port 81 UDP an Port 80 UDP des Servers und macht nach dem Senden einen Serversocket auf Port 81 UDP auf und wartet auf Antwort.

Server:

Code: Alles auswählen

import socket

class echoserver(object):

    def __init__(self, host, port):
        self.bufsize = 1024
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.serversocket.bind((host, port))
        print "s_srv: init"
        
    def loop(self):
        print "s_srv: 1st loop"
        
        while True:
            (data, addr) = self.serversocket.recvfrom(self.bufsize)
            print data
            
            if not data:
                print "s_srv: no data"
            else:
                c = client()
                c.send("echo: " + data, addr)
                print "s_srv: data sent"

class client(object):
    
    def __init__(self):
        self.clientsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        print "s_cl: init"
        
    def send(self, data, dest):
        self.clientsocket.sendto(data, dest)
        self.clientsocket.close()
        print "s_cl: data sent"
        
if __name__ == "__main__":
    s = echoserver("192.168.0.21", 80)
    s.loop()
und der entsprechende Client so:

Code: Alles auswählen

import socket

class echoclient(object):
    
    def __init__(self, desthost, destport, recvhost, recvport):
        self.desthost = desthost
        self.destport = destport
        self.recvhost = recvhost
        self.recvport = recvport
        
        self.clientsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.clientsocket.bind((self.recvhost, self.recvport))
        
        print "ec_cl: init"
        
    def send(self, data):
        self.clientsocket.sendto(data, (self.desthost, self.destport))
        self.clientsocket.close()
        print "ec_cl: sent"
        
        s = server(self.recvhost, self.recvport)
        s.receive()

class server(object):

    def __init__(self, host, port):
        self.bufsize = 1024
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.serversocket.bind((host, port))
        print "ec_srv: init"
        
    def receive(self):
        
        print "ec_srv: receive"
        (data, addr) = self.serversocket.recvfrom(self.bufsize)
        print data


        
if __name__ == "__main__":
    c = echoclient("192.168.0.21", 80, "192.168.0.31", 81)
    c.send("Hallo")
Das kannes doch wohl nicht sein!?
BlackJack

Alles reichlich kompliziert was Du da mit den Klassen anstellst. Hier mal Client und Server in einem Programm, nur mit jeweils einer einfachen Funktion:

Code: Alles auswählen

import socket
import sys

def server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server_socket.bind(('localhost', 10000))
    while True:
        data, sender_address = server_socket.recvfrom(1024)
        print data
        if data:
            server_socket.sendto(data, sender_address)


def client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client_socket.sendto('Hallo Echo', ('localhost', 10000))
    echo_data = client_socket.recv(1024)
    print echo_data
    client_socket.close()


def main():
    if sys.argv[1] == 'server':
        server()
    else:
        client()
Du bekommst doch die Absenderadresse von `recvfrom()`, an die kannst Du auch etwas zurückschicken.
antaeus
User
Beiträge: 48
Registriert: Dienstag 19. September 2006, 10:10

Jepp, vielen Dank... Das ist so viel netter! Mein Fehler war einfach immer, dass ich connect benutzt habe. (Was ja völliger Blödsinn ist, wenn man es sich mal überlegt...) Sendto kannte ich halt nicht :roll:
Antworten