/** Echo-Server in C unter 150 Zeilen **/

// Server mit Variante select(), der mit ESC beendet werden kann.
// Die Kompilierung ist mit UNICODE oder ANSI moeglich.
// Um anschaulich zu bleiben, verzichtet der Quelltext auf die Fehlerkontrolle der
// Netzfunktionen und ist daher nicht fuer einen produktiven Einsatz geeignet.
// 2015 www.asdala.de.

#include <stdio.h>
#include <conio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define BUFSIZE 128 // Groesse des Sende- und Empfangspuffers
#define SELWAIT 10ul // Warteschleife auf Benutzereingabe in Sekunden

typedef struct _tClt
  { char buf[BUFSIZE]; SOCKET sock; TCHAR name[40]; unsigned int bufIn, bufOut; } tClt;

int cltCt = 0;
tClt *clts[FD_SETSIZE];

/** Klient anlegen **/
int initClt(SOCKET s, TCHAR *name)
  {
  tClt *clt = (tClt*) GlobalAlloc(GPTR, sizeof(tClt));
  clt->sock = s;
  lstrcpy(clt->name, name);
  clts[cltCt++] = clt;
  return 1;
  }

/** Klient entlassen **/
void closeClt(int index)
  {
  tClt *clt = clts[index];
  int i;
  /* Beende serverseitige Verbindung zu Klient (FIN) */
  shutdown(clt->sock, SD_SEND);
  while (recv(clt->sock, clt->buf, BUFSIZE, 0) > 0)
    ;
  /* Entlasse Klient */
  closesocket(clt->sock);
  printf(TEXT("%s entlassen.\n"), clt->name);
  /* Verwerfe Klientenakte */
  GlobalFree(clt);
  for (i=index; i<cltCt; ++i)
    clts[i] = clts[i+1];
  cltCt--;
  }

/** Klienten entlassen **/
void closeClts(void)
  {
  while(cltCt)
    closeClt(cltCt-1);
  }

/** Hauptprogramm **/
int main(void)
  {
  WSADATA wsaData;
  SOCKET listSock = INVALID_SOCKET, cltSock = INVALID_SOCKET;
  struct addrinfo defAddr = { 0 }, *addr = NULL;
  struct sockaddr_in cltSockAddr;
  FD_SET rSet, wSet;
  TCHAR cltName[40];
  const struct timeval selWait = { SELWAIT, 0ul };
  int res, i, cltSelCt, cltSockAddrSize = sizeof cltSockAddr;

  SetConsoleTitle(TEXT("Echo-Server"));

  /* Lade Netzbibliothek */
  WSAStartup(MAKEWORD(2,2), &wsaData);
  printf(TEXT("Netzbibliothek geladen.\n"));

  /* Ermittele eigene Adresse und speichere sie in addr */
  defAddr.ai_flags = AI_PASSIVE;
  defAddr.ai_family = AF_INET;
  defAddr.ai_socktype = SOCK_STREAM;
  defAddr.ai_protocol = IPPROTO_TCP;
  getaddrinfo(NULL, "7", &defAddr, &addr);

  /* Erstelle Abhoerkanal passend zu addr */
  listSock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
  printf(TEXT("Abhoerkanal erstellt.\n"));

  /* Binde Abhoerkanal an IP-Adresse und Port aus addr */
  res = bind(listSock, addr->ai_addr, (int)addr->ai_addrlen);
  freeaddrinfo(addr);
  printf(TEXT("Abhoerkanal gebunden an Port 7.\n"));

  /* Starte Abhoeren */
  listen(listSock, SOMAXCONN);
  printf(TEXT("Abhoeren ... Programmende nach maximal %lu s mit einer Taste.\n"), SELWAIT);

  while (!_kbhit()) {

    /* Bereite Abhoerkanal und Klientenkanaele auf Datenabfrage mit select() vor */
    FD_ZERO(&rSet); FD_ZERO(&wSet);
    FD_SET(listSock, &rSet);
    for (i = 0; i < cltCt; i++)
       if (clts[i]->bufIn > clts[i]->bufOut)
          FD_SET(clts[i]->sock, &wSet);
       else
          FD_SET(clts[i]->sock, &rSet);

    /* Datenabfrage mit select() */
    cltSelCt = select(0, &rSet, &wSet, NULL, &selWait);

    /* Teste Abhoerkanal, ob neue Klienten auf Annahme warten */
    if (FD_ISSET(listSock, &rSet)) {
      --cltSelCt;
      cltSock = accept(listSock, (struct sockaddr *)&cltSockAddr, &cltSockAddrSize);
      wsprintf(cltName, TEXT("%.30hs:%hu"), inet_ntoa(cltSockAddr.sin_addr),
        ntohs(cltSockAddr.sin_port));

      /* Lege Klientenakte an */
      initClt(cltSock, cltName);
      printf(TEXT("%s angenommen.\n"), cltName);
      }

    /* Fuer alle Klientenkanaele */
    for (i=0; cltSelCt && i<cltCt; ++i) {
      tClt *clt = clts[i];

      /* Falls Daten zum EMPFANG bereitstehen, empfange Daten */
      if (FD_ISSET(clt->sock, &rSet)) {
        --cltSelCt;
        res = recv(clt->sock, clt->buf, BUFSIZE, 0);
        clt->bufIn = res;
        printf(TEXT("%d Byte(s) von %s empfangen.\n"), res, clt->name);
        if (res == 0) {
          printf(TEXT("%s verabschiedet sich.\n"), clt->name);
          closeClt(i);
          }
        continue;
        }

      /* Falls Daten zum VERSAND bereitstehen, sende Daten */
      if (FD_ISSET(clt->sock, &wSet)) {
        --cltSelCt;
        res = send(clt->sock, clt->buf + clt->bufOut, clt->bufIn - clt->bufOut, 0);
        printf(TEXT("%d Byte(s) an %s gesendet.\n"), res, clt->name);
        clt->bufOut += res;
        if (clt->bufOut == clt->bufIn) {
          clt->bufOut = 0;
          clt->bufIn = 0;
          }
        }
      }
    }

  /* Raeume auf */
  closeClts();
  closesocket(listSock);
  printf(TEXT("Abhoeren beendet.\n"));
  WSACleanup();
  printf(TEXT("Netzbibliothek entladen.\n"));
  return 0;
  }