/** 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;
}