8
\$\begingroup\$

A bit of an unusual one, but hey, why not? :)

The objective: Write a functioning IRC daemon in your language of choice that provides barebones functionality, in as few characters as possible. As long as it fulfills the criteria below, it does not have to fully comply with the IRC RFCs (this would make the challenge significantly less fun), it just needs to work.

Requirements:

  • Clients must be able to connect on port 6667 and use it. At least irssi and XChat must be able to connect to it successfully with a default configuration.
  • Users must be able to specify their own nickname, change it while already connected, join channels, leave channels, and exit cleanly (ie. QUIT).
  • Channels should be created like normal - joining a channel with no users in it 'creates' it. They do not have to be persistent.
  • Users must be able to send both channel messages and private messages (ie. to other users).
  • The WHOIS command must be implemented, as well as PING/PONG, LIST, and NAMES (mostly in order to keep clients happy).

Restrictions:

  • You may not use any third-party libraries (that includes non-core evented I/O libraries). Only standard libraries that come with the platform you use, are permitted.
  • If your implementation includes explicit support for IRC in the standard library, you may not use that either. Standard-library network functionality is, of course, fine.
  • Your submission must be able to run independently. No being clever with mIRC scripting :)

Modes, kicks, etc. do not have to be implemented (unless required to make it work with the above clients). SSL isn't necessary either. Just the above barebones functionality, to keep the challenge short and fun.

More information on what IRC is here, RFCs are 1459 and 2812 (I can't link to them directly due to my lack of reputation). Shortest functional and requirements-compliant submission wins!

\$\endgroup\$
  • 2
    \$\begingroup\$ You might want to give some background what an IRCd (or even IRC) is for people who aren't familiar with IRC. \$\endgroup\$ – Martin Ender Aug 21 '14 at 21:45
  • 2
    \$\begingroup\$ Have you written one yourself to have an idea of how much time it would take and how much code would be involved? Non-golfed example code would help people estimate whether this is the right size question for the amount of time they happen to have free. \$\endgroup\$ – trichoplax Aug 21 '14 at 21:55
  • \$\begingroup\$ @MartinBüttner Edited the post. Not enough reputation to link to the RFCs directly, but with the RFC numbers they shouldn't be hard to find. \$\endgroup\$ – Sven Slootweg Aug 21 '14 at 21:56
  • \$\begingroup\$ @githubphagocyte Yup, I've had a go at writing one myself before (though it's more of an experiment than anything else). I'd say that an experienced developer in a dynamic language (think Python, Node.js) would be able to put together such a basic functioning IRCd in 1-2 hours at most, in non-golfed form. Probably less, if you're already familiar with the RFCs. \$\endgroup\$ – Sven Slootweg Aug 21 '14 at 21:58
  • \$\begingroup\$ Which port should the server listen on? 6667, 194 or something else? \$\endgroup\$ – nyuszika7h Aug 22 '14 at 16:29
3
\$\begingroup\$

C++ (partially golfed) 5655 (with CRLF counting for 1)

This compiles in VS 2013 (uses auto, lambdas and winsock) It seemed to be working before I golfed it so unless I mucked it up it should still be ok. One of the reasons it is so big is that the numeric replies I am returning include the text specified in the RFC - I don't know if that is necessary or not. I tested it with KVirc because it runs portably (not allowed to install software on my PC!) KVirc seems to work with my server but I don't know about other clients - I did what I thought the RFC said but a lot of it is underspecified so hopefully I understood it right.

The server handles DIE, KILL, NICK, USER, MODE, WHOIS, WHO, JOIN, PART, TOPIC, LIST, NAMES, PRIVMSG, USERS, PING, PONG and QUIT to varying degrees. For most of them I return the required responses including doing most of the checks needed to return the specified error replies. For some of them I cheat:

  • USERS always returns 446 "USERS has been disabled"
  • the channel MODE message always returns 477 "Channel doesn't support modes"
  • the user MODE message works properly but the flags are not used by the other commands

I figure it is only partially golfed because I am not very good at golfing so if you see something big please edit the answer and fix it.

Here is the golfed version

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

Here is the mostly ungolfed version (still uses some 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;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.