Schreiben Sie eine funktionierende Bare-Bones-IRCd

8

Ein bisschen ungewöhnlich, aber hey, warum nicht? :) :)

Das Ziel: Schreiben Sie einen funktionierenden IRC-Daemon in der Sprache Ihrer Wahl, der Barebone-Funktionen in möglichst wenigen Zeichen bietet. Solange es die folgenden Kriterien erfüllt, muss es die IRC-RFCs nicht vollständig erfüllen (dies würde die Herausforderung erheblich weniger Spaß machen), es muss nur funktionieren.

Bedarf:

  • Clients müssen in der Lage sein, eine Verbindung zu Port 6667 herzustellen und diesen zu verwenden. Zumindest irssi und XChat müssen in der Lage sein, mit einer Standardkonfiguration eine erfolgreiche Verbindung herzustellen.
  • Benutzer müssen in der Lage sein, ihren eigenen Spitznamen anzugeben, ihn zu ändern, während sie bereits verbunden sind, Kanäle beizutreten, Kanäle zu verlassen und sauber zu beenden (dh QUIT).
  • Kanäle sollten wie gewohnt erstellt werden. Wenn Sie einem Kanal beitreten, in dem sich keine Benutzer befinden, wird er "erstellt". Sie müssen nicht hartnäckig sein.
  • Benutzer müssen in der Lage sein, sowohl Kanalnachrichten als auch private Nachrichten (dh an andere Benutzer) zu senden.
  • Der WHOISBefehl muss ebenso implementiert werden wie PING/ PONG, LISTund NAMES(hauptsächlich, um die Kunden zufrieden zu stellen).

Beschränkungen:

  • Sie dürfen keine Bibliotheken von Drittanbietern verwenden (einschließlich nicht zum Kern gehörender E / A-Bibliotheken). Es sind nur Standardbibliotheken zulässig, die mit der von Ihnen verwendeten Plattform geliefert werden.
  • Wenn Ihre Implementierung explizite Unterstützung für IRC in der Standardbibliothek enthält, können Sie diese auch nicht verwenden. Die Netzwerkfunktionalität der Standardbibliothek ist natürlich in Ordnung.
  • Ihre Einreichung muss unabhängig ausgeführt werden können. Kein kluges MIRC-Scripting :)

Modi, Kicks usw. müssen nicht implementiert werden (es sei denn, dies ist erforderlich, damit es mit den oben genannten Clients funktioniert). SSL ist auch nicht notwendig. Nur die oben genannten Barebone-Funktionen, um die Herausforderung kurz und unterhaltsam zu halten.

Weitere Informationen zu IRC finden Sie hier . Die RFCs sind 1459 und 2812 (ich kann aufgrund meines mangelnden Rufs keine direkte Verknüpfung zu ihnen herstellen). Die kürzeste funktionale und anforderungskonforme Einreichung gewinnt!

Sven Slootweg
quelle
2
Vielleicht möchten Sie Hintergrundinformationen darüber geben, was eine IRCd (oder sogar eine IRC) für Personen ist, die mit IRC nicht vertraut sind.
Martin Ender
2
Haben Sie selbst eine geschrieben, um eine Vorstellung davon zu bekommen, wie viel Zeit es dauern würde und wie viel Code erforderlich wäre? Beispielcode ohne Golf würde den Leuten helfen, abzuschätzen, ob dies die richtige Größe für die Zeit ist, die sie zufällig frei haben.
Trichoplax
@ MartinBüttner Beitrag bearbeitet. Nicht genug Ruf, um direkt mit den RFCs zu verknüpfen, aber mit den RFC-Nummern sollten sie nicht schwer zu finden sein.
Sven Slootweg
@githubphagocyte Yup, ich habe schon einmal versucht, selbst eines zu schreiben (obwohl es eher ein Experiment als alles andere ist). Ich würde sagen, dass ein erfahrener Entwickler in einer dynamischen Sprache (denken Sie an Python, Node.js) in der Lage sein würde, eine solche grundlegend funktionierende IRCd in höchstens 1-2 Stunden in nicht Golf-Form zusammenzustellen. Wahrscheinlich weniger, wenn Sie bereits mit den RFCs vertraut sind.
Sven Slootweg
Welchen Port soll der Server abhören? 6667, 194 oder was anderes?
Nyuszika7h

