Server mit Ethernet Shild

Hallöchen,
ich hab nen Arduino Mega mit nem EthernetShild und möchte mir nen Server erstellen.
Die Beispiele von der Ethernet Lib sind soweit auch super.

Hat einer vieleicht ne Ahnung wie ich einen Server erstelle, der nach dem connect zuerst etwas senden z.b. "Hallo und Willkommen blabla".
Die Beispiele erwaten vom Client immer zuerst ne Eingabe :frowning:

Gibt es da was um zu erkennen das ein Client connected hat ?

Vielen Dank....

Die Frage ist, was Du genau machen willst? Wir reden hier über verschiedene "Ebenen" der Kommunikation. Die eine Ebene (Layer 4 / OSI/ISO Schichtenmodel) auf der die Verbindung zu Deinem Server aufgebaut wird ist TCP. Die kümmert sich nur darum, das die beiden Partner Daten austauschen können. Den Rest muss Deine Anwendung machen.
Dazu verwendet man in der Regel ein Protokoll, z.B. HTTP für einen Webserver. Ein Protokoll definiert dann wie die Kommunikation stattfinden muss. Bei HTTP muss der Client einen HTTP-Request senden, den der Server dann mit dem HTTP-Response beantwortet.
Bei SMTP (Mailversand) "redet" zuerst der Server und sagt dem Client wer er ist. Kannst ja mal zum Spass ein telnet auf Port 25 von Google machen:

telnet alt2.gmail-smtp-in.l.google.com 25
Trying 74.125.143.27...
Connected to alt2.gmail-smtp-in.l.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP io8si5826005lab.15
quit
221 2.0.0 closing connection io8si5826005lab.15
Connection closed by foreign host.

Du brauchst also für Deinen Anwendungsfall ein Protokoll das Du definierst und dann programmierst.

sven1977:
ich hab nen Arduino Mega mit nem EthernetShild und möchte mir nen Server erstellen.

Aha, "einen Server".
Und welchen Dienst und welches Protokoll soll der Server bedienen?
Telnet?
SSH?
FTP?
Mail?
DNS?
Usenet?
IRC?
HTTP?
Oder irgendwas anderes?

sven1977:
Die Beispiele von der Ethernet Lib sind soweit auch super.

Hm, ich rate einfach mal drauflos, was Du meinen könntest.
HTTP vielleicht.

sven1977:
Hat einer vieleicht ne Ahnung wie ich einen Server erstelle, der nach dem connect zuerst etwas senden z.b. "Hallo und Willkommen blabla".
Die Beispiele erwaten vom Client immer zuerst ne Eingabe :frowning:

Gibt es da was um zu erkennen das ein Client connected hat ?

HTTP funktioniert so:

  • Der HTTP-Client sendet eine Anforderung (Request) an den Server, z.B. einen GET Request.
  • Daraufhin sendet der HTTP-Server eine dementsprechende Antwort an den HTTP-Client zurück

Beim "Connecten" alleine sendet der Server noch gar nichts, sondern erst, wenn er einen gültigen Request empfangen hat. Und im Request steht drin, was der Server als Antwort ausliefern soll.

Beispiel:
GET / HTTP/1.1
==> Server soll die seine Default-Datei ausliefern ("Startseite")

Anderes Beispiel:
GET /xyz.html HTTP/1.1
==> Server soll die Datei "xyz.html" ausliefern

Noch ein Beispiel:
GET /forum/index.php HTTP/1.1
==> Server soll die Datei "index.php" aus dem Unterverzeichnis /forum ausliefern

Wenn Du möchtest, dass ein HTTP-Server nicht immer auf jeden Request dasselbe Blablah antwortet, dann muss
a) der Client unterschiedliche GET-Anforderungen senden, beim ersten Connect z.B. nur
GET / HTTP/1.1
==> Der Server antwortet dann mit der Default-Datei aus seinem HTML-Docs Verzeichnis mit verschiedenen "Links"
(nein, in Deinem Programm muss das keine echte Datei sein, Du kannst auch Texte aus dem Flash-Speicher senden)

