Forum: Mikrocontroller und Digitale Elektronik werbserver uip easyweb datenaustausch ethernet arm


von leluno (Gast)


Lesenswert?

Ich möchte mit dem LPC1768-DK2-board Daten per Ethernet austauschen. Ide 
ist codeRed. Die Beispielcodes easyweb und uip laufen. Erste Frage: Was 
ist besser geeignet, Easyweb oder uip?

Da uip zuerst funktioniert hat, habe ich mich vorläufig für uip 
entschieden.  das ganze läuft mit Proto_Threads. Das ist so kompliziert, 
dass ich da nicht wirklich einsteigen möchte/kann. Möglicherweise muss 
man das aber auch nicht, um uip benutzen zu können. Grundlegende 
Funktion scheint uip.c/uip_process() zu sein. Im Kopfteil heißt es:
1
/*
2
* uIP is a small implementation of the IP, UDP and TCP protocols (as
3
 * well as some basic ICMP stuff). The implementation couples the IP,
4
 * UDP, TCP and the application layers very tightly. To keep the size
5
 * of the compiled code down, this code frequently uses the goto
6
 * statement. While it would be possible to break the uip_process()
7
 * function into many smaller functions, this would increase the code
8
 * size because of the overhead of parameter passing and the fact that
9
 * the optimier would not be as efficient.
10
 *
11
 * The principle is that we have a small buffer, called the uip_buf,
12
 * in which the device driver puts an incoming packet. The TCP/IP
13
 * stack parses the headers in the packet, and calls the
14
 * application. If the remote host has sent data to the application,
15
 * this data is present in the uip_buf and the application read the
16
 * data from there. It is up to the application to put this data into
17
 * a byte stream if needed. The application will not be fed with data
18
 * that is out of sequence.
19
 *
20
 * If the application whishes to send data to the peer, it should put
21
 * its data into the uip_buf. The uip_appdata pointer points to the
22
 * first available byte. The TCP/IP stack will calculate the
23
 * checksums, and fill in the necessary header fields and finally send
24
 * the packet back to the peer.
25
*/

Kernproblem für den Datenaustausch ist daher wohl der Zugriff auf 
uip_buf. Zum Senden von Daten müssen Daten in den uip_buf gestellt 
werden. Hat jemand dafür schon fertige Funktionen?

Für Anregungen zum Problem Datenaustausch per Ethernet wäre ich äüßerst 
dankbar.

von Karl K. (leluno)


Angehängte Dateien:

Lesenswert?

vielen Dank für die breite Unterstützung.
Wie man sieht, funktioniert der TCP-Empfang mit diesem Code:
1
   /* receive packet and put in uip_buf */
2
    uip_len = tapdev_read(uip_buf);
3
      if(uip_len > 0)    /* received packet */
4
      { 
5
          if(BUF->type == htons(UIP_ETHTYPE_IP))  /* IP packet */
6
          {
7
            //uip_arp_ipin();//obsolet
8
9
            uip_process(UIP_DATA);//==//uip_input();
10
        memcpy(kktest,uip_buf,50);
11
        kktest[50]=1;

Dargestellt wird eine Sendung der Daten 012345 per UDP. Angezeigt wird 
Empfänger,Protokoll (17=UDP) und die payload. Damit ist eine Auswertung 
eingehender TCP/UDP Daten problemlos möglich.

Was mir jetzt noch fehlt, ist die Sendung von Daten. Auch bei diesem 
Problem vertraue ich auf breite Unterstützung.

von Stefanus (Gast)


Lesenswert?

Du kannst von meinem Webserver (bzw dem socketd darin) abgucken.
http://stefanfrings.de/avr_io/index.html
1
// Do something when a connection has been established
2
static PT_THREAD(handle_connection(struct application_state *app_state))
3
{
4
    PSOCK_BEGIN(&app_state->socket);
5
    PSOCK_SEND_STR_P(&app_state->socket, PSTR("Welcome\n"));    
6
    PSOCK_END(&app_state->socket);
7
}
8
9
// Process TCP connection. This is called by inetd in case of events 
10
// related to a connection and also periodically. 
11
12
void socketd_appcall(struct application_state *app_state) {
13
    // is the connection closed?
14
    if (uip_closed() || uip_aborted() || uip_timedout()) {
15
        // do nothing
16
        return;
17
    }
18
    // is this a new connection?
19
    if (uip_connected()) { 
20
  // Initialize the Protosocket. 
21
  PSOCK_INIT(&app_state->socket, app_state->inputbuffer, sizeof (app_state->inputbuffer));
22
    }
23
    handle_connection(app_state);
24
}

Du findest da auch Beispiele, wo der Puffer byteweise gefüllt wird 
(nicht mit Strings).

von Stefanus (Gast)


Lesenswert?

Ich benutze oft das Makro PSOCK_GENERATOR_SEND, um Daten zu senden, die 
berechnet werden.

Zum Beispiel Variable i enthält eine Integer zahl, die soll als 
Menschen-Lesbarer String gesendet senden. Also ist der String zur 
Laufzeit berechnet. Da hast du zwei Möglichkeiten:

1) Wandle die Zahl mit sprintf in einen String um, speichere den String 
in ein Array und sende ihn mit PSOCK_SEND_STR. Du brauchst also ein 
Array als Zwischenspeicher das groß genu ist, den String aufzunehmen.

2) Rufe PSOCK_GENERATOR_SEND auf, einer der Argumente ist eine Funktion, 
die den Puffer füllen soll. Sie wandelt die Zahl mit sprintf in einen 
String um und benutzt dabei direkt den I/O Buffer. Dann brauchst du kein 
Array, also weniger RAM.

Allerdings muss das Programm dann jederzeit imstande sein, die 
Umwandlung zu wiederholen (nämlich wenn der Empfänger einen 
Übertragungsfehler meldet oder dessen Acknowledge unterwegs verloren 
geht).

PSOCK_GENERATOR_SEND(&app_state->socket, myfunction, app_state);

Was man dabei beachten sollte, ist dies:

PSOCK_GENERATOR_SEND sendet bei jedem Aufruf genau ein Ethernet Paket. 
Was da gesendet wird, muss also (mitsamt Protokoll-Overhead) in den 
Puffer passen. Außerdem ist es nicht gut, auf diese weise viele kleine 
Pakete zu senden, weil sonst der Overhead größer ist, als die Nutzdaten. 
Zum Beispiel würde ich nicht auf diese Art 10.000 Integer zahlen einzeln 
senden. Denn das ergäbe 10.000 mini kleine Pakete.

