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

// Server in Thread-Variante.
// 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.
// Da die Kanaele blockieren, beendet sich der Server nach Eingabe von ESC erst dann,
// wenn die recv()-Funktion in main() und die accept()-Funktionen der Threads zurueckkehren.
// Das bedeutet, alle Klienten muessen noch mindestens einen Datenaustausch haben und
// ein weiterer Klient muss andocken, damit der Server beendet werden kann.
// Alternative: Harter Abbruch mit Ctl-C (und Nichtfreigabe von System-Ressourcen).
// Da CreateThread() die CRT nicht initialisiert und Speicher nach Thread-Ende nicht
// wieder vollstaendig freigibt, wurden alle benoetigten CRT-Funktionen durch eigene
// Routinen emuliert (Alternative: Nutzung der CRT-Funktion _beginthread()).
// Dies hat zusaetzlich den Vorteil, daß die CRT-Bibliothek beim Binden entfallen kann
// und so die Groesse des Kompilats deutlich reduziert wird.
// 2015 www.asdala.de.

#include <winsock2.h>
#include <ws2tcpip.h>
#define BUFSIZE 128 // Groesse des Sende- und Empfangspuffers

HANDLE hStdOut, hStdIn;

/** NON-CRT-Ersatz fuer printf() **/
void myprintf(const TCHAR *fmt, ...)
  {
  va_list args;
  TCHAR s[255];
  DWORD stdOutWritten;
  int i;
  va_start(args, fmt);
  i = wvsprintf(s, fmt, args);
  WriteConsole(hStdOut, s, i, &stdOutWritten, 0);
  va_end(args);
  }

/** Non-CRT-Ersatz fuer kbhit() **/
int escPressed(void)
  {
  DWORD read;
  BOOL success;
  INPUT_RECORD inRec;
  static int escPressed;
  while(!escPressed) {
    success = PeekConsoleInput(hStdIn, &inRec, 1, &read);
    if (!success || !read)
      return 0;
    if (inRec.EventType == KEY_EVENT && inRec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) {
      escPressed = 1;
      break;
      }
    success = ReadConsoleInput(hStdIn, &inRec, 1, &read);
    if (!success || !read)
      return 0;
    }
  return 1;
  }

/** Klienten-Thread **/
DWORD WINAPI cltThread(void *cltSock_)
  {
  int res;
  char buf[BUFSIZE];
  SOCKET cltSock = (SOCKET)cltSock_;
  struct sockaddr_in cltSockAddr;
  int cltSockAddrSize = sizeof cltSockAddr;
  TCHAR cltName[40];
  DWORD cltThreadId = GetCurrentThreadId();

  getpeername(cltSock, (struct sockaddr*)&cltSockAddr, &cltSockAddrSize);
  wsprintf(cltName, TEXT("%.30hs:%hu"), inet_ntoa(cltSockAddr.sin_addr),
    ntohs(cltSockAddr.sin_port));
  myprintf(TEXT("%lu: Thread verbunden mit %s.\n"), cltThreadId, cltName);

  /* Empfange und sende Daten */
  res = recv(cltSock, buf, BUFSIZE, 0);
  while (res > 0 && !escPressed()) {
    myprintf(TEXT("%lu: %d Byte(s) von %s empfangen.\n"), cltThreadId, res, cltName);
    res = send(cltSock, buf, res, 0);
    myprintf(TEXT("%lu: %d Byte(s) an %s gesendet.\n"), cltThreadId, res, cltName);
    res = recv(cltSock, buf, BUFSIZE, 0);
    }

  /* Entlasse Klienten */
  res = shutdown(cltSock, SD_SEND);
  while (recv(cltSock, buf, BUFSIZE, 0) > 0)
    ;
  closesocket(cltSock);
  myprintf(TEXT("%lu: %s entlassen.\n\n"), cltThreadId, cltName);
  return 0;
  }

/** Hauptprogramm **/
int main(void)
  {
  WSADATA wsaData;
  SOCKET listSock, cltSock;
  TCHAR cltName[40];
  struct addrinfo defAddr = { 0 }, *addr;
  struct sockaddr_in cltSockAddr;
  int res, cltSockAddrSize = sizeof cltSockAddr;
  HANDLE cltThreadHandle;
  DWORD cltThreadID;

  SetConsoleTitle(TEXT("Echo-Server"));
  hStdIn = GetStdHandle(STD_INPUT_HANDLE);
  hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

  /* Lade Netzbibliothek */
  res = WSAStartup(MAKEWORD(2,2), &wsaData);
  myprintf(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);
  myprintf(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);
  myprintf(TEXT("Abhoerkanal gebunden an Port 7.\n"));

  /* Starte Abhoeren */
  listen(listSock, SOMAXCONN);
  myprintf(TEXT("Abhoeren ... Programmende mit ESC.\n"));

  while (!escPressed()) {
   /* Nimm Klienten an und erstelle einen Thread fuer ihn mit Parameter cltSock */
    cltSock = accept(listSock, (struct sockaddr *)&cltSockAddr, &cltSockAddrSize);
    wsprintf(cltName, TEXT("%.30hs:%hu"), inet_ntoa(cltSockAddr.sin_addr),
      ntohs(cltSockAddr.sin_port));
    myprintf(TEXT("%s angenommen.\n"), cltName);
    cltThreadHandle = CreateThread(0, 0, cltThread, (void*)cltSock, 0, &cltThreadID);
    myprintf(TEXT("Thread %lu erstellt.\n"), cltThreadID);
    CloseHandle(cltThreadHandle);
    }

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