epoll Server
-
Hi,
ich suche Code bezüglich eines Servers der mit Epoll, statt mit select geschrieben wurde. Ich hab den ganzen Tag nichts wirkliches als Vorgabe gefunden, würde mich aber interessieren, wie das geht!
Ich danke euch
Gruß, Raven
-
Raven761 schrieb:
Hi,
ich suche Code bezüglich eines Servers der mit Epoll, statt mit select geschrieben wurde. Ich hab den ganzen Tag nichts wirkliches als Vorgabe gefunden, würde mich aber interessieren, wie das geht!
Ich danke euch
Gruß, Raven
Hi,
Tntnet verwendet epoll. Aber nicht statt select, sondern einfach so
.
-
hi
danke, ich hab mir deinen / den quelltext angeschaut und hab auch selber n bissel was gemacht
nur leider klappt es grad nicht, was das löschen von fd's angeht. er zählt die nur hoch, löscht sie aber nicht!
for(n = 0; n < res; ++n) { if(events[n].data.fd == serverSocket1->get_m_sock()) { if(serverSocket1->accept(*serverSocket2) < 0) { perror("Server accept"); continue; } clientfd = serverSocket2->get_m_sock(); cout << "clientfd: " << clientfd << endl; if(clientfd) { setnonblocking(clientfd); ev.events = EPOLLIN; ev.data.fd = clientfd; if(epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev) < 0) { perror("epoll_ctl ADD\n"); exit(1); } } } else { serverSocket3->set_m_sock(events[n].data.fd); n = serverSocket3->recv(s); if(n == 0) { cout << "data received: " << data << endl; if(epoll_ctl(epfd, EPOLL_CTL_DEL, events[n].data.fd, &ev) < 0) { perror("epoll_ctl DEL\n"); } data = 0; cout << "closed connection " << events[n].data.fd << endl; } else if(n < 0) { cout << events[n].data.fd << " error occured, errno: " << errno << endl; } else { data += n; } } }
wie muss das dann richtig heißen wenn ich den fd löschen will?
gruß und danke
Raven
-
Ok, habs selber rausgefunden.
Ich kann es mit close() machen, das funktioniert zuverlässiger als CTL_DEL und entfernt auch automatisch den FD aus der Liste.
-
So, ich hab noch eine Frage!
Ab und zu, wenn es zuviele Zugriffe gleichzeitig gibt, dann bekomme bei "recv" einen Fehler: Bad descriptor.
Jetzt habe ich die Vermutung, dass es daran liegt, weil meine Sockets auf dem Heap erstellt wurden und irgendwie der FD nicht schnell genug übergeben wird.
Kann das sein?
serverSocket3->set_m_sock(events[n].data.fd); n = serverSocket3->recv(s);
Also mit set_m_sock wird der Filedescriptor vom Socket eingestellt!
-
Das kann nicht sein. So etwas gibt es nicht, dass Du etwas nicht schnell genug übergeben kannst.
Der Fehler kann daher kommen, dass der Client die Verbindung beendet. Dann wirst Du zwar geweckt, aber wenn Du recv versucht, geht es schief.
Wobei ich mich wundere, dass Du in einer bestehenden Klasse ständig den FD ändern willst. In der Regel hat man eine Serverklasse pro FD. Dann musst Du bei einem Event die entsprechende Serverklasse finden. Tntnet macht das über eine std::map<int, Jobqueue::JobPtr>. Da wird der FD auf seine zugehörige Klasse gemappt.
-
Hi,
ich versteh das nicht ganz wie du das meinst. Das heißt dein Server hat immer den selben FD? Ich habe leider auch nicht die Funktion gefunden, die du mir vorgeschlagen hast.
Ich habe nur einen Server offen, der läuft bei mir als Thread. Um mehrere Clients zu bedienen, möchte ich die FDs pollen (mit epoll). Was ist da falsch in der Überlegung? Oder wie macht man es effektiver?
Ich mein, dass der Client die Verbindung schließt, das hat bei mir wunderbar funktioniert. Nur wenn das alles viel zu schnell ging, dann habe ich die "Bad Filedescriptor" Nachricht bekommen.
Ich würde mich sehr über eine Antwort deinerseits freuen! Du scheinst es sehr drauf zu haben
Gruß
-
tntnet schrieb:
Wobei ich mich wundere, dass Du in einer bestehenden Klasse ständig den FD ändern willst. In der Regel hat man eine Serverklasse pro FD. Dann musst Du bei einem Event die entsprechende Serverklasse finden.
Meinst du vielleicht die Klasse, wo ich den FD übergebe? Das mit dem "set_m_sock"?
Das ist keine Serverklasse, sondern eine Socketklasse. Ich weiß nicht ob das so stimmt, aber kann natürlich sein dass ich mich gewaltig irre!
Ich bin am überlegen, ob ich die Klasse weglasse, bin mir aber nicht ganz sicher. Dann würde ich "recv" direkt ansprechen und den FD übergeben.
Wie müsste denn das dann alles lauten wenn ich Daten empfangen will? Ich blick da irgendwie überhaupt nicht durch... Und Beispiele, bzw. richtige Erklärungen finde ich leider nicht (manpage usw. reicht mir nicht
Zu select findet man alles mögliche, das finde ich leider etwas schade..
Bitte hilf mir, ich wär dir sehr dankbar!
-
Bei der objektorientierte Programmierung hat man jeweils Klassen, die Entitäten räpresentieren. In C++ versuche ich die Entitäten einer Applikation zu identifizieren. Du hast Filedeskriptoren. Die kapsele ich in einer Klasse. Statt diese eine Socketklasse immer wieder mit unterschiedlichen Filedescriptoren zu füttern, mach doch lieber pro Filedescriptor eine Instanz der Socketklasse. Dann nimmst Du bei Aktivität auf einem Filedescriptor das zu diesem FD zugehörige Klasse und übergibst dieser die Kontrolle.
Ich hoffe, ich habe mich einigermassen verständlich ausgedrückt. Es geht hier eigentlich nicht um epoll, sondern um allgemeines Design.
Sinnvollerweise kapselt man das epoll auch in einer Klasse. In meinem Tntnet habe ich eine Klasse Poller, welches intern entweder poll oder epoll verwendet. Das mache ich so, weil es Betriebssysteme gibt, wie z. B. IBM AIX, die nur poll, aber kein epoll kennen. Tntnet verwendet das schnellere epoll, wenn es auf der Plattform vorhanden ist, ansonsten poll, welches portabler ist. Das epoll taucht nicht am Interface auf, so dass es für den Anwender transparent ist.
-
Hi,
danke für deine Antwort. So wie ich das verstanden habe, müsste ich dann aber jedesmal einen neuen Socket erstellen. Okay, das ist möglich. Aber wie geht man vor?
- zuerst alle Sockets erstellen und in einem Vektor abstellen
oder
- nach und nach die Sockets erstellen
Machst du das mit "JobQueue" bei Tntnet?
Sollte ich das dann auch in Vektoren integrieren und den jenigen dann suchen lassen?
Zur Zeit geht mein Code. Ich habe es jetzt so gemacht, aber es gefällt mir nicht ganz.
Socket serverSocket1, serverSocket2, Sock; serverSocket1.create(); Sock.create(); int listenfd, clientfd, epfd; int poll_timeout = -1; int i, n = 0; int res, state; char buf[MAXRECV + 1]; memset(buf, 0, MAXRECV + 1); string s; listenfd = serverSocket1.get_m_sock(); setnonblocking(listenfd); serverSocket1.bind(serverport); serverSocket1.listen(); // create epoll's descriptors epfd = epoll_create(MAX_CLIENTS); if(epfd < 0) { perror("epoll_create\n"); exit(1); } // add the listen socket to epoll struct epoll_event events[MAX_CLIENTS], e; e.events = EPOLLIN | EPOLLET | EPOLLHUP; // set the events for the epoll function - see man page e.data.fd = listenfd; state = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &e); // set the events to the filedescriptor (listen) if(state < 0) { perror("epoll_ctl, error adding the listen fd\n"); exit(1); } // wait for the events to occur while(logger->continueRun()) { res = epoll_wait(epfd, events, MAX_CLIENTS, poll_timeout); if(res < 0) { perror("epoll_wait"); } else if(res == 0) { poll_timeout = -1; // set timeout to infinite } else { poll_timeout = 100; for(i = 0; i < res; ++i) { if(events[i].data.fd == listenfd) { state = serverSocket1.accept(Sock); if(state < 0) { perror("Server accept"); continue; } clientfd = Sock.get_m_sock(); if(clientfd >= 0) { setnonblocking(clientfd); e.events = EPOLLIN | EPOLLHUP | EPOLLERR; e.data.fd = clientfd; state = epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &e); if(state < 0) { perror("epoll_ctl ADD\n"); exit(1); } } else cout << "fault" << endl; } else { n = ::recv(events[i].data.fd, buf, MAXRECV, 0); if(n == 0 ) { epoll_event e; e.data.fd = events[i].data.fd; state = epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &e); if(state < 0) { perror("epoll_ctl DEL\n"); } ::close(events[i].data.fd); data = 0; } else if(n < 0 && errno != EAGAIN) { cout << events[i].data.fd << " . Error occured, errno: "; perror(""); cout << endl; ::close(events[i].data.fd); } else { data += n; } } } }
**
Könntest du vielleicht über den Code drüber schaun, ob das so OK ist? Oder ist das unsauber?**Gibt es auch eine elegantere Methode, um herauszufinden ob der Client die Verbindung geschlossen hat? Mit EPOLLHUP ging es nicht. recv == 0 ist mir etwas zu wage..
Ich hatte es auch mal probiert, 20 Sockets über eine Schleife zu erstellen, diese dann epoll zu übergeben, sie beim event wieder zu suchen und damit zu arbeiten. aber ich habe oft fehler bekommen und wusste nicht, ob das die richtige methode ist. (falscher FD rausgesucht)Danke für deine Hilfe!
-
In deinem Code finde ich merkwürdig, wie du mit dem Timeout umgehst. Erst steht's auf "Unendlich", wenn du Events reinbekommst, stellst du den Timeout auf eine 10tel Sekunde und wenn der Timeout mal ablaufen sollte, stellst du ihn wieder auf "Unendlich". Warum nimmst du nicht einfach einen fixen Timeout von 5 (oder meinetwegen 10 oder 2 oder 1/2) Sekunden?
Desweiteren:
- Wenn du einen Socket, der bei epoll dabei ist, mitclose
schließt, brauchst du ihn nicht mitepoll_ctl
aus epoll rausnehmen, das klappt von alleine.-
Raven761 schrieb:
Gibt es auch eine elegantere Methode, um herauszufinden ob der Client die Verbindung geschlossen hat? Mit EPOLLHUP ging es nicht. recv == 0 ist mir etwas zu wage..
recv==0 ist die perfekte Möglichkeit und genau dafür gedacht. Vertrau darauf.
- Warum schließt du Sockets sofort, wenn du was von ihnen empfangen hast? Oder ist das Protokoll-gerecht?
- Falls du zu jedem Socket zusätzliche Daten mitschleppen willst, geht das etwa so (Code ungetestet und nur hingeschmiert):
struct ConnInfo { int socket; int irgendwas; // ... // Um neue Daten zu senden void sendData( const char* data, size_t size ) { to_send.insert( to_send.end(), data, data+size ); trySend(); } // Um alles zu senden, was noch nicht gesendet wurde void sendRest() { if ( to_send.size() ) { // Soviel wie möglich senden int s = ::send( socket, &to_send[0], to_send.size(), 0 ); // Gesendetes aus dem Buffer rausnehmen if ( s >= 0 ) to_send.erase( to_send.begin(), to_send.begin()+s ); } } private: // Daten, die noch gesendet werden müssen, evtl mehrere Buffer in queue, vector unperformant std::vector<char> to_send; }; void addToPoll( int poll_handle, int socket, ConnInfo* info ) { epoll_event ev; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; ev.data.ptr = info; if ( -1 == epoll_ctl( poll_handle, EPOLL_CTL_ADD, socket, &ev ) ) // Irgendeine Fehlerbehandlung, such's dir aus std::cout << "Error in epoll_ctl: " << strerror(errno) << "\n"; } // Listener hinzufügen: addToPoll( epfd, listenfd, NULL /*keine zusätzliche Info, damit erkennen wir den Listener*/ ); // Beim pollen: int res = epoll_wait( epfd, events, MAX_CLIENTS, poll_timeout ); if ( res < 0 ) break; // Oder irgendwas for ( int i=0; i<res; ++i ) { ConnInfo* conn = static_cast<ConnInfo*>( events[i].data.ptr ); if ( conn == NULL ) // Listener { // Accept und so // ... // Client hinzufügen ConnInfo* ci = new ConnInfo(); ci->socket = new_socket; addToPoll( epfd, new_socket, ci ); } else { if ( events[i].events & (EPOLLERR | EPOLLHUP) ) { // Fehler close( conn->socket ); delete conn; continue; } if ( events[i] & EPOLLOUT ) { // Wieder zum Senden bereit, weitere Daten senden falls vorhanden conn->sendRest(); } // Kein "else if" hier, nur "if", darf ruhig und gerne beides auftreten if ( events[i] & EPOLLIN ) { // Empfangen, bei recv<=0 den Socket wieder mit close + delete rausnehmen // Für recv > 0: if ( ! handleMsg( conn, msg, msg_size ) ) ; // Verbindung soll geschlossen werden, Socket wieder rausnehmen } } } // Und handleMsg irgendwie so: void handleMsg( ConnInfo* conn, const char* msg, size_t msg_size ) { conn->sendData( answer, answer_size ); }
-
Hi,
also Vielen Dank, dass es Leute gibt, die sich mit dem Thema auskennen!
Zu deinen Fragen:
- Das mit dem Timeout habe ich bereits geändert. Ich fand es dann auch albern.
- Den Socket schließe ich, weil ich ihn eigentlich danach nicht mehr benötige. Mein Server soll nur Daten für einen Test empfangen, selber soll er nichts senden! Er nimmt nur Daten an und misst die Durchsatzrate. Dafür wird nach dem Accept gleich eine "Stoppuhr" gestartet, die bei recv == 0 gestoppt wird. Mit den empfangenen Daten kann man dann die Durchsatzgeschwindigkeit messen.Nicht destotrotz gefällt es mir sehr gut was du mir vorgestellt hast. Die Möglichkeit zum Senden der restlichen Daten werde ich mir merken!
Ich habe gestern und heute an meinem Code weitergearbeitet und jetzt funktioniert er auch ganz gut. Erste Tests mit 3 Blade Servern haben sehr gute Erfolge gezeigt! Wenn ich fertig bin, poste ich meinen Code für andere hier rein
Danke vielmals!
-
Hi,
mein Server funktioniert soweit ganz gut. Allerdings bekomme ich ab und zu nach einer gewissen Zeit beim hinzufügen des Sockets einen "EBADF" error. Das heißt, der File Descriptor ist kaputt oder es stimmt irgendwas anderes nicht. Allerdings weiß ich nicht woran das liegen kann!
Hier mein Code:
void Server::addFd(int fd) { setnonblocking(fd); struct epoll_event e; e.events = EPOLLIN | EPOLLHUP; e.data.fd = fd; int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &e); if(ret < 0) { perror("Add FD: epoll_ctl\n"); exit(1); } } void Server::removeFd(int fd) { int ret = ::close(fd); if(ret < 0) { perror("Failed closing socket"); } } void Server::serverThread() { long long n; //data client[MAX_CLIENTS]; map<string, data> cl; map<int, string> fd; Socket serverSocket1, Sock; serverSocket1.create(); Sock.create(); int listenfd, clientfd; int i; int res, state; bool rebuildPollFd = false; char buf[MAXRECV + 1]; memset(buf, 0, MAXRECV + 1); string s; listenfd = serverSocket1.get_m_sock(); setnonblocking(listenfd); serverSocket1.bind(serverport); serverSocket1.listen(); // create epoll's descriptors epfd = epoll_create(MAX_CLIENTS); if(epfd < 0) { perror("epoll_create\n"); exit(1); } // add the listen socket to epoll struct epoll_event events[MAX_CLIENTS]; addFd(listenfd); // wait for the events to occur while(logger->continueRun()) { res = epoll_wait(epfd, events, MAX_CLIENTS, 100); if(res < 0) { if((errno != EINTR) && (errno != EAGAIN)) { perror("epoll_wait"); } } else { for(i = 0; i < res; ++i) { if(events[i].data.fd == listenfd) { state = serverSocket1.accept(Sock); if(state < 0) { perror("Server accept"); continue; } string ip = serverSocket1.get_networkip(); clientfd = Sock.get_m_sock(); cout << clientfd << endl; if(clientfd >= 0) { addFd(clientfd); } else { cout << "Fault!" << endl; } cl[ip].time = stopwatch(); cl[ip].data = 0; fd[clientfd] = ip; cl[ip].ip = ip; } else { n = ::recv(events[i].data.fd, buf, MAXRECV, 0); string iip = fd[events[i].data.fd]; if(n == 0) { cl[iip].time = (stopwatch() - cl[iip].time); int tmp = cl[iip].data*1000000/cl[iip].time; // average rate logger->debug(2, "IP: " + cl[iip].ip + " \tData received: " + dTos(cl[iip].data)+ " " + conv.unit + " \tTransferrate: " + dTos(tmp) + " " + conv.unit + "/s"); removeFd(events[i].data.fd); cl[iip].data = 0; } else if(n < 0 && errno != EAGAIN) { cl[iip].time = (stopwatch() - cl[iip].time); cout << events[i].data.fd << " . Error occured, errno: "; perror(""); cout << endl; removeFd(events[i].data.fd); } else { cl[iip].data += n; } } }
Habt ihr ne Idee? Wie kommt der Fehler überhaupt zustande? Kann es am Client liegen?
Gruß und danke vielmals,
Raven