Achtung: Du kannst Dich nicht darauf verlassen, dass die Ethernet Pakete 
so groß sein dürfen, wie der Puffer. DSL überträgt zum Beispiel maximal 
rund 1400 bytes soweit ich mich recht erinnere, als rund 1300 bytes 
Nutzdaten.

Die Funktion uip_mss() verrät Dir, wie viele Bytes du tatsächlich 
maximal am Stück senden kannst.

von Stefanus (Gast)


Lesenswert?

Ach ich Esel. ich habe gar nicht bemerkt, dass ed Dir um UDP geht. Und 
ich labere dich voll von wegen TCP...

naja, für UDP findest Du in meinem Projekt auch was zum Abgucken, und 
zwar in der Datei dhcpc.c.

uip_send(...) ist die gesuchte Funktion.

von Jojo S. (Gast)


Lesenswert?

es gibt auch eine uip Doku und Beispiele wie den Webserver. Es wird 
zyklisch ein uip_periodic() aufgerufen und darin deine appcall(). Darin 
wird der Verbindungszustand geprüft und bei connected wird 
handle_connection() als Standard abgearbeitet. Und die sieht so aus das 
erst auf ankommende Daten geprüft wird und dann auf ausgehende. Für die 
ausgehenden Daten wird dann der buffer+len gesetzt. Damit die state 
machine für das Senden richtig funktioniert muss man aber schon die PT_ 
Funktionen nutzen.
Für UDP wird eine uip_udp_apcall() aufgerufen.

von leluno (Gast)


Lesenswert?

Vielen Dank, das ist doch schon mal sehr hilfreich. Mir geht es auch 
nicht um UDP. Mit TCP kann man auch UDP empfangen. Reines UDP 
funktioniert bei meiner uIP-Version nicht. Die UDP-Verbindung habe ich 
nur deswegen aufgezeichnet, weil ich ein UDP-Terminal auf dem PC habe.

von Jojo S. (Gast)


Lesenswert?

UDP und TCP sind zwei paar Schuhe, die gemeinsame Sohle ist IP, auf dem 
Level hast du die Pakete abgefischt. Damit musst du aber das TCP 
Protokoll selber implementieren und das ist zu mühselig wo dir uip das 
doch schon liefert.
Ausserdem hast du das ARP auskommentiert was man auch nicht tun sollte. 
Damit geht deine Verbindung kaputte wenn dein Gegner oder ein Switch 
nach einer Zeit die IP-MAC Zuordnung verwirft. Die Paketbehandlung in 
der mainloop oder uip_task sollte man erst mal so lassen wie sie ist.
Der Angriffspunkt ist dann das Makro UIP_APPCALL in uipopt.h. Dann 
brauchst du nur noch die Behandlung für connect und Daten rein/raus zu 
bauen.
Wenn du TCP Server für mehrere Ports implementieren möchtest sollte die 
appcall Funktion ein Verteiler sein, etwa so:
1
#include "uip.h"
2
#include "fs20d.h"
3
#include "httpd.h"
4
5
void tcpdispatcher_init(void)
6
{
7
  httpd_init();
8
  fs20d_init();
9
}
10
void tcpdispatcher_appcall(void)
11
{
12
  switch (uip_conn->lport)
13
  {
14
  case HTONS(80):
15
    httpd_appcall();
16
    break;
17
  case HTONS(3333):
18
    fs20d_appcall();
19
    break;
20
  }
21
}

Für UDP muss in uip-conf.h die Unterstützung mit dem define 
UIP_CONF_UDP=1 eingeschaltet werden und in der main loop auch die 
Behandlung für uip_udp_appcall (ähnlich der TCP Verarbeitung) drin sein.

von Karl K. (leluno)


Lesenswert?

Jojo S. schrieb:
> Ausserdem hast du das ARP auskommentiert

uip_arp_ipin() ist nur eine Scheinfunktion ohne Inhalt:
#define uip_arp_ipin()
Warum RDB so etwas in einem Beispielcode läßt, verstehe ich nicht.

Bei Aktivierung von UDP kommt die Fehlermeldung

unknown type name 'uip_udp_appstate_t'  uip.h  /RDB1768cmsis2_uIP/uip 
line 1223  C/C++ Problem

es gibt zwar eine structure uip_tcp_appstate_t nicht jedoch 
uip_udp_appstate_t?

von Jojo S. (Gast)


Lesenswert?

das RDB_uip Beispiel habe ich auch gefunden, warum das uip_arp_ipin() 
auskommentiert ist weiss ich jetzt ad hoc auch nicht. Die Funktion ist 
jedenfalls vorhanden und nur auskommentiert, in uip_arp.h per define und 
in uip_arg.c per '#ifdef 0'. Es gibt aber noch ein uip_arp_arpin() und 
das ist aktiv. Vielleicht ist die erste Funktion aus Performancegründen 
auskommentiert worden, die wird ja bei jedem ankommenden Paket 
ausgeführt.

Den udp_app_state Type soll man sich selber definieren in uipopt.h, das 
kann ein void Pointer oder sonstwas sein damit man sich 
Verbindungsabhängig noch einen Status merken kann.
 typedef int uip_udp_appstate_t;
Ist schon länger her das ich damit ein UDP gesendet habe und ich 
erinnere mich das es fummelig war, an dieser Stelle habe ich glaube ich 
auch lange gesucht.

von Karl K. (leluno)


Lesenswert?

ich habe jetzt einmal versucht, eine Sendefunktion aufzubauen:
1
...
2
memcpy(kktest,xDefaultPartARPPacketHeader,42);
3
kktest[ipsrc]=192;kktest[ipsrc+1]=168;kktest[ipsrc+2]=0;kktest[ipsrc+3]=146;
4
kktest[ipdest]=192;kktest[ipdest+1]=168;kktest[ipdest+2]=0;kktest[ipdest+3]=124;
5
kktest[prot]=6;
6
kktest[sport]=0;kktest[sport+1]=80;
7
kktest[dport]=0;kktest[dport+1]=80;
8
kktest[payload]=33;
9
kktest[payload+1]=34+zl;
10
kktest[payload+2]=35+zl;
11
kktest[payload+3]=36+zl;
12
zl++;
13
tapdev_send(kktest,50);
14
...
einen Sende-frame kktest zusammengestellt, über tapdev_send in den 
uip_buf
 geladen. Das device versucht zu senden, bei wireshark kommt aber nichts 