b) Und wenn beim Client dann "auf einen Link geklickt" wird, bekommt der Server einen anderen Request, z.B.
GET /index.html?schalten=PIN1&action=ON HTTP/1.1
==> Und daran müßte der Server erkennen, dass eine einen anderen Request erhalten hat und daraufhin eine andere HTML-Antwort senden als bei dem vorhergehenden Request.

Und damit der Server das kann, muß er einen erhaltenen Request genau erkennen und exakt auswerten, was er als Request erhält.

Wowo vielen Dank, das sind ja gleich kleine Tuts :slight_smile:

Aber wie mkl0815 schon geschrieben hat. Wie das mit dem SMTP Server ist, das er zuerst sich vorstellt.
Z.b. bei Google bekomme ich zuerst "220 mx.google.com ESMTP k8si27402133lbm.40".

Ich müsste also erstmal erkennen, das ein Client Connected ist und dann mit "server.write("Hallo ich bin Arduino bla")" den Text versenden.
Aber wie erkenne ich, das ein Client connected hat ?

Wie mach ich das mit Arduino ?

Du hast dir doch bestimmt schon die Ethernet-Beispiele der IDe angesehen. Dort gibt es ja auch das Beispiel "WebServer".

void loop()
{
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
...

Das ist in der loop schon der entscheidende Teil

1.) EthernetClient client = server.available(); Hier wird mit der Methode EthernetServer::available() geschaut, ob ein Client verfügbar ist.
Wenn ja, enthält die Variable "client" danach einen Zeiger auf das Client-Objekt. Wenn nicht wird die Variable auf NULL gesetzt. Das wird dann mit dem
if (client) { geprüft.

2.) while (client.connected()) { Hier wird dann solange die Client-Verbindung bearbeitet, wie der Client von sich aus die Verbindung nicht beendet hat.

3.) if (client.available()) { Hier wird dann geschaut, ob vom Client Daten geliefert wurden, die man verarbeiten muss.

Mehr ist das nicht. Wenn Du also gleich nach dem if(client) und if(client.connected()) ein client.println("Hallo da draussen"); machst, sollte das der Client bekommen.
Ansonsten schau Dir mal das Beispiel "ChatServer" an.

Mario.

hi,

Die Beispiele erwaten vom Client immer zuerst ne Eingabe

durchaus nicht, sondern eine anfrage.
wenn du im browser die ip-adresse des arduino aufrufst, ist das so eine anfrage. Du mußt Dich überhaupt vom gedanken freimachen, daß ein client ein gerät/computer oder so etwas ist. ein client ist ein programm, meist eben der browser(internet explorer, firefox usw).
der client sagt: "gib mir was", vorher kann der arduino nix schicken.
Du könntest für den pc ein programm schreiben, das auf eine immer wieder wiederholte anfrage des arduino antwortet, aber dann ist eben dieses programm der server.

Hat einer vieleicht ne Ahnung wie ich einen Server erstelle, der nach dem connect zuerst etwas senden z.b. "Hallo und Willkommen blabla".

der arduino sendet ja nach dem connect etwas. eben die startseite (oder bei angepasster adressierung eine andere seite), aber die anforderung muß vom client kommen.

gruß stefan

Hallöchen...

leider funktioniert das mit dem "server.available();" nicht =(

Also nochmal...

Wenn ich in Putty im RAW mit Google 74.125.143.27 Port 25 connecte kommst sofort "220 mx.google.com ESMTP tn2si6018734lab.25".
Ohne das ich vorher irgendwas eingeben musste.

Nehme ich aber das Beispiel "ChatServer" mit geänderten Port 25 und starte zusätzlich den Serialmonitor und connecte dann mit Putty wie
bei google, passiwert nix.
Erst wenn ich einmal Enter eingegeben habe bekomme ich das echo in Putty und im Serialmonitor "We have a new client".

Ich möchte aber gerne nach dem connect, wie bei Google "220 mx.google.com ESMTP tn2si6018734lab.25" , einen Berüßungstext haben.

Das hat auch nix mit Google zu tun. Ich kann das auch mit dem Port 21 (FTP) machen. Ich bekomme von meinem FTP Server auch
eine Begrüßung.

HEUL =(

hier mal der code:

/*
 Chat  Server
 
 A simple server that distributes any incoming messages to all
 connected clients.  To use telnet to  your device's IP address and type.
 You can see the client's input in the serial monitor as well.
 Using an Arduino Wiznet Ethernet shield. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 * Analog inputs attached to pins A0 through A5 (optional)
 
 created 18 Dec 2009
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe
 
 */

#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network.
// gateway and subnet are optional:
byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,2, 10);
IPAddress gateway(192,168,2, 1);
IPAddress subnet(255, 255, 255, 0);


// telnet defaults to port 23
EthernetServer server(25);
boolean alreadyConnected = false; // whether or not the client was connected previously

void setup() {
  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);
  // start listening for clients
  server.begin();
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  Serial.print("Chat server address:");
  Serial.println(Ethernet.localIP());
}

void loop() {
  // wait for a new client:
  EthernetClient client = server.available();

  // when the client sends the first byte, say hello:
  if (client) {
    client.println("Hallo da draussen"); 
    if (!alreadyConnected) {
      // clead out the input buffer:
      client.flush();    
      Serial.println("We have a new client");
      client.println("Hello, client!"); 
      alreadyConnected = true;
    } 

    if (client.available() > 0) {
      // read the bytes incoming from the client:
      char thisChar = client.read();
      // echo the bytes back to the client:
      server.write(thisChar);
      // echo the bytes to the server as well:
      Serial.write(thisChar);
    }
  }
}

Keiner ne Idee :frowning:

Pack mal einfach noch ein "client.flush();" hinter das erste client.println("Hallo da draussen");
Dann sollte es klappen.

Ne, leider funktioniert das auch nicht.
Langsam denke ich, dass mein Ethernet Shild ne Macke hat :frowning:

Ich verstehe auch nicht wie das funktinieren soll. Im Script steht doch nix
davon, das er erkennen kann ob einer Connectet hat.

Es wird doch nur abgefragt ob/wann der Client die ersten Bytes gesendet hat. Das hat doch nix
mit dem Connect zu tun. Ich brauche aber eine Bestätigung zum Connect um auf der Serverseite
zuerst das erste Byte zu senden.

Kannes vieleicht sein, das soetwas in der Lib nicht vorgesehen ist ?

Das stimmt nicht ganz. Bei TCP gibt es einen TCP-Handshake, bei dem der Client zunächst ein Paket sendet (Flag SYN), auf das der Server mit einem Paket (Flag SYN ACK) antwortet. Das wiederum wird vom Client wieder mit einem "ACK" Paket beantwortet. Damit gilt die TCP-Verbindung als hergestellt, obwohl noch keine echten Daten übertragen wurden.
Der Server kann also durchaus wissen, on ein Client da ist oder nicht.
Die spannende Frage ist, wie das in der Lib und auf dem WizNet Chip implementiert ist. Also wann liefert das server.available() einen Client zurück.

Laut Doku wohl erst, wenn der Client tatsächlich was gesendet hat.

Gets a client that is connected to the server and has data available for reading.

Schaut man sich die beiden verantwortlichen Funktionen in der Klasse EthernetServer und EthernetClient an, sieht man auch warum:

EthernetClient EthernetServer::available()
{
  accept();

  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}


int EthernetClient::available() {
  if (_sock != MAX_SOCK_NUM)
    return W5100.getRXReceivedSize(_sock);
  return 0;
}

In EthernetServer::available() sieht man, das zunächst geprüft wird, ob ein Client den Status "Established" (TCP verbindung steht) oder "Close wait" (verbindung soll abgebaut werden, ist aber noch aktiv) hat. Das wäre genau das, was Du benötigst.
Dann wird aber zusätzlich mit dem "if (client.available())" die Funktion EthernetClient::available() aufgerufen. Und die liefert nur was sinnvolles zurück, wenn schon Daten übermittelt wurden "return W5100.getRXReceivedSize(_sock);". Wenn die empfangene Datengröße 0 ist, entspricht das im If-Statement einem "false".

Ich habe mir mal den Spass gemacht und den Code in
wiefolgt angepasst:

EthernetClient EthernetServer::available()
{
  accept();

  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
/**
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
**/
      return client;
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}

Damit passiert dann folgendes:

nc -v 192.168.0.211 25
Connection to 192.168.0.211 25 port [tcp/smtp] succeeded!
Hallo da draussen

Daran, das die Ausgabe der Servers direkt unter der "succeeded" Zeile steht erkennt man, das ich nicht Enter drücken musste.
Was zumindest erstmal meine Theorie beweist. Allerdings wäre ich nun vorsichtig, meine Änderung einfach zu übernehmen. Denn es kann durchaus sein, das sich andere Teile der Lib darauf verlassen, das EthernetServer::available() nur dann einen Client zurückliefert, wenn schon Daten gesendet wurden.

Hier noch der Code mit dem ich das auf dem Arduino getestet habe:

/*
 Chat  Server
 
 A simple server that distributes any incoming messages to all
 connected clients.  To use telnet to  your device's IP address and type.
 You can see the client's input in the serial monitor as well.
 Using an Arduino Wiznet Ethernet shield. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 * Analog inputs attached to pins A0 through A5 (optional)
 
 created 18 Dec 2009
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe
 
 */

#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network.
// gateway and subnet are optional:
byte mac[] = { 
  0xDE, 0xAD, 0xB2, 0x3F, 0xaE, 0xcD };
IPAddress ip(192,168,0, 211);
IPAddress gateway(192,168,0, 252);
IPAddress subnet(255, 255, 255, 0);


// telnet defaults to port 23
EthernetServer server(25);
boolean alreadyConnected = false; // whether or not the client was connected previously

void setup() {
  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);
  // start listening for clients
  server.begin();
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  Serial.print("Chat server address:");
  Serial.println(Ethernet.localIP());
}

