Wie kann man mit C++ ein Chat Programm über Winsock schreiben?
Voraussetzungen:
- Windows 9x/ME/2000
- C++ Compiler (ich empfehle Microsoft Visual C++)
Zunächst ein paar grundsätzliche Sachen:
- Winsock muss in #include
rein
- Winsock muss gelinkt werden
- Winsock muss zuerst initialisiert werden.
- Ein Socket muss erstellt werden.
- Um Ereignisse aufzunehmen, muss eine Anfrage gemacht werden.
- Diese Ereignisse müssen in einer Prozedur abgefangen werden.
- Die IP-Adresse und der Port müssen in ein struct hineingeschrieben werden, um
dann eine Verbindung aufzubauen.
- Nun können Daten gesendet und empfangen werden.
- Zum Schluss muss das Socket wieder geschlossen werden und Winsock muss geschlossen werden.
Zu den Funktionen:
Um Winsock in den #include
Bereich hinzuzufügen, muss man folgendes eingeben:
#include “winsock2.h“ // falls das nicht geht, dann #include “winsock.h“
Um Winsock zu linken muss man bei Microsoft Visual C++ Alt + F7 drücken, Linker auswählen
und bei Objekt-/Bibliothek-Module folgendes einfügen: ws2_32.lib.
Ich weiß nicht, wie das bei anderen Compilern ist, es kann auch sein, dass man das nur bei Microsoft machen muss.
Um Winsock zu initialisieren, muss die Funktion WSAStartup verwendet werden:
Funktion:int WSAStartup
Argumente: WORD wVersionRequested, LPWSADATA lpWSAData
wVersionRequested:
Gibt die höchste Version an, die Winsock verwenden kann. Das höherwertige Byte gibt die untere
Versionsnummer (Revision) an. Das andere Byte gibt die obere Versionsnummer an.
Ich verwende die Versionsnummer 1,1 , die jeder verwenden sollte!
lpWSAData:
Pointer, der die WSADATA
Struktur verwendet, um nähere Informationen zur Winsock DLL
zu ermitteln.
Rückgabewert: wenn die Funktion erfolgreich ausgeführt wurde, bekommt man 0 zurück.
Beispiel:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // Versionsnummer 1,1
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
// falsche DLL gefunden
return;
}
// prüft, ob die Versionsnummer 1,1 gesetzt wurde
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
// falsche DLL-Version gefunden
WSACleanup( );
return;
}
// die Winsock DLL ist aufrufbar, weitermachen!
Funktion:SOCKET socket
Argumente:int af, int type, int protocol
af:
bezeichnet eine „address family“. Ist gewöhnlich immer AF_INET
.
type:
bezeichnet die Art des neuen Sockets.
SOCK_STREAM
-> muss für eine TCP Verbindung gewählt werden.
SOCK_DGRAM
-> muss für eine UDP Verbindung gewählt werden.
protocol:
ein besonderes Protokoll, das für das Socket verwendet werden soll. Sollte immer 0 sein!
Rückgabewert: wenn kein Fehler auftritt, gibt die Funktion das neue Socket zurück.
Ansonsten bekommt man INVALID_SOCKET
.
Beispiel:
SOCKET connect_socket;
connect_socket = socket(AF_INET,SOCK_STREAM,0);
Um eine Anfrage an Windows für die Ereignisse zu machen, muss die Funktion WSAAsyncSelect
verwendet werden.
Funktion:int WSAAsyncSelect
Argumente:SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent
s:
Socket, für das eine Anfrage gemacht werden soll.
hWnd:
Ein Handle mit dem Fenster, das die Nachrichten empfangen soll.
wMsg:
Nachricht, die empfangen werden soll.
lEvent:
Ereignisse, die empfangen werden sollen.
Folgende Werte können eingesetzt werden:
FD_READ
Benachrichtigung: Bereitschaft zum Lesen (wichtig!)
FD_WRITE
Benachrichtigung: Bereitschaft zum Senden
FD_OOB
Benachrichtigung: „out of band“ Daten empfangen
FD_ACCEPT
Benachrichtigung: Hereinkommende Verbindungen (wichtig!)
FD_CONNECT[code] Benachrichtigung: Verbindung aufgebaut (wichtig!)
Benachrichtigung: Verbindung abgebrochen (wichtig!)
[code]FD_CLOSEFD_QOS
Benachrichtigung: Änderung von „quality of service“
FD_GROUP_QOS
Benachrichtigung: Änderung von „group quality of service“
Rückgabewert: wenn die Funktion erfolgreich ausgeführt wurde, bekommt man 0 zurück.
Ansonsten SOCKET_ERROR.
Beispiel:
#define WM_SOCKET 60
...
WSAAsyncSelect(connect_socket,hMainDlg,WM_SOCKET,
FD_CONNECT|FD_READ|FD_CLOSE|FD_ACCEPT);
connect_socket
bezieht sich auf das Beispiel für die Funktion socket
.
hMainDlg
ist das Handle des Fensters.
Ein Beispiel zum Abfangen der Ereignisse könnte wie folgt aussehen:
#include “winsock2.h“
...
BOOL CALLBACK MainDlgProc(HWND, UINT, WPARAM, LPARAM);
…
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
switch(wMsg)
{
case WM_SOCKET: // bezieht sich auf das Beispiel von WSAAsyncSelect
switch(LOWORD(lParam))
{
case FD_READ:
… // kommt später bei recv noch, da man hier den Code mit recv einfügen muss!
}
}
}
Dieses Beispiel macht eine Anfrage für die Ereignisse an Windows.
Alle Ereignisse werden an die Funktion MainDlgProc
gesendet.
Es ist leider nicht so einfach, diese Zeilen in ein Programm einzubinden, da es z.B. kein MFC sein darf und es muss vorher ein Aufruf mit DialogBox geschehen.
Um das mit MFC zu machen, muss man die Funktion WindowProc
mit dem Klassen-Assistent
(Strg + W) erstellen.
Um die IP-Adresse und den Port in ein struct
zu schreiben und dann eine Verbindung aufzubauen, verwendet man folgende Zeilen:
struct sockaddr_in addy2;
addy2.sin_family = AF_INET;
addy2.sin_port = htons(1001); // das verwendet den Port 1001, kann problemlos geändert werden
addy2.sin_addr.s_addr = inet_addr("127.0.0.1");
// das verwendet die IP-Adresse 127.0.0.1, kann auch geändert werden
connect(connect_socket,(struct sockaddr*)&addy2,sizeof(addy2)); // stellt die Verbindung her
connect_socket
bezieht sich wieder auf das Beispiel von oben.
Um etwas zu einem geöffneten Socket zu senden, muss man die Funktion send verwenden.
Funktion:int send
Argumente:SOCKET s, const char FAR * buf, int len, int flags
s:
ein Socket, das geöffnet ist.
buf:
ein Buffer, das die Daten zum Senden enthält.
len:
die Länge von buf, kann ohne Probleme auf 200 und mehr gesetzt werden.
flags:
bezeichnet den Weg, wie die Daten gesendet werden. Sollte immer 0 sein!
Rückgabewert: wenn kein Fehler auftritt, enthält der Rückgabewert die gesendeten Zeichen.
AnsonstenSOCKET_ERROR.
Beispiel:
send(connect_socket, "Hallo", 200,0);
[code]
Das Beispiel sendet ‘Hallo’ an [code]connect_socket, das sich auf das Beispiel oben bezieht.
Um Daten zu empfangen, muss die Funktion recv
verwendet werden. Diese sollte aufgerufen werden,
wenn neue Daten bereit sind (siehe Beispiel von WSAAsyncSelect
).
Funktion:int recv
Argumente:SOCKET s, char FAR* buf, int len, int flags
s:
ein Socket, das geöffnet ist.
buf:
ein Buffer, das die hereinkommenden Daten empfängt.
len:
bezeichnet die Länge von buf.
flags:
bezeichnet den Weg, wie die Daten empfanger werden. Sollte immer 0 sein!
Rückgabewert: wenn kein Fehler auftritt, enthält der Rückgabewert die empfangenen Zeichen.
Ansonsten SOCKET_ERROR.
Beispiel:
char readbuff[2000];
int rlen;
rlen = recv(connect_socket,readbuff,2000,0);
readbuff[rlen] = 0;
Das Beispiel empfängt Daten von connect_socket
, das sich auf das Beispiel oben bezieht.
In readbuff
stehen nun die empfangenen Daten drin und man kann sie dann ausgeben, z.B. in eine MessageBox.
Um eine Verbindung abzubrechen, muss man die Funktion WSASendDisconnect
aufrufen.
Hinweis: Diese Funktion ist nur in winsock2.h vorhanden!
Funktion:int WSASendDisconnect
Argumente:SOCKET s, LPWSABUF lpOUT
s:
Socket, das die Verbindung abbrechen soll.
lpOUT:
Daten, die gesendet werden sollen. Sollte immer NULL
sein!
Rückgabewert: wenn die Funktion erfolgreich ausgeführt wurde, bekommt man 0 zurück.
Ansonsten SOCKET_ERROR.
Um das geöffnete Socket wieder zu schließen, muss man die Funktion closesocket
aufrufen.
Funktion:int closesocket
Argumente:SOCKET s
s:
Socket, das geschlossen werden soll.
Rückgabewert: wenn die Funktion erfolgreich ausgeführt wurde, bekommt man 0 zurück.
Ansonsten SOCKET_ERROR.
Beispiel:
closesocket(connect_socket);
Das Beispiel schließt connect_socket
, das sich auf das Beispiel oben bezieht.