an. Beim Debugging sieht das ganze sehr zerhackt aus. Es gibt nicht 
nachvollziehbare Rücksprünge. Ich fürchte, dass das am Protothread 
liegt.

Da ich noch relativ weit am Anfang bin, nochmal die Systemfrage 
-insbesondere an  Stefanus, der die Protothreads offensichtlich 
verstanden hat:

uip scheint mir eher ein Auslaufmodell zu sein, wie die Funktion 
uip_arp_ipin zeigt, die bei einer Programmpflege - da fehlerbehaftet und 
überflüssig - im Programm nicht mehr auftauchen dürfte. Adam Dunkel 
arbeitet selbst bei lwip mit, was das Ende der von ihm entwckelten 
Protothreads bedeuten dürfte. Ist diese Einschätzung richtig?

Welches der möglichen Stack-Programme für Arms wird am sinnvollsten 
eingesetzt?
-uip
-lwip
-easyweb
oder macht es möglicherweise sogar Sinn, den Webserver von U-Radig
auf den Arm anzupassen?

Ich bin interessierter Laie, es reicht mir wenn es irgendwie läuft.

von Jojo S. (Gast)


Lesenswert?

karl k. schrieb:
> tapdev_send(kktest,50);
> ...
> einen Sende-frame kktest zusammengestellt, über tapdev_send in den

so geht das nicht. tapdev_send() ist die low level Funktion um einen 
Frame zu senden, die wird von uip aufgerufen und die must du in Ruhe 
lassen.
Zu sendende Daten müssen in den Buffer nach uip_appdata und uip_len muss 
>0 gesetzt werden. In der main loop wird dann bei uip_len > 0 das Senden 
ausgelöst. Das setzt aber vorraus das auch eine richtige TCP-Verbindung 
aufgebaut wurde, sonst steht im Header Murks. Nur die IP Adresse 
reinpatchen reicht nicht.
Das uip_arp_ipin() ist kein echtes Problem, auskommentieren und gut ist. 
Den gleichen Code findet man auch in dem weitergeführten Contiki 
Projekt. In der main loop wird auf das ARP reagiert, dafür gibt es einen 
eigenen Ethernettype. Der auskommentierte Teil war für ein Refresh bei 
beliebigen IP Paketen mit passender MAC Adresse, das soll man aber gar 
nicht tun.

uip ist schon brauchbar, lwip ist 'schöner' und an den BSD sockets 
orientiert, braucht aber mehr Resourcen. lwip habe ich auch schon in 
mehreren kommerzielen Produkten gesehen, Contiki ist wohl eher ein Mini 
Betriebssystem für kleine Geräte.

Das Beispiel von dem du ausgegangen bist enthält doch einen Webserver, 
richtig? Funktioniert der denn wenn du den Code unverändert lässt?

von Karl K. (leluno)


Lesenswert?

bis auf das Senden geht alles prima, auch der Webserver. ich probier das 
jetzt mal mit uip_appdata. Danke für den Hinweis.

von Jojo S. (Gast)


Lesenswert?

hast du auch die Doku zu uip?
siehe: 
http://citeseerx.ist.psu.edu/viewdoc/download?rep=rep1&type=pdf&doi=10.1.1.154.2510
Packe mal das Beispiel aus 1.7.2 in eine Datei und ersetze die appcall() 
vom Webserver durch die aus dem Beispiel. Dann sollte einfach ein 
connect auf den Port 2345 möglich sein.

von Karl K. (leluno)


Lesenswert?

vielen dank für den Link auf das manual.
Ich habe die Beispiele ausprobiert. Sie gehen alle nicht.

Neuer Ansatz:
void uip_send(const void *data, int len);
* Send data on the current connection.
 *
 * This function is used to send out a single segment of TCP
 * data. Only applications that have been invoked by uIP for event
 * processing can send data.
 *
 * The amount of data that actually is sent out after a call to this
 * funcion is determined by the maximum amount of data TCP allows. uIP
 * will automatically crop the data so that only the appropriate
 * amount of data is sent. The function uip_mss() can be used to query
 * uIP for the amount of data that actually will be sent.

->Daten zusammenstellen + app invoke for event processing

1.Problem: Zusammenstellen der Daten im array kktest

Die von mir verwendete Funktion xDefaultPartUDPPacketHeader aus lwip 
passt irgendwie nicht.

der Aufbau muss wohl sein:
udp: IPUDPH 20iph + 8udph =28byte
tcp: IPTCPH 20iph + 20tcph =40byte

8udp byte ist sport+dport+len+chks
20 tcp byte beginnend ab sourceIP bis urgent pointer?

20 ip byte verion bis destip
Wo gibt es im Programm eine Maske zum Ausfüllen dieser Daten oder muss 
man dies von Hand machen?

Meine bisherigen Sendeversuche haben jedenfall schon deswegen nicht 
funktioniert, weil der header nicht stimmte.

von Karl K. (leluno)


Lesenswert?

neue Merkwürdigkeit: Die Beispielprogramme hängen an dieser Stelle:
1
#define uip_ipaddr(addr, addr0,addr1,addr2,addr3) do { \
2
                     ((u16_t *)(addr))[0] = HTONS(((addr0) << 8) | (addr1)); \
3
                     ((u16_t *)(addr))[1] = HTONS(((addr2) << 8) | (addr3)); \
4
                  } while(0)
wer kann mir den Sinn dieses while(0) erklären?

: Bearbeitet durch User
von Karl K. (leluno)


Lesenswert?

nach Aktivieren von uip_ipaddr und uip_netmask klappt jetzt schon mal 
der Aufbau einer Sendeverbindung. Licht am Ende des Tunnels...

von Jojo S. (Gast)


Lesenswert?

Ich empfinde diese Protothreads auch als wilde Trickserei, aber 
irgendwie funktioniert es ja... Im do{}while(0) kann der Code nicht 
hängen, ist ja einfach eine Schleife die einmal ausgeführt und nicht 
wiederholt wird. Der Grund ist Fehler bei der Makroauflösung zu 
verhindern:
http://c-faq.com/cpp/multistmt.html