Antworten:

3

C ++ (teilweise Golf) 5655 (wobei CRLF für 1 zählt)

Dies wird in VS 2013 kompiliert (verwendet Auto, Lambdas und Winsock). Es schien zu funktionieren, bevor ich es Golf gespielt habe. Wenn ich es nicht vermasselt habe, sollte es immer noch in Ordnung sein. Einer der Gründe, warum es so groß ist, ist, dass die numerischen Antworten, die ich zurückschicke, den im RFC angegebenen Text enthalten - ich weiß nicht, ob dies notwendig ist oder nicht. Ich habe es mit KVirc getestet, weil es portabel läuft (keine Software auf meinem PC installieren darf!). KVirc scheint mit meinem Server zu funktionieren, aber ich weiß nichts über andere Clients - ich habe getan, was ich dachte, dass der RFC sagte, aber viel davon ist so wenig spezifiziert, dass ich es hoffentlich richtig verstanden habe.

Der Server verarbeitet DIE, KILL, NICK, USER, MODE, WHOIS, WHO, JOIN, PART, THEMA, LISTE, NAMEN, PRIVMSG, USERS, PING, PONG und QUIT in unterschiedlichem Maße. Für die meisten von ihnen gebe ich die erforderlichen Antworten zurück, einschließlich der meisten Überprüfungen, die erforderlich sind, um die angegebenen Fehlerantworten zurückzugeben. Für einige von ihnen betrüge ich:

  • USERS gibt immer 446 zurück "USERS wurde deaktiviert"
  • Die Kanal-MODE-Nachricht gibt immer 477 "Kanal unterstützt keine Modi" zurück.
  • Die Benutzer-MODE-Nachricht funktioniert ordnungsgemäß, aber die Flags werden von den anderen Befehlen nicht verwendet

Ich denke, es ist nur teilweise Golf gespielt, weil ich nicht sehr gut Golf spielen kann. Wenn Sie also etwas Großes sehen, bearbeiten Sie bitte die Antwort und korrigieren Sie es.

Hier ist die Golfversion

