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