Ich habe nun das Problem gelöst. Also:
Zunächst habe ich (doch noch) ein
Handle kriiert:
Delphi-Quellcode:
(...)
var
Msg:TMsg;
Handle:HWND;
WC:TWndClassEX=(cbSize:SizeOf(WC);
lpfnWndProc:@WndProc;
lpszClassName:'
0');
begin
RegisterClassEx(wc);
Handle:=CreateWindowEx(0,'
0','
',0,0,0,0,0,0,0,hInstance,
NIL);
S:=TServer.Create(1223,
Handle);
// Handle wird hier an den Server übergeben
with S
do
if not Listen
then
begin
Free;
S:=nil;
PostQuitMessage(0);
end;
while true
do
begin
if not GetMessage(Msg,0,0,0)
then Break;
DispatchMessage(Msg);
end;
Erläuterung:
Obwohl ich CreateWindowsEx aufrufe, hat der Server kein Fenster, da fast alle übergebenen Parameter null sind. Er stellt ein Daemon dar.
Nachdem ich das
Handle erhalten habe übergebe ich dieses an den TServer.Create. S.Listen versetzt das Programm in den Listen-Zustand (hier: Port 1223):
Delphi-Quellcode:
function TServer.Listen:Boolean;
var wsaData: TWSADATA;
SockAddr: sockaddr_in;
begin
Result:=false;
if (WSAStartup(MAKEWORD(2,0),WSAData)) =SOCKET_ERROR
then Exit;
//Winsock wird geladen
Sock:=Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//Socket kriiert
if Sock = INVALID_SOCKET
then Exit;
if WSAAsyncSelect(Sock,
Handle,WM_SOCKET,FD_SERVER)= SOCKET_ERROR
then Exit;
//Socket Messages abgefangen
ZeroMemory(@SockAddr, sizeof(SockAddr));
// Daten..
SockAddr.sin_addr.S_addr := INADDR_ANY;
// für..
SockAddr.sin_family := AF_INET;
// die..
SockAddr.sin_port := htons(Port);
// Verbindungsart werden festgelegt.
if bind(Sock,SockAddr,SizeOf(SockAddr))=SOCKET_ERROR
then Exit;
// Socket wird an ein Port gebunden
if WinSock.listen(Sock, 1) = SOCKET_ERROR
then Exit;
// Socket wird in den Listen-Zustand versetzt
Result:=true;
end;
Erläuterung:
Nachdem das Socket kriiert wurde, wird die WSAAsyncSelect()-Funktion aufgerufen. Dieser übergeben wir das zuvor erzeugte
Handle. Was bewirkt nun WSAAsyncSelect?
Nunja, dieser Funktion werden noch weitere Parameter übergeben, nämlich WM_SOCKET (=WM_USER=1024, s.
Unit Messages) und FD_SERVER (=FD_READ+FD_ACCEPT+FD_CLOSE, s.
Unit WinSock). FD_SERVER enhätlt die Summe aller Socket-Messages, die abgefangen werden sollen. Wird eine Socket-Message abgefangen, dann soll dies dem Server durch die (Windows-)Message WM_SOCKET mitgeteilt werden.
Kurz an einem Beispiel:
Der Client verbindet sich mit dem Server. Dabei verschickt er an den Server-Socket die Message FD_ACCEPT. Die Message wird von Windows erkannt und an das Server-Programm weitergeleitet. Dabei sind die Message-Parameter, die der Server erhält folgende:
Delphi-Quellcode:
while true do
begin
{hier kommen die Windows Messages an}
if not GetMessage(Msg,0,0,0) then Break;
DispatchMessage(Msg); // Msg wird an WndProc weitergeleitet. dabei ist
// Msg.Msg=WM_SOCKET, Msg.wParam=SOCK, Msg.lParam=FD_ACCEPT;
end;
Analog gilt die Prozedur für das Lesen von eintreffenden Nachrichten, die der Client geschickt hat.
Schließt sich nun der Client (korrekt), dann ruft er im onClose-Ereignis zuvor die Winsock-Funktion shutdown() auf.
Diese Funktion schickt die Message FD_CLOSE an den Server-Socket. Die WndProc Prozedur des Servers ruft nach Erhalt von FD_CLOSE die WinSock-Funktion CloseSocket()auf. Die Verbindung wird geschlossen.
Schließt sich der Client nicht korrekt, d.h. wird er erzwungen geschlossen (z.B. durch den TaskManager), so bleibt ihm keine Zeit mehr die onClose-Prozedur aufzurufen. Folglich wird shutdown() ebenfalls nicht aufgerufen und FD_CLOSE wird nicht verschickt.
Hier tritt Windows in Kraft. Wird ein Socket erzwungenerweise geschlossen, so wird eine Message verschickt, die den Fehlercode und FD_CLOSE enthält. Dabei setzt Windows die Hexadezimalzahlen des Fehlercodes und der FD_CLOSE-Konstante zusammen: $xxxx + $0020 = $xxxx0020, und verschickt anschließend diese.
Wird der Client durch den TaskManager geschlossen, so ist der Fehlercode 10053 (HD: $2745). Hierzu kurz die
MSDN:
Zitat:
WSAECONNABORTED (10053)
• Translation: Software caused connection abort.
• Description: An established connection was stopped by the software in your host computer, possibly because of a data
transmission time-out or protocol error.
Windows verschickt also dann die Socket-Message $27450020, also in Dezimalschreibweise 658833440. Beim Server angekommen, muss diese Socket-Message wieder in den Fehlercode und in die Socket-Konstante (FD_CLOSE) zersetzt werden.
Dies könnte man z.B. wie folgt machen:
Delphi-Quellcode:
if lParam>65535 then
lParam:=Hex4ToInt(copy(IntToHex(lParam,8),5,4));
---------------------------------------------------------------------------
function Hex4ToInt(Value:String):Integer;
begin
Result:=StrToInt(Value[1])*16*16*16+StrToInt(Value[2])*16*16
+StrToInt(Value[3])*16+StrToInt(Value[1]);
end;
lParam enthält nach Aufruf dieser Befehle die FD_CLOSE-Message. Diese wird schließlich im Weiteren in der WndProc-Prozedur verarbeitet.
Analog gilt die ganze Sache beim Absturz des Servers.