#include<time.h>
#include<map>
#include<list>
#include<vector>
#include<string>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<winSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define P m.p[0]
#define Q m.p[1]
#define E SOCKET_ERROR
#define I INVALID_SOCKET
#define T c_str()
#define H second
#define Y first
#define S string
#define W stringstream
#define G else
#define J G if
#define A auto
#define Z bool
#define B empty()
#define K return
#define N 513
#define X(n,x)if(x){r=n;goto f;};
#define U(x,i)for(A i=x.begin();i !=x.end();++i)
#define L(x)U(x,i)
#define V(x)for(A i=x.begin();i!=--x.end();++i)
#define M(x)FD_ZERO(&x);FD_SET(t.s,&x);L(l){FD_SET(i->s,&x);}
#define R(a,b,...){M v={a,b,{__VA_ARGS__}};w(d,v);}
#define F(x)}J(!_stricmp(m.c.T,x)){
using namespace std;struct C{S t;list<S>n;};struct M{S f;S c;vector<S>p;};struct D{SOCKET s;SOCKADDR_IN a;int m,l,i;char b[N];S n,u,h,r;time_t p,q;};map<S,C>c;list<D>l;void w(D d,M m);void x(D&t,S r,Z n){L(c)i->H.n.remove(t.n);L(l){A d=*i;if(d.n!=t.n)R(d.n,"QUIT",t.n,r)J(n)R("","ERROR","QUIT",r)}closesocket(t.s);t.s=I;}void w(D d,M m){S s=(!m.p.B?":"+m.f+" ":"")+m.c;V(m.p)s+=" "+*i;s+=" :"+*m.p.rbegin()+"\r\n";int c=0;do{int b=send(d.s,s.T+c,s.size()-c,0);if(b>0)c+=b;G x(d,"send error",0);}while(s.size()-c>0);}Z e(D&d,M m){A z=m.p.size();if(!_stricmp(m.c.T,"DIE")){K 1;F("KILL")if(z<1)R("","461",d.n,"USER","Not enough parameters")G{Z f=0;L(l)if(i->n==P){f=1;x((*i),P,1);}if(f==0)R("","401",d.n,P,"No such nick/channel")}F("NICK")if(z<1)R("","431",d.n,"No nickname given")G{Z f=0;L(l)if(i->n==P)f=1;if(f==1)R("","433",d.n,"Nickname is already in use")G d.n=P;}F("USER")if(z<4)R("","461",d.n,"USER","Not enough parameters")G{Z f=0;L(l)if(i->u==P)f=1;if(f==1)R("","462",d.n,"Unauthorized channel (already registered)")G{d.u=P;d.m=atoi(Q.T);d.h=m.p[2];d.r=m.p[3];R("","001",d.n,"Welcome to the Internet Relay Network "+d.n+"!"+d.u+"@"+d.h)}}F("MODE")if(z<1)R("","461",d.n,"MODE","Not enough parameters")J(P==d.n){if(z<2)R("","221",d.n,S("")+(d.m&2?"+w":"-w")+(d.m&3?"+i":"-i"))G{A x=(147-Q[1])/14;if(Q[0]=='+'){d.m|=1<<x;}G{d.m&=~(1<<x);}}}G R("","477",d.n,P,"Channel doesn't support modes")F("WHOIS")if(z<1)R("","431",d.n,"No nickname given")G{Z f=0;L(l)if(i->n==P){f=1;R("","311",d.n,(i->n,i->u,i->h,"*",i->r))}if(f==1)R("","318",d.n,P,"End of WHOIS")G R("","401",d.n,P,"No such nick/channel")}F("WHO")L(c[P].n)U(l,j)if(*i==j->n)R("","352",d.n,P,j->u,j->h,"*",j->n,"",j->r)R("","315",d.n,P,"End of WHO")F("JOIN")if(z<1)R("","461",d.n,"JOIN","Not enough parameters")J(P=="0")L(c){U(i->H.n,j)if(*j==d.n)R("","PART",i->Y,d.n)i->H.n.remove(d.n);}G{A&C=c[P];Z f=0;L(C.n)if(*i==d.n){f=1;}if(f==0){C.n.push_back(d.n);R(d.n,"JOIN",P)if(C.t.B)R("","331",d.n,P,"No topic is set")G R("","332",d.n,P,C.t)S q;L(C.n)q+=(q.B?"":" ")+*i;R("","353",d.n,"=",P,q)R("","366",d.n,P,"End of NAMES")}}F("PART")if(z<1)R("","461",d.n,"PART","Not enough parameters")G{Z f=0;A&C=c[P];L(C.n)if(*i==d.n)f=1;C.n.remove(d.n);if(f){if(z<2)m.p.push_back(d.n);R(d.n,"PART",P,Q)}G R("","442",d.n,P,"You're not on that channel")}F("TOPIC")if(z<1)R("","461",d.n,"TOPIC","Not enough parameters")G{A&C=c[P];if(z<2){C.t="";R("","331",d.n,P,"No topic is set")}G{C.t=Q;R("","332",d.n,P,C.t)}}F("LIST")if(z<1){L(c){W ss;ss<<i->H.n.size();R("","322",d.n,i->Y,ss.str(),i->H.t.B?"No topic is set":i->H.t)}R("","323",d.n,"End of LIST")}G{W ss;ss<<c[P].n.size();R("","322",d.n,P,ss.str(),c[P].t.B?"No topic is set":c[P].t)R("","323",d.n,"End of LIST")}F("NAMES")if(z<1){L(c){S q;U(i->H.n,j)q+=(q.B?"":" ")+*j;R("","353",d.n,"=",i->Y,q)}R("","366",d.n,"End of NAMES")}G{S q;L(c[P].n)q+=(q.B?"":" ")+*i;R("","353",d.n,"=",P,q)R("","366",d.n,P,"End of NAMES")}F("PRIVMSG")if(z<1)R("","411",d.n,"No recipient given(PRIVMSG)")J(z<2)R("","412",d.n,"No text to send")G{Z f=0;A from=d.n;L(c)if(i->Y==P){f=1;U(i->H.n,k)U(l,j)if(*k==j->n){A d=*j;R(from,"PRIVMSG",d.n,Q)}}if(f==0)L(l)if(i->n==P){f=1;A d=*i;R(from,"PRIVMSG",d.n,Q)}if(f==0)R("","401",d.n,P,"No such nick/channel")}F("USERS")R("","446",d.n,"USERS has been disabled")F("PING")R("","PONG",P,Q)F("PONG")d.p=time(NULL)+60;d.q=0;F("QUIT")if(!z)m.p.push_back(d.n);x(d,P,1);}G{R("","421",d.n,m.c,"Unknown command")}K 0;}M g(char*d){M m;char*n=d;while(*d!='\0'){if(m.c.B){if(*d==':'){for(;*d!='\0'&&*d!=' ';++d);*d='\0';m.f=n+1;n=++d;}for(;*d!='\0'&&*d!=' ';++d);*d='\0';m.c=n;n=++d;}J(*d==':'){for(;*d!='\0';++d);m.p.push_back(n+1);n=++d;}G{for(;*d!='\0'&&*d!=' ';++d);*d='\0';m.p.push_back(n);n=++d;}}K m;}int main(){int r;WSADATA u;SOCKADDR_IN la;la.sin_family=AF_INET;la.sin_port=htons(6667);la.sin_addr.s_addr=htonl(INADDR_ANY);timeval h;h.tv_sec=0;h.tv_usec=10000;fd_set rs,ws,es;D t;t.n="IRCd";X(1,(0!=WSAStartup(MAKEWORD(2,2),&u)))X(2,(I==(t.s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))))X(3,(E==bind(t.s,(SOCKADDR*)&la,sizeof(la))))X(4,(E==listen(t.s,SOMAXCONN)))while(1){M(rs)M(ws)M(es)X(5,(E==select(0,&rs,&ws,&es,&h)))X(6,(FD_ISSET(t.s,&es)))if(FD_ISSET(t.s,&rs)){D d={};d.l=sizeof(d.a);d.s=accept(t.s,(SOCKADDR*)&d.a,&d.l);X(7,(I==d.s))W s;s<<inet_ntoa(d.a.sin_addr)<<":"<<ntohs(d.a.sin_port);d.n=s.str();d.p=time(NULL)+60;d.q=0;l.push_back(d);}L(l){D&d=*i;if(d.p>0&&time(NULL)>d.p){R("","PING",d.n)d.p=0;d.q=time(NULL)+60;}if(d.q>0&&time(NULL)>d.q)x(d,"PONG",1);if(FD_ISSET(d.s,&es))x(d,"select except",0);if(FD_ISSET(d.s,&rs)){int b=recv(d.s,d.b+d.i,sizeof(d.b)-d.i-1>0,0);if(b>0)d.i+=b;G x(d,"recv error",0);char*y=d.b+d.i-2;if(!strcmp(y,"\r\n")){*y++='\0';*y='\0';M m=g(d.b);memset(d.b,0,N);d.i=0;if(d.p>0&&time(NULL)<d.p){d.p=time(NULL)+60;d.q=0;}if(e(d,m))X(0,1)}}}l.remove_if([](const D&d){K d.s==I;});}r=0;f:L(l)x(*i,"exit",0);x(t,"exit",0);WSACleanup();K r;}