void loop() {
  // wait for a new client:
  EthernetClient client = server.available();
  
  // when the client sends the first byte, say hello:
  if (client) {
    Serial.println("client connected");
    
    client.println("Hallo da draussen"); 
    client.flush();
    
    while(client.connected()) {
      
    if (client.available() > 0) {
      // read the bytes incoming from the client:
      char thisChar = client.read();
      // echo the bytes back to the client:
      server.write(thisChar);
      // echo the bytes to the server as well:
      Serial.write(thisChar);
      
     }
    
    }
       Serial.println("client disconnected");
 
  }
}

Bei Bedarf kann ich auch noch den tcpdump Mitschnitt zur Analyse liefern :slight_smile:

hey mkl0815,

boa.... da hast du dir aber viel Mühe gemacht :slight_smile:
Hab nie daran gedacht mal in der Lib zu schauen und zu versuchen da rumzuschreiben. Aber es funktioniert endlich :smiley:

Frage mich nur warum die das so gemacht haben ?

Kann ich eigendlich das, was in der Lib steht, auch so in meinem Arduinoscript benutzen ?
Z.b. EthernetClass::_server_port[sock] etc.

Vielen Dank :grin:

Kann ich eigendlich das, was in der Lib steht, auch so in meinem Arduinoscript benutzen ?

Wenn du alle erforderlichen #include auch einfügst, klar.
Auch die Libraries werden mit deinem Projekt zusammen kompiliert, und prinzipiell ist es egal, in welcher Datei der Code ist oder ob Code-Teile mehrfach vorkommen.

Du kannst auch die (evtl. zu modifizierenden) Library - Dateien direkt in deinen Sketch - Ordner packen und in extra tabs direkt in der IDE bearbeiten.
Achtung :
#include <standard.h> // sucht standard.h zuerst in den libraries
#include "mynonstandard.h" // sucht zuerst im sketch-Ordner
Evtl. helfen auch unterschiedliche Dateinamen, zwischen verschiedenen "Library" - Versionen zu unterscheiden.