Eine gute und ausführliche Beschreibung der Protodingens findest du 
hier:
http://stefanfrings.de/avr_io/protosockets.html

Um etwas zu Senden musst du dich erstmal entscheiden ob TCP oder UDP. 
Wenn TCP dann ist eine Verbindung nötig, dann ist die nächste Frage ob 
die Verbindung aktiv (Client) oder passiv (Server) aufgebaut werden 
soll. Bei UDP ist keine Verbindung nötig, aber du musst dem uip per 
uip_udp_bind() sagen wohin du senden möchtest. Diese appcall Geschichte 
ist dann trotzdem nötig, wenn deine app aufgerufen wird darfst du mit 
uip_send() senden, der Header wird dann von uip gefüllt.

von Karl K. (leluno)


Lesenswert?

Problem mit uip_send: memcpy erzeugt eine Fehlermeldung

uip_sappdata müsste ein Zeiger auf uip_buffer sein. Warum klappt memcpy 
nicht?
1
uip_len=52;
2
memcpy(kktest,xDefaultPartTCPPacketHeader,52);
3
uip_send(kktest,  52);
4
5
void
6
uip_send(const void *data, int len)
7
{
8
  if(len > 0) {
9
    uip_slen = len;
10
    if(data != uip_sappdata) {
11
       memcpy(uip_sappdata, (data), uip_slen);
12
    }
13
  }
14
}

von JojoS (Gast)


Lesenswert?

Was für eine Fehlermeldung, memcpy kann keine Fehler melden. Eine 
Compiler Fehlermeldung?

von Karl K. (leluno)


Lesenswert?

Beim Debuggen source not found:
No source available for "memcpy() at 0x3160"

von leluno (Gast)


Lesenswert?

Das problem memcpy ist erledigt. Daten werden jetzt mit uip_send richtig 
eingesetzt.
Viele meiner Probleme scheinen an der Optimierung zu liegen. Do while(0) 
fliegt dabei raus. Deswegen funktioniert zb uip_periodici nicht. Heute 
abend geht es weiter.

von Jojo S. (Gast)


Lesenswert?

karl k. schrieb:
> No source available for "memcpy() at 0x3160"

Das ist aber kein Fehler, das heisst ja nur das zur genannten Funktion 
kein Quellcode vorliegt. memcpy() ist in der C-Runtime und da kann man 
davon ausgehen das sie tut was sie soll. Oder im Disassembler 
durchsteppen wenn man mag. memcpy() ist natürlich immer potentiell 
gefährlich weil man sich den Speicher überschreiben kann, ohne 
Speicherschutz Mechanismen bekommt man das nicht sofort mit.
Und deine 'Optimierung' solltest du lieber lasser, für ein while(0) 
generiert dir der Compiler gar keinen Code aber verhindert Syntaxfehler 
bei der Makroexpandierung.
Wenn uip_periodic() nicht funktioniert hast du etwas anderes 
falschgemacht. Wie sieht deine appcall() Anwendung denn jetzt aus? 
uip_periodic() ist für TCP, wolltest du nicht UDP senden? Oder meintest 
du uip_udp_periodic()?

von Stefanus (Gast)


Lesenswert?

> uip scheint mir eher ein Auslaufmodell zu sein

Mir auch, aber nicht wegen mutmaßlicher Fehler sondern weil lwip 
komfortabler ist und dazu passende Mikrocontroller inzwischen auch für 
Hobbyelektroniker leicht zu haben sind.

uIP ist meines Wissens nach ohne Alternativen, wenn man Mikrocontroller 
mit wenig RAM (<4KB) verwenden möchte. Der geringe Speicherbedarf 
verursacht wiederum das größte Manko: uIP hat eine feste Window-Size von 
1, weswegen vor allem als Webserver im öffentlichen Internet lahm ist. 
Wobei ich generell davon abrate, solche potentiell unsicheren Sachen ins 
Netz zu hängen.

Ich finde, dass die Programmierung von Webservern auf einem Multitasking 
OS (wie Linux) 100x komfortabler ist, als auf Mikrocontrollern. Also 
mach das nur, wenn es wirklich unbedingt sein muss oder wenn Du 
sportlichen Ehrgeiz dafür hast.

von Karl K. (leluno)


Lesenswert?

Jojo S. schrieb:
> Wenn uip_periodic() nicht funktioniert hast du etwas anderes
> falschgemacht.

Es ist tatsächlich so, dass die do-while(0)-Macros vom vom codeRed 
compiler wegoptimiert werden. Aber dieses Problem ist gelößt.

Ich möchte im Prinzip einen Ethernet-Hausbus aufbauen. Ob Daten per TCP 
oder per UDP gesendet werden, ist im Prinzip egal.

Ich hänge derzeit bei uip_process: Wenn ich irgendetwas an einen per 
uip_listen geöffneten Port sende, wird die UIP_APPCALL in uip_process 
nicht aufgerufen weil die flags nicht stimmen.

Stefanus schrieb:
> wegen mutmaßlicher Fehler

Die Fehler liegen sicher nicht im uip-Programm. Ein solches Programm zu 
erstellen ist eine gigantische Leistung. Es ist nur leider sehr 
schwierig, dieses Programm anzupassen.

Ich mach jedenfalls erstmal ein paar Tage Pause.

: Bearbeitet durch User
von Stefanus (Gast)


Lesenswert?

> Die Fehler liegen sicher nicht im uip-Programm.
Sehe ich auch so.

von Karl K. (leluno)


Lesenswert?