Hier ist die meist ungolfed Version (verwendet noch einige Makros):

#include <time.h>
#include <map>
#include <list>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <winSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define READ_BUFFER_SIZE 513
#define EXIT_IF(n,x) if (x) { retval=n; goto finished; };
#define LOOPX(x,it) for (auto it = x.begin(); it != x.end(); ++it)
#define LOOP(x) LOOPX(x,it)
#define LOOP2(x) for (auto it = x.begin(); it != --x.end(); ++it)
#define MAKE_SET(x) FD_ZERO(&x); FD_SET(listener.socket, &x); LOOP(socket_list) { FD_SET(it->socket, &x); }
#define RESPOND(a, b, ...) { message response = {a, b, {__VA_ARGS__}}; tell(data, response); }
#define CASE(x) } else if (!_stricmp(msg.command.c_str(),x)) { std::cout << "Received " << x << " from " << data.nickname << std::endl;
struct channel { std::string topic;  std::list<std::string> nicknames; };
struct message { std::string prefix; std::string command; std::vector<std::string> params; };
struct socket_data { SOCKET socket; SOCKADDR_IN address; int mode,address_length,read_buffer_index; char read_buffer[READ_BUFFER_SIZE]; std::string nickname,username,servername,realname; time_t ping_timer,pong_timer; };
std::map<std::string,channel> channels;
std::list<socket_data> socket_list;
void tell(socket_data data, message msg);
void disconnect(socket_data& target, std::string reason, bool notify)
{
    LOOP(channels) it->second.nicknames.remove(target.nickname);
    LOOP(socket_list)
    {
        auto data = *it;
        if (data.nickname != target.nickname) RESPOND(data.nickname, "QUIT", target.nickname, reason)
        else if (notify) RESPOND("", "ERROR", "QUIT", reason)
    }
    closesocket(target.socket);
    target.socket = INVALID_SOCKET;
    std::cout << "Disconnected " << target.nickname << " reason=" << reason << std::endl;
}
void print(socket_data data, message msg, char *heading)
{
    std::cout << heading << ":\n  " << inet_ntoa(data.address.sin_addr) << ":" << ntohs(data.address.sin_port) << "\n";
    if (!msg.prefix.empty()) std::cout << "  Prefix=" << msg.prefix << "\n";
    std::cout << "  Command=" << msg.command;
    int count = 0; LOOP(msg.params) std::cout << "\n  Param[" << count++ << "]=" << *it;
    std::cout << std::endl;
}
void tell(socket_data data, message msg)
{
    print(data, msg, "Response");

    std::string str = (!msg.prefix.empty() ? ":" + msg.prefix + " " : "") + msg.command;
    LOOP2(msg.params) str += " " + *it;
    str += " :" + *msg.params.rbegin() + "\r\n";

    int start = 0;
    do
    {
        int bytes = send(data.socket, str.c_str() + start, str.size() - start, 0);
        if (bytes > 0) start += bytes; else disconnect(data, "send error", 0);
    }
    while (str.size() - start > 0);
}
bool process(socket_data &data, message msg)
{
    print(data, msg, "Request");

    auto size = msg.params.size();
    auto first = size<1 ? "" : msg.params[0], second = size<2 ? "" : msg.params[1];
    if (!_stricmp(msg.command.c_str(), "DIE")) { return true;
    // and now all the cases
    CASE("KILL")    if (size<1)
                        RESPOND("", "461", data.nickname, "USER", "Not enough parameters")
                    else
                    {
                        bool found = false;
                        LOOP(socket_list) if (it->nickname == first) { found = true; disconnect((*it), first, 1); }
                        if (found == false) RESPOND("", "401", data.nickname, first, "No such nick/channel")
                    }
    CASE("NICK")    if (size<1)
                        RESPOND("", "431", data.nickname, "No nickname given")
                    else
                    {
                        bool found = false;
                        LOOP(socket_list) if (it->nickname == first) found = true;
                        if (found == true) RESPOND("", "433", data.nickname, "Nickname is already in use")
                        else data.nickname = first;
                    }
    CASE("USER")    if (size<4)
                        RESPOND("", "461", data.nickname, "USER", "Not enough parameters")
                    else
                    {
                        bool found = false;
                        LOOP(socket_list) if (it->username == first) found = true;
                        if (found == true) RESPOND("", "462", data.nickname, "Unauthorized command (already registered)")
                        else
                        {
                            data.username = first; data.mode = atoi(second.c_str()); data.servername = msg.params[2];  data.realname = msg.params[3];
                            RESPOND("", "001", data.nickname, "Welcome to the Internet Relay Network " + data.nickname + "!" + data.username + "@" + data.servername)
                        }
                    }
    CASE("MODE")    if (size<1)
                        RESPOND("", "461", data.nickname, "MODE", "Not enough parameters")
                    else if (first == data.nickname)
                    {
                        if (size < 2)
                            RESPOND("", "221", data.nickname, std::string("") + (data.mode & 2 ? "+w" : "-w") + (data.mode & 3 ? "+i" : "-i"))
                        else
                        {
                            auto x = (147 - second[1]) / 14;
                            if (second[0] == '+')
                            {
                                data.mode |= 1 << x;
                                std::cout << "set " << first << " mode bit " << x << "w=2, i=3" << std::endl;
                            }
                            else
                            {
                                data.mode &= ~(1 << x);
                                std::cout << "clear " << first << " mode bit " << x << "w=2, i=3" << std::endl;
                            }
                        }
                    }
                    else
                        RESPOND("", "477", data.nickname, first, "Channel doesn't support modes")
    CASE("WHOIS")   if (size < 1)
                        RESPOND("", "431", data.nickname, "No nickname given")
                    else
                    {
                        bool found = false;
                        LOOP(socket_list) if (it->nickname == first) { found = true; RESPOND("", "311", data.nickname, (it->nickname, it->username, it->servername, "*", it->realname)) }
                        if (found == true) RESPOND("", "318", data.nickname, first, "End of WHOIS")
                        else RESPOND("", "401", data.nickname, first, "No such nick/channel")
                    }
    CASE("WHO")     LOOP(channels[first].nicknames) LOOPX(socket_list, dit) if (*it == dit->nickname)
                        RESPOND("", "352", data.nickname, first, dit->username, dit->servername, "*", dit->nickname, "", dit->realname)
                    RESPOND("", "315", data.nickname, first, "End of WHO")
    CASE("JOIN")    if (size < 1)
                        RESPOND("", "461", data.nickname, "JOIN", "Not enough parameters")
                    else if (first == "0")
                        LOOP(channels) { LOOPX(it->second.nicknames, dit) if (*dit == data.nickname) RESPOND("","PART", it->first, data.nickname) it->second.nicknames.remove(data.nickname); }
                    else
                    {
                        auto& channel = channels[first];
                        bool found = false;
                        LOOP(channel.nicknames) if (*it == data.nickname) { found = true; }
                        if (found == false)
                        {
                            channel.nicknames.push_back(data.nickname);
                            RESPOND(data.nickname, "JOIN", first)
                            if (channel.topic.empty()) RESPOND("", "331", data.nickname, first, "No topic is set")
                            else RESPOND("", "332", data.nickname, first, channel.topic)
                            std::string list; LOOP(channel.nicknames) list += (list.empty() ? "" : " ") + *it;
                            RESPOND("", "353", data.nickname, "=", first, list)
                            RESPOND("", "366", data.nickname, first, "End of NAMES")
                        }
                    }
    CASE("PART")    if (size < 1)
                        RESPOND("", "461", data.nickname, "PART", "Not enough parameters")
                    else
                    {
                        bool found = false;
                        auto &channel = channels[first];
                        LOOP(channel.nicknames) if (*it == data.nickname) found = true;
                        channel.nicknames.remove(data.nickname);
                        if (found)
                        {
                            if (size < 2) msg.params.push_back(data.nickname);
                            RESPOND(data.nickname, "PART", first, second)
                        }
                        else RESPOND("", "442", data.nickname, first, "You're not on that channel")
                    }
    CASE("TOPIC")   if (size < 1)
                        RESPOND("", "461", data.nickname, "TOPIC", "Not enough parameters")
                    else
                    {
                        auto& channel = channels[first];
                        if (size < 2) { channel.topic = ""; RESPOND("", "331", data.nickname, first, "No topic is set") }
                        else { channel.topic = second; RESPOND("", "332", data.nickname, first, channel.topic) }
                    }
    CASE("LIST")    if (size < 1)
                    {
                        LOOP(channels)
                        {
                            std::stringstream ss; ss << it->second.nicknames.size();
                            RESPOND("", "322", data.nickname, it->first, ss.str(), it->second.topic.empty() ? "No topic is set" : it->second.topic)
                        }
                        RESPOND("", "323", data.nickname, "End of LIST")
                    }
                    else
                    {
                        std::stringstream ss; ss << channels[first].nicknames.size();
                        RESPOND("", "322", data.nickname, first, ss.str(), channels[first].topic.empty() ? "No topic is set" : channels[first].topic)
                        RESPOND("", "323", data.nickname, "End of LIST")
                    }
    CASE("NAMES")   if (size < 1)
                    {
                        LOOP(channels)
                        {
                            std::string list; LOOPX(it->second.nicknames, dit) list += (list.empty() ? "" : " ") + *dit;
                            RESPOND("", "353", data.nickname, "=", it->first, list)
                        }
                        RESPOND("", "366", data.nickname, "End of NAMES")
                    }
                    else
                    {
                        std::string list; LOOP(channels[first].nicknames) list += (list.empty() ? "" : " ") + *it;
                        RESPOND("", "353", data.nickname, "=", first, list)
                        RESPOND("", "366", data.nickname, first, "End of NAMES")
                    }
    CASE("PRIVMSG") if (size < 1)
                        RESPOND("", "411", data.nickname, "No recipient given (PRIVMSG)")
                    else if (size < 2)
                        RESPOND("", "412", data.nickname, "No text to send")
                    else
                    {
                        bool found = false;
                        auto from = data.nickname;
                        LOOP(channels) if (it->first == first)
                        {
                            found = true;
                            LOOPX(it->second.nicknames, nit) LOOPX(socket_list, dit) if (*nit == dit->nickname) { auto data = *dit; RESPOND(from, "PRIVMSG", data.nickname, second) }
                        }
                        if (found == false)
                            LOOP(socket_list) if (it->nickname == first)
                            {
                                found = true;
                                auto data = *it; RESPOND(from, "PRIVMSG", data.nickname, second)
                            }
                        if (found == false)
                            RESPOND("", "401", data.nickname, first, "No such nick/channel")
                    }
    CASE("USERS")   RESPOND("", "446", data.nickname, "USERS has been disabled")
    CASE("PING")    RESPOND("", "PONG", first, second)
    CASE("PONG")    data.ping_timer = time(NULL) + 60; data.pong_timer = 0;
    CASE("QUIT")    if (!size) msg.params.push_back(data.nickname);
                    disconnect(data, first, 1);
    // end of the cases
    } else {
        std::cout << "Received invalid message from " << data.nickname << " msg=" << msg.command << std::endl;
        RESPOND("", "421", data.nickname, msg.command, "Unknown command")
    }
    return false;
}
message parse(char *data)
{
    message msg;
    char *pointer = data;
    while (*data != '\0')
    {
        if (msg.command.empty())
        {
            if (*data == ':')
            {
                for (; *data != '\0' && *data != ' '; ++data); *data = '\0';
                msg.prefix = pointer + 1;
                pointer = ++data;
            }
            for (; *data != '\0' && *data != ' '; ++data); *data = '\0';
            msg.command = pointer;
            pointer = ++data;
        }
        else if (*data == ':')
        {
            for (; *data != '\0'; ++data);
            msg.params.push_back(pointer+1);
            pointer = ++data;
        }
        else
        {
            for (; *data != '\0' && *data != ' '; ++data); *data = '\0';
            msg.params.push_back(pointer);
            pointer = ++data;
        }
    }
    return msg;
}
int main()
{
    int retval;
    WSADATA wsaData;
    SOCKADDR_IN listen_address; listen_address.sin_family = AF_INET; listen_address.sin_port = htons(6667); listen_address.sin_addr.s_addr = htonl(INADDR_ANY);
    timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 10000;
    fd_set socket_read_set, socket_write_set, socket_except_set;
    socket_data listener; listener.nickname = "IRCd";
    EXIT_IF(1, (0 != WSAStartup(MAKEWORD(2, 2), &wsaData)))
    EXIT_IF(2, (INVALID_SOCKET == (listener.socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))))
    EXIT_IF(3, (SOCKET_ERROR == bind(listener.socket, (SOCKADDR *)&listen_address, sizeof(listen_address))))
    EXIT_IF(4, (SOCKET_ERROR == listen(listener.socket, SOMAXCONN)))
    while (1)
    {
        MAKE_SET(socket_read_set) MAKE_SET(socket_write_set) MAKE_SET(socket_except_set)
        EXIT_IF(5, (SOCKET_ERROR == select(0, &socket_read_set, &socket_write_set, &socket_except_set, &timeout)))
        EXIT_IF(6, (FD_ISSET(listener.socket, &socket_except_set)))
        if (FD_ISSET(listener.socket, &socket_read_set))
        {
            socket_data data = {}; // zero everything
            data.address_length = sizeof(data.address);
            data.socket = accept(listener.socket, (SOCKADDR *)&data.address, &data.address_length);
            EXIT_IF(7, (INVALID_SOCKET == data.socket))
            std::stringstream ss; ss << inet_ntoa(data.address.sin_addr) << ":" << ntohs(data.address.sin_port); data.nickname = ss.str();
            data.ping_timer = time(NULL)+60; data.pong_timer = 0;
            socket_list.push_back(data);
            std::cout  << "Connected " << data.nickname << " ping=" << data.ping_timer << std::endl;
        }
        LOOP(socket_list)
        {
            socket_data &data = *it;
            if (data.ping_timer > 0 && time(NULL) > data.ping_timer)
            {
                RESPOND("", "PING", data.nickname)
                data.ping_timer = 0; data.pong_timer = time(NULL) + 60;
                std::cout << "Sent PING to " << data.nickname << " pong=" << data.pong_timer << std::endl;
            }
            if (data.pong_timer > 0 && time(NULL) > data.pong_timer) disconnect(data, "PONG", 1);
            if (FD_ISSET(data.socket, &socket_except_set)) disconnect(data, "select except", 0);
            if (FD_ISSET(data.socket, &socket_read_set))
            {
                int bytes = recv(data.socket, data.read_buffer + data.read_buffer_index, sizeof(data.read_buffer) - data.read_buffer_index - 1 > 0, 0);
                if (bytes > 0) data.read_buffer_index += bytes; else disconnect(data, "recv error", 0);
                char *pointer = data.read_buffer + data.read_buffer_index - 2;
                if (!strcmp(pointer, "\r\n"))
                {
                    *pointer++ = '\0'; *pointer = '\0'; // remove the \r\n
                    message msg = parse(data.read_buffer);
                    memset(data.read_buffer, 0, READ_BUFFER_SIZE); data.read_buffer_index = 0;
                    if (data.ping_timer > 0 && time(NULL) < data.ping_timer)
                    {
                        data.ping_timer = time(NULL) + 60; data.pong_timer = 0;
                        std::cout << "Reset ping for " << data.nickname << " ping=" << data.ping_timer << std::endl;
                    }
                    if (process(data, msg)) EXIT_IF(0, true)
                }
            }
        }
        socket_list.remove_if([](const socket_data& data){ return data.socket == INVALID_SOCKET; });
    }
    retval = 0;
finished:
    LOOP(socket_list) disconnect(*it, "exit", 0);
    disconnect(listener, "exit", 0);
    WSACleanup();
    return retval;
}
Jerry Jeremiah
quelle