Escreva um IRCd básico

8

Um pouco incomum, mas ei, por que não? :)

O objetivo: escreva um daemon IRC em funcionamento no seu idioma de escolha que forneça a funcionalidade de barebones, com o menor número possível de caracteres. Desde que atenda aos critérios abaixo, ele não precisa estar em total conformidade com os RFCs do IRC (isso tornaria o desafio significativamente menos divertido), ele só precisa trabalhar.

Requisitos:

  • Os clientes devem poder conectar-se na porta 6667 e usá-la. Pelo menos o irssi e o XChat devem poder se conectar a ele com êxito com uma configuração padrão.
  • Os usuários devem poder especificar seu próprio apelido, alterá-lo enquanto já estiver conectado, ingressar em canais, sair de canais e sair corretamente (ou seja, QUIT).
  • Os canais devem ser criados normalmente - ingressar em um canal sem usuários 'cria' ele. Eles não precisam ser persistentes.
  • Os usuários devem poder enviar mensagens de canal e privadas (ou seja, para outros usuários).
  • O WHOIScomando deve ser implementada, bem como PING/ PONG, LISTe NAMES(principalmente, a fim de manter os clientes felizes).

Restrições:

  • Você não pode usar nenhuma biblioteca de terceiros (que inclui bibliotecas de E / S com eventos não principais). Somente bibliotecas padrão que acompanham a plataforma que você usa são permitidas.
  • Se sua implementação incluir suporte explícito ao IRC na biblioteca padrão, você também não poderá usá-lo. A funcionalidade de rede da biblioteca padrão é, obviamente, boa.
  • Seu envio deve poder ser executado de forma independente. Não ser inteligente com scripts mIRC :)

Modos, pontapés, etc. não precisam ser implementados (a menos que seja necessário para fazê-lo funcionar com os clientes acima). O SSL também não é necessário. Apenas a funcionalidade barebones acima, para manter o desafio curto e divertido.

Mais informações sobre o IRC é aqui , RFC são 1459 e 2812 (eu não posso ligar para eles diretamente devido a minha falta de reputação). O menor envio funcional e compatível com os requisitos vence!

Sven Slootweg
fonte
2
Você pode dar uma ideia do que é um IRCd (ou mesmo IRC) para pessoas que não estão familiarizadas com o IRC.
Martin Ender
2
Você mesmo escreveu um para ter uma idéia de quanto tempo levaria e quanto código estaria envolvido? Um código de exemplo sem golfe ajudaria as pessoas a estimar se essa é a pergunta do tamanho certo para a quantidade de tempo livre.
Trichoplax
@ MartinBüttner Editou a publicação. Não há reputação suficiente para vincular diretamente aos RFCs, mas com os números de RFC eles não devem ser difíceis de encontrar.
Sven Slootweg 21/08/14
@githubphagocyte Sim, eu já escrevi um antes (embora seja mais um experimento do que qualquer outra coisa). Eu diria que um desenvolvedor experiente em uma linguagem dinâmica (pense em Python, Node.js.) seria capaz de montar um IRCd de funcionamento tão básico em 1-2 horas, no máximo, de forma não-golfe. Provavelmente menos, se você já conhece os RFCs.
Sven Slootweg
Em qual porta o servidor deve escutar? 6667, 194 ou algo mais?
precisa saber é o seguinte

Respostas:

3

C ++ (golfe parcialmente) 5655 (com CRLF contando por 1)

Isso é compilado no VS 2013 (usa auto, lambdas e winsock). Parecia estar funcionando antes de eu jogar o golfe, a menos que eu amassasse, ainda assim deveria estar ok. Uma das razões pelas quais é tão grande é que as respostas numéricas que estou retornando incluem o texto especificado na RFC - não sei se isso é necessário ou não. Testei-o com o KVirc porque ele é executado de forma portável (não é permitido instalar software no meu PC!) O KVirc parece funcionar com meu servidor, mas não conheço outros clientes - fiz o que achava que a RFC dizia, mas muito disso está subespecificado, então espero que eu entenda direito.

O servidor lida com DIE, KILL, NICK, USER, MODE, WHOIS, WHO, JOIN, PART, TOPIC, LIST, NAMES, PRIVMSG, USERS, PING, PONG e QUIT em vários graus. Para a maioria deles, retorno as respostas necessárias, incluindo a maioria das verificações necessárias para retornar as respostas de erro especificadas. Para alguns deles eu trapaceio:

  • USERS sempre retorna 446 "USERS foi desativado"
  • a mensagem MODO do canal sempre retorna 477 "O canal não suporta modos"
  • a mensagem MODO do usuário funciona corretamente, mas os sinalizadores não são usados ​​pelos outros comandos

Eu acho que é apenas parcialmente jogado, porque eu não sou muito bom em jogar golfe; se você vir algo grande, edite a resposta e corrija-a.

Aqui está a versão golfada

#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;}

Aqui está a versão mais não destruída (ainda usa algumas macros):

#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
fonte