Ich versuche die Manual-examples abzuarbeiten:
1
//++++++++++++ kk ++++++++++++++++++
2
void tcpdispatcher_appcall(void)
3
{
4
  ledtest=kkx;//6,4
5
  switch (uip_conn->lport)
6
  {
7
case HTONS(70):
8
    httpd_appcall();
9
    break;
10
case HTONS(60):
11
//led2_tog;
12
example1_app();
13
  break;
14
  case HTONS(80):
15
example1_app();
16
    break;
17
  }
18
}
19
//--------------------------------------------------
20
void  example1_app(void)  {//40
21
  ledtest=1;
22
struct httpd_state *s = (struct httpd_state *)&(uip_conn->appstate);
23
  if(uip_connected())  {
24
//  s->state  =  WELCOME_SENT;
25
//#define STATE_WAITING 0
26
//#define STATE_OUTPUT  1
27
s->state=0;
28
  uip_send("Welcome!\n",  9);
29
  return;
30
  }

APPCALL ruft den dispatcher auf. Das funktioniert aber nur mit dem 
httpd-port 80. Obwohl andere ports per uip_listen initialisiert sind, 
wird der dispatcher nicht angesprungen, UIP_APPCALL() in uip_process() 
also nicht ausgeführt. wenn ich Port 80 eine andere Funktion zuweise 
wird diese bei Aufruf der Webseite ausgeführt, nicht jedoch wenn ich per 
UDP-Terminal an POrt 80 sende. Wie kann ich das Programm dazu bringen, 
dass andere Ports als Port 80 den dispatcher aufrufen?

von Jojo S. (Gast)


Lesenswert?

karl k. schrieb:
> wird diese bei Aufruf der Webseite ausgeführt, nicht jedoch wenn ich per
> UDP-Terminal an POrt 80 sende.

UDP != TCP. Mit deinem UDP Terminal kannst du kein TCP Paket senden. 
Probiere mal 'telnet lpc1768-ipadresse 60', dann sollte deine 
example_app1() angesprungen werden.

von Volker (Gast)


Lesenswert?

Versuche es mal Ports > 1024

von Volker (Gast)


Lesenswert?

Ups, da fehlte noch ein 'mit' :-)

von Jojo S. (Gast)


Lesenswert?

Die Portnr. ist dem uip egal, das kennt keine reservierten Systemports. 
Und Telnet akzeptiert normalerweise auch beliebige ports wenn die als 
Option angegeben wird.
Alternativ kannst du das Terminalprogramm 'putty' und da die 'raw' 
session als Option verwenden:
http://www.chiark.greenend.org.uk/~sgtatham/putty/

Um mit UDP zu arbeiten muss wie geschrieben das UDP #define gesetzt 
sein, eine uip_udp_appcall angelegt werden und statt uip_listen() 
uip_bind() benutzen.

von Volker (Gast)


Lesenswert?

uip nicht, aber vielleicht seine Firewall im PC?

von Stefanus (Gast)


Lesenswert?

uip_listen ist nur für TCP.

UDP Programme werden an einen Port gebunden und aufgerufen, wenn etwas 
auf dem Port empfangen wurde sowie in regelmäßigen Abstände (periodic).

Schau wie ich den dhcp Client implementiert habe.

State-Holder für den Thread:
1
struct dhcpc_state {
2
    struct pt pt;              // Protothread of the DHCP client
3
    struct uip_udp_conn *conn; // Connection of the UPD socket
4
    ...
5
    // Hier gehören Benutzerdefinierte Variablen der UDP Anwendungen hin.
6
    // Alle UDP Anwendungen müssen die gleiche Struktur verwenden.
7
};
8
9
typedef char uip_udp_appstate_t;

So sagst Du uIP, welche Funktion bei Empfang eines UDP Paketes 
aufgerufen werden soll:
1
void dhcpc_appcall(void) {
2
    ...
3
}
4
5
#define UIP_UDP_APPCALL dhcpc_appcall

Der UDP teil von uIP ist (im Gegensatz zum TCP Teil) nur für eine 
einzige UDP Anwendung ausgelegt. Diese eine Anwendung kann auf vielen 
Ports empfangen, aber nur an eine Gegenstelle Senden.

Durch das Binden eines Portes machst Du uIP bereit, Daten auf dem Port 
zu senden bzw. zu empfangen. Es handelt sich dabei NICHT um eine 
Verbindung (an dieser Stelle ist der von uIP vorgegebene Begriff conn 
irreführend). UDP arbeitet Verbindungslos. Dementsprechend entfallen 
auch Verbindungs-Aufbau und -Abbau. Es werden einfach einzelne Pakete 
von Rechner A Port x nach Rechner B Port y gesendet. Ob sie auch 
wirklich ankommen, ist dem UDP Protokoll egal.

Einen Port binden:
1
    uip_ipaddr(addr, 255, 255, 255, 255);
2
    status.conn = uip_udp_new(&addr, HTONS(67));
3
    if (status.conn != NULL) {
4
        uip_udp_bind(status.conn, HTONS(68));
5
    }

Du sagst damit dem uIP, dass die UDP Anwendung zu 255.255.255.255 Port 
67 senden soll, und empfangen tut er auf seiner eigenen IP-Adresse Port 
68.

Anmerkung: Die meisten UDP Anwendungen nutzen für Sendung und Empfang 
den gleichen Port. In meinem Fall geht's aber um DHCP, da ist das etwas 
anders.

Wenn nun uIP etwas empfängt, dann ruft er die Anwendung auf, also in 
meinem Fall dhcpc_appcall(). Er ruft die Applikation außerdem periodisch 
auf. In meinem Fall ungefähr einmal pro Sekunde, weil ich den Timer 
entsprechen initialisiert habe:
1
timer_set(&periodic_timer, CLOCK_SECOND);

In meiner UDP Applikation benutze ich uip_newdata(), um herauszufinden, 
ob etwas empfangen wurde. Die Empfangenen Daten befinden sich im Puffer 
uip_appdata. UDP Pakete können nicht größer sein, als der Puffer, also 
enthält der Puffer immer das vollständige empfangene Paket.

Nachdem Du empfangene Daten verarbeitet hast (falls überhaupt etwas 
empfangen wurde), darfst du senden. Befülle dazu den Puffer uip_appdata 
mit den Daten, die du senden willst. Danach rufst Du uip_send auf:
1
uip_send(uip_appdata, length);
Length ist die Anzahl von bytes, die gesendet werden soll. Auch hier 
gilt, dass das ganze Paket in den Puffer rein passen muss. UDP Pakete 
können nicht größer sein.

Die Funktion uip_mss() verrät Dir, wie viele Bytes du maximal in den 
Puffer schreiben darfst.

von Stefanus (Gast)


Lesenswert?

Zum Debugging kann es hilfreich sein, die Windows Firewall zu 
deaktivieren. Die blockt nämlich den Empfang aus sämtlichen Ports ab, 
sofern sie nicht ausdrücklich freigegeben werden. Dem normalen Windows 
User fällt das nicht auf, wiel die Setup-Programme sich um die 
Konfiguration der Windows-Firewall kümmern.

