Programmieren unter MS-Windows

Kapitel 17: Einführung in die WinSockets

Letzte Änderung: 17.3.97 von B. Tritsch

Überblick

Zurück zum Index "PC- und MS-Windows-Support"

Zurück zum Inhalt


WinSockets - Eine Einführung

Bis vor wenigen Jahren war die Programmierung von verteilten Applikationen auf der Basis eines TCP/IP-Netzwerkes wenigen Spezialisten auf UNIX-Workstations vorbehalten. In der heutigen Zeit des Internet-Booms und der möglichen Verwendung eines TCP/IP-Protokoll-Stacks auf Windows-PCs hat sich dies drastisch geändert. Die primäre Entwicklungsschnittstelle für Netzwerkfunktionalitäten sind die Berkeley Sockets. Sie können sowohl für verbindungsorientierte (TCP) als auch verbindungslose (UDP) Protokolle aus der TCP/IP-Familie verwendet werden. Das Programmiermodell ist dabei das Client/Server-Modell: Server warten auf eintreffende Anfragen, Clients initiieren eine Kommunikations-Session. Die Sockets sind hierbei bidirektionale Verbindungen über sogenannte Ports zwischen zwei Endpunkten, die irgendwo im Netzwerk liegen. Typischerweise sind Client und Server die beiden Endpunkte.

Es gibt eine Reihe von Implementierungsunterschieden zwischen der klassischen UNIX-Version der Berkeley Sockets und den MS-Windows-spezifischen WinSockets. Ein signifikanter Unterschied ist beispielsweise, daß unter MS-Windows die Socket Descriptors und die File Descriptors nicht austauschbar sind. Jegliche Annahme über eine Übereinstimmung dieser beiden Konzepte, wie sie unter UNIX üblich sind, führt zum Fehlerfall. Weiterhin benötigt die WinSocket-Bibliothek eine Initialisierung (WSAStartup) bevor sie ihre Arbeit aufnehmen kann, und eine Beendigung (WSACleanup) für ihre saubere Terminierung. Nicht zuletzt werden in der WinSocket-Implementation eine Reihe von Erweiterungen des Berkeley-Standards eingeführt, um den ereignisorientierten Charakter von Windows-Anwendungen speziell zu unterstützen.

Ein Beispiel für das Erfragen der Systemzeit

Ein einfaches WinSocket-Programm, das die Systemzeit über den Standard TCP-Port 37 bei einem Time-Service erfragt, kann nicht seine Herkunft aus der UNIX-Welt leugnen. Dennoch zeigt es schon die Mechanismen für folgende Aufgaben in einer Windows-Umgebung:

Die im folgenden vorgestellte Beispielanwendung ist kommandozeilenorientiert. Als Parameter muß der Name eines potentiellen Time Servers mitgegeben werden, der RFC868-konform sein muß. Als Ergebnis sollte nach erfolgreicher Konvertierung der empfangenen Daten die aktuelle Zeit ausgegeben werden.

#include <iostream.h>
#include <time.h>
#include <winsock.h>

void main(int argc, char *argv[])
{
    time_t t;
    int s;
    struct sockaddr_in a;
    struct hostent *h;
    WSADATA wsaData;

    if (argc != 2)
    {
        cout << "Usage: " << argv[0] << " host\n";
        exit(1);
    }
    if(WSAStartup(0x101, &wsaData))
    {
        cout << "Unable to initialize WinSock library.\n";
        exit(1);
    }
    h = gethostbyname(argv[1]);
    if (h == NULL)
    {
        cout << "Cannot resolve hostname\n";
        WSACleanup();
        exit(1);
    }

    a.sin_family = AF_INET;
    a.sin_port = htons(37);
    memcpy(&(a.sin_addr.s_addr), h->h_addr, sizeof(int));
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == 0)
    {
        cout << "Cannot establish connection: "
             << WSAGetLastError() << '\n';
        WSACleanup();
        exit(1);
    }
    if (connect(s, (struct sockaddr *)&a, sizeof(a)))
    {
        cout << "Cannot establish connection: "
             << WSAGetLastError() << '\n';
        WSACleanup();
        exit(1);
    }
    if (recv(s, (char *)&t, 4, 0) != 4)
        cout << "Unable to obtain time.\n";
    else
    {
        t = ntohl(t) - 2208988800;
        cout << asctime(localtime(&t));
    }
    closesocket(s);
    WSACleanup();
}

WinSockets und die MFC

Die Microsoft Foundation Class Library kapselt die Socket-Funktionalitäten in der CAsyncSocket-Klasse. Ihre Member-Funktionen korrespondieren in vielen Fällen mit den standardisierten asynchronen Funktionen der WinSocket-Bibliothek.

Die Socket-Initialisierung geschieht in der MFC mit AfxSocketInit, es braucht jedoch keine Funktion für Aufräumarbeiten aufgerufen werden. Die schon oben vorgestellte Time-Funktionalität kann eine MFC-Applikation nicht auf der Kommandozeile ausführen. Daher muß eine kleine Dialogbox mit einer Eingabefläche für den Hostnamen des Time Servers, einer Ausgabefläche für das Ergebnis und einem "Connect"-Button für den Start der Anfrage als Benutzerschnittstelle bereitgestellt werden. Die Programmentwicklung aus der MFC-Rahmenanwendung ist entsprechend dem Standard.

Die folgenden Zeilen zeigen die benötigte Klasse CGTSocket und die zugehörigen Member-Funktionen (Datei GTDLG.CPP):

class CGTSocket : public CAsyncSocket
{
public:
	CGTSocket(CGTDlg *pDlg) {m_pDlg = pDlg;};
	virtual void OnReceive(int nErrorCode);
	virtual void OnClose(int nErrorCode);
	CGTDlg *m_pDlg;
	time_t t;
};

void CGTSocket::OnReceive(int nErrorCode)
{
	Receive(&t, 4);
	t = ntohl(t) - 2208988800;
	m_pDlg->m_sTime = asctime(localtime(&t));
	m_pDlg->UpdateData(FALSE);
}

void CGTSocket::OnClose(int nErrorCode)
{
	m_pDlg->m_cConnect.EnableWindow(TRUE);
	delete this;
}

void CGTDlg::OnConnect() 
{
	CGTSocket *pSocket;

	pSocket = new CGTSocket(this);
	UpdateData(TRUE);
	m_cConnect.EnableWindow(FALSE);
	pSocket->Create();
	pSocket->AsyncSelect(FD_READ | FD_CLOSE);
	pSocket->Connect(m_sHost, 37);
}

Zum nächsten Kapitel