Wireshark ist übrigens auch sehr hilfreich.

von Stefanus (Gast)


Lesenswert?

> Der UDP teil von uIP ist (im Gegensatz zum TCP Teil) nur für eine
> einzige UDP Anwendung ausgelegt. Diese eine Anwendung kann auf
> vielen Ports empfangen, aber nur an eine Gegenstelle Senden.

Sorry, das war quatsch. Vergiss diesen Satz einfach.

von Stefanus (Gast)


Lesenswert?

Ich glaube, ich sollte noch eine seltsame Sache in meiner dhcpc.h 
erklären:
1
struct dhcpc_state {
2
    struct pt pt;              // Protothread of the DHCP client
3
    struct uip_udp_conn *conn; // Connection of the UPD socket
4
    ...
5
};
6
7
//typedef struct dhcpc_state uip_udp_appstate_t;
8
typedef char uip_udp_appstate_t;
9
10
#define UIP_UDP_APPCALL dhcpc_appcall

Eigentlich ist es in uip vorgesehen, diese eine auskommentierte Zeile zu 
haben, wo der Typ vom UDP Status (uip_udp_appstate_t) definiert wird. 
Jede "virtuelle" UDP Verbindung hat eine eigene Instanz dieser Struktur 
(wie bei TCP).

Ich brauchte allerdings eine einfache Möglichkeit, jederzeit von Außen 
auf den Status meines DHCP Clients zuzugreifen, sogar schon bevor er 
überhaupt gestartet wurde. Deswegen hatte ich in dhcpc.c einfach eine 
statische Instanz der Struktur angelegt:
1
static struct dhcpc_state status;
Und ich habe uip_udp_appstate_t durch einen Dummy vom Typ char ersetzt.

Ich bin inzwischen der Ansicht, dass diese Lösung ziemlich quick and 
dirty ist, mach das besse nicht. Du solltest in deiner Anwendung lieber 
wie vorgesehen die Zeile
1
typedef struct dhcpc_state uip_udp_appstate_t;
verwenden.

von Karl K. (leluno)


Lesenswert?

alle Hinweise waren hilfreich, der Empfang - tcp+udp - funktioniert 
jetzt.

Wesentlicher Fehler war die fehlende Definition der UDP-Verbindung
1
  uip_ipaddr_t addr;
2
   struct uip_udp_conn *c;
3
   uip_ipaddr(&addr, 192,168,0,124);
4
   c = uip_udp_new(&addr, HTONS(67));
5
   if(c != NULL) {
6
     uip_udp_bind(c, HTONS(67));
7
   }


Frage zu UIP_UDP_APPCALL:
1
    if(uip_udp_conn->lport != 0 &&
2
       UDPBUF->destport == uip_udp_conn->lport &&
3
       (uip_udp_conn->rport == 0 ||
4
        UDPBUF->srcport == uip_udp_conn->rport) &&
5
       (uip_ipaddr_cmp(uip_udp_conn->ripaddr, all_zeroes_addr) ||
6
  uip_ipaddr_cmp(uip_udp_conn->ripaddr, all_ones_addr) ||
7
  uip_ipaddr_cmp(BUF->srcipaddr, uip_udp_conn->ripaddr))) {
8
      goto udp_found;
9
    }
10
  }
11
  UIP_LOG("udp: no matching connection found");
12
  goto drop;
13
  
14
 udp_found:
15
  uip_conn = NULL;
16
  uip_flags = UIP_NEWDATA;
17
  uip_sappdata = uip_appdata = &uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN];
18
  uip_slen = 0;
19
  UIP_UDP_APPCALL();
20
 udp_send:
21
  if(uip_slen == 0) {
22
    goto drop;
23
  }
24
  uip_len = uip_slen + UIP_IPUDPH_LEN;

bei Stefanus wird
PT_THREAD(handle_dhcp(void)) {
aufgerufen. Bei diesem Appcall müssen doch nur noch die per UDP 
gesendeten Daten aus uip_buf ausgelesen werden? Wozu braucht man da 
einen Protothread?


als nächstes versuche ich über uip_process(UIP_UDP_SEND_CONN) Daten zu 
senden.

von Stefanus (Gast)


Lesenswert?

> Wozu braucht man da einen Protothread?

Du hast Recht, ich hätte den DHCP Client auch ohne Protothreads 
implementieren können, wenn ich das gewollt hätte.

Der DHCP CLient sendet einen DHCP Request, wartet auf Antwort und 
wiederholt ggf mehrmals. Außerdem muss er vor Ablauf der Adresse, einen 
Refresh Anfordern. Und Falls der DHCP Server gar nicht passend 
antwortet, muss er auf eine statische IP Adresse zurück fallen 
(jedenfalls wollte ich es so haben).

Ich darf aber keine Warteschleifen programmieren. Weiterhin darf die 
Applikation bei jedem Aufruf durch uIP nur ein einziges Paket senden. 
Und sie muss schnellstmöglich (zu uIP) zurück kehren, sonst bekommt uIP 
weder Gelegenheit, den Inhalt des Puffers zu senden (an den Ethernet 
Controller zu übergeben), noch empfangene Pakete beim Ethernet 
Controller abzuholen.

Also hätte ich entweder einen Zustandautomaten gebraucht, oder eben die 
Protothreads, was ja letztendlich nichts anderes ist.

von Stefanus (Gast)


Lesenswert?

> als nächstes versuche ich über uip_process(UIP_UDP_SEND_CONN)
> Daten zu senden.

Nein! Du sollst uip_send() benuten!

von Karl K. (leluno)


Lesenswert?

wenn ich das richtig verstanden habe, füllt uip_send den uip_buffer. 
Danach wird der Inhalt des uip_buf durch uip_process abgearbeitet.

Bei TCP über Aufruf von uip_process(UIP_DATA), bei UDP demzufolge 
uip_process(UIP_UDP_SEND_CONN). Sonst wird der UDP-Teil in uip_process 
gar nicht angesprungen?

von Stefanus (Gast)


Lesenswert?

Nein, uip_send() löst das Senden des Puffers aus.

uip_process sollst du aus der Applikation heraus gar nicht aufrufen. Die 
Funktion ist im Referenz-Manual von uIP 1.0 auch gar nicht dokumentiert.

uip_process() wird (indirekt über Makros) von der Hauptschleife 
aufgerufen. Schau mal in uip.h, welche defines alles uip_process 
enthalten.

von Karl K. (leluno)


Lesenswert?

ich bin mit tcp senden etwas weitergekommen. Die Befehlsfolge
1
uip_ipaddr_t ipaddr;
2
uip_ipaddr(&ipaddr, 192,168,0,124);
3
uip_send(kktest,  4);// payload in uip_buf
4
uip_connect(&ipaddr, HTONS(70));
sendet einen korrekten TCP-Frame/ len=60, überträgt allerding die 
Payload nicht mit.

die uip_process-Macros habe ich aufgelößt, weil ich das Programm 
verstehen will. Warum diese Hauptfuktion durch Macros verschleiert wird, 
ist für mich nicht nachvollziehbar.
Du hast recht, uip_process(UIP_UDP_SEND_CONN) ist nicht erforderlich, 
weil der udp Teil über

{uip_udp_conn = &uip_udp_conns[i]; 
uip_process(UIP_UDP_TIMER);}//=>uip_udp_periodic(i);

aufgerufen wird

von Karl K. (leluno)


Lesenswert?

1
 send:
2
  DEBUG_PRINTF("Sending packet with length %d (%d)\n", uip_len,
3
         (BUF->len[0] << 8) | BUF->len[1]);
4
  kktest[70]=uip_len; kktest[61]=uip_buf[61];uip_len+=20;
5
  uip_buf[61]='t';uip_buf[62]='e';uip_buf[63]='s';uip_buf[64]='t';
6
7
  UIP_STAT(++uip_stat.ip.sent);
8
  /* Return and let the caller do the actual transmission. */
9
  uip_flags = 0;

Wenn ich hier uip_len und uip_buf verändere, wird die payload korrekt 
übertragen, d.h. der Fehler liegt wahrscheinlich bei der Ausführung von 
uip_send.

von Jojo S. (Gast)


Lesenswert?

karl k. schrieb:
> uip_send(kktest,  4);// payload in uip_buf
> uip_connect(&ipaddr, HTONS(70));

du sendest zuerst und baust danach die Verbindung auf? Sorry, aber noch 
abstruser kann man das uip nicht missbrauchen :-(
Wer ist der Gegner für das connect? Wie siehst du das die Verbindung 
aufgebaut wurde??

von Karl K. (leluno)


Lesenswert?

hier steckt der Fehler:
1
  BUF->optdata[0] = TCP_OPT_MSS;
2
  BUF->optdata[1] = TCP_OPT_MSS_LEN;
3
  BUF->optdata[2] = (UIP_TCP_MSS) / 256;
4
  BUF->optdata[3] = (UIP_TCP_MSS) & 255;
5
  uip_len = UIP_IPTCPH_LEN + TCP_OPT_MSS_LEN;
6
  BUF->tcpoffset = ((UIP_TCPH_LEN + TCP_OPT_MSS_LEN) / 4) << 4;
7
  goto tcp_send;

für heute reichts...

von huhuuu (Gast)


Lesenswert?

karl k. schrieb:
> Warum diese Hauptfuktion durch Macros verschleiert wird,
> ist für mich nicht nachvollziehbar.

dazu musst du verstehen was protothreads sind ...
dann ergibt das plötzlich alles einen sinn ^^

dann würdest du feststellen das dein uip_send einfach nur sendet ...
während der protothread auch das ACK ( bei tcp) bearbeitet und 
dementsprechend eine sendewiederholung  macht

aber generell gilt:
verbindung öffnen  ... senden ... dann schließen

von Karl K. (leluno)


Lesenswert?

uip_send sendet nicht sondern stellt nur Daten in uip_buf ein. Durch das 
Einstellen der Daten wird über den Timer bei uip_len>0 uip_process 
ausgelößt. Erst am Ende von uip_process wird über 
UIP_STAT(++uip_stat.ip.sent) das Senden über den Protothread aktiviert. 
Wegen der Verzögerung durch den Timer funktioniert auch uip_send vor 
uip_connect, wobei ich das jetzt umgestellt habe. Das war aber nicht das 
Problem. Problem war das Wegoptimieren der while(0)-Schleifen -warum 
auch immer- und die Verschiebung durch optdata.

TCP-Senden und TCP-Empfangen funktioniert jetzt einwandfrei. Die 
Anpassungen im Code sehen so aus:
1
//Sendebefehl:
2
kksend_status=1;
3
uip_ipaddr_t ipaddr;
4
uip_ipaddr(&ipaddr, 192,168,0,124);
5
uip_connect(&ipaddr, HTONS(70));
6
uip_send(kktest,  14);// payload in uip_buf
7
8
//Änderung in uip_send:
9
  if(len > 0) {
10
    uip_sappdata = &uip_buf[UIP_IPTCPH_LEN + UIP_LLH_LEN];//ab 54
11
        if(kksend_status==1){//aufruf tcp_send
12
      uip_sappdata = &uip_buf[UIP_IPTCPH_LEN + UIP_LLH_LEN+4];//ab 58
13
      kksend_len=UIP_IPTCPH_LEN + UIP_LLH_LEN+len;
14
        }else{
15
      uip_sappdata = &uip_buf[UIP_IPTCPH_LEN + UIP_LLH_LEN];//ab 54
16
      }
17
    uip_slen = len;
18
    if(data != uip_sappdata) {
19
       memcpy(uip_sappdata, (data), uip_slen);
20
 char i=0;i++;i--;//????
21
    }
22
23
24
//Änderungen in uip_process:
25
 send:
26
  DEBUG_PRINTF("Sending packet with length %d (%d)\n", uip_len,
27
         (BUF->len[0] << 8) | BUF->len[1]);
28
if(kksend_status==1){
29
uip_len+=14;
30
kksend_status==0;
31
}
32
33
  UIP_STAT(++uip_stat.ip.sent);
34
  /* Return and let the caller do the actual transmission. */

: Bearbeitet durch User
von Jojo S. (Gast)


Angehängte Dateien:

Lesenswert?

karl k. schrieb:
> TCP-Senden und TCP-Empfangen funktioniert jetzt einwandfrei. Die
> Anpassungen im Code sehen so aus:
> //Sendebefehl:
> kksend_status=1;
> uip_ipaddr_t ipaddr;
> uip_ipaddr(&ipaddr, 192,168,0,124);
> uip_connect(&ipaddr, HTONS(70));
> uip_send(kktest,  14);// payload in uip_buf

glaube ich nicht, nie und nimmer wenn die Befehle genauso nacheinander 
aufgerufen werden. Das ist dann vielleicht KKP, aber niemals TCP!
Nochmal die Frage: gegen was testest du das? Und hast du dir mal die 
Grundlagen zu UDP und TCP angesehen?
TCP ist ein Protokoll bei dem eine Verbindung aufgebaut wird und es gibt 
verschiedene Zustände von Aufbau bis Abbau. Die ignorierst du alle wenn 
du den Stack verfummelst und versuchst ohne die appcall wie in den 
Beispielen zu arbeiten.
uip_connect() führt keinen connect aus, nach dem Aufruf der Funktion 
ist noch keine Verbindung etabliert(!). Das uip_connect() bereitet nur 
den TCP Header vor und setzt den Verbindungsstatus auf aktive 
Verbindung. Abgehandelt wird das automagisch in der main loop über 
uip_process().
Du musst in der appcall brav auf das Ereignis uip_connected() warten, 
dann weisst du das der Gegner deine Verbindung akzeptiert hat. Und 
Senden/Empfangen darfst du dann in uip_poll().
Im Anhang nochmals mein Beispiel das einen einfachen TCP Server 
implementiert. Der wartet auf ein connect von einem Client und der muss 
erstmal den String 'Jojos iPhone' senden. Dann wird ein 'ok' 
zurückgesendet. Dann wird in den Empfangsstauts gewechselt und auf 
Strings im Format 'FS20 xxxx' gewartet und wenn sowas angekommen ist 
wird eine Funktion mit (xxxx) aufgerufen.

von Karl K. (leluno)


Angehängte Dateien:

Lesenswert?

getestet mit wireshark, gesendet wird alle 10sec. Die Übertragung wird 
bestätigt. die payload stimmt.

von JojoS (Gast)


Lesenswert?

Das ist keine TCP Verbindung. Sieh dir Flags an. Vgl. TCP State 
machine.

von Stefanus (Gast)


Lesenswert?

Rot = fehlerhaft.

von Stefanus (Gast)


Lesenswert?

Benutze uIP so wie es vorgesehen ist, oder verzichte drauf.

Offensichtlich möchtest Du lieber deinen eingenen TCP/IP Stack 
schreiben. Nur zu, dann mach es! Sobald Du das Protokoll und den 
Algorithmus für die CRC verstanden hast, kannst Du damit loslegen.

Aber versuche bitte nicht, uIP zu vergewaltingen. Das kann nicht gut 
gehen. uIP funktioniert, wenngleich der Code hässlich ist und schwer zu 
durchschauen. Aber er funktioniert und ist korrekt.

von Karl K. (leluno)


Lesenswert?

natürlich habt ihr recht. Ich habe zwischenzeitlich das Tutorial von 
Stefan durchgearbeitet und das telnet-Beispiel auch zum laufen gebracht. 
Uip ist aber wohl nicht das was ich will. Die Protothreads haben ihre 
Begründung darin, möglichst schlanken code für kleine MCs zu generieren. 
Ich arbeite zur Zeit mit einem LPC1768 und einem Atmega1284. Beide haben 
ausreichend Resourcen, dafür braucht man keine Protothreads. Es muss 
auch einfacher gehen.

Das LPC-DK2-Board hat neben Ethernet auch einen SD-Steckplatz. Einen 
Vs1053 habe ich bereits zum laufen gebracht. Es bietet sich also an, mit 
dem Webserver gleich ein Webradio zu integrieren. Hat jemand dafür - 
einfachen - brauchbaren Code?

von Stefanus (Gast)


Lesenswert?

> Die Protothreads haben ihre Begründung darin, möglichst schlanken
> code für kleine MCs zu generieren.

Ganz genau. Klein und häßlich ist das.

von Pete K. (pete77)


Lesenswert?

Wie einfach ist das doch mit dem Raspberry Pi :-)

Also Ethernet auf µC ist schon die große Schule (meine Meinung).

von huhuuu (Gast)


Lesenswert?

karl k. schrieb:
> natürlich habt ihr recht. Ich habe zwischenzeitlich das Tutorial
> von
> Stefan durchgearbeitet und das telnet-Beispiel auch zum laufen gebracht.
> Uip ist aber wohl nicht das was ich will. Die Protothreads haben ihre
> Begründung darin, möglichst schlanken code für kleine MCs zu generieren.
> Ich arbeite zur Zeit mit einem LPC1768 und einem Atmega1284. Beide haben
> ausreichend Resourcen, dafür braucht man keine Protothreads. Es muss
> auch einfacher gehen.
>
> Das LPC-DK2-Board hat neben Ethernet auch einen SD-Steckplatz. Einen
> Vs1053 habe ich bereits zum laufen gebracht. Es bietet sich also an, mit
> dem Webserver gleich ein Webradio zu integrieren. Hat jemand dafür -
> einfachen - brauchbaren Code?

https://github.com/watterott/WebRadio


der IP stack ist evtl für dich einfacher zu gebrauchen ...

von Karl K. (leluno)


Angehängte Dateien:

Lesenswert?

udp senden und empfangen läuft jetzt mit korrektem Protokoll problemlos.

Der einfachste im Netz zu findende stack ist der mega8udp. Der Autor ist 
unbekannt.

Zunächst habe ich den lowlevel-Teil vom redlib-uip übernommen und 
isoliert. Die Schnittstelle sind die drei Funktionen init, send, 
receive. Der mega8udp-stack stellt auf rund 200 codezeilen für diese 
drei lowlevel-Funktionen praktisch den zu sendenden frame zusammen. 
Einfach und übersichtlich und für UDP-Datenaustausch völlig ausreichend.

Ich habe diesen code um eine UDP-Sendefunktion ergänzt, so dass das 
DK2-board von sich aus senden kann. Da der lowlevel-Teil getrennt ist, 
ist der code auch für andere Systeme verwendbar.

Als nächstes möchte ich Daten aus dem local-net hinaus über das Internet 
versenden. Anfangen möchte ich mit einem ntp-Abruf. Kann ich hier direkt 
von meinem DK2-board auf den ntp-server zugreifen oder muss der gateway 
zwischengeschaltet werden? Hat jemand eine einfache ntp-Funktion?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.