AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Netzwerke Delphi Server-Disconnect erkennen (WinSock)
Thema durchsuchen
Ansicht
Themen-Optionen

Server-Disconnect erkennen (WinSock)

Ein Thema von dde · begonnen am 14. Dez 2004 · letzter Beitrag vom 19. Dez 2004
Antwort Antwort
dde

Registriert seit: 7. Mai 2003
132 Beiträge
 
#1

Server-Disconnect erkennen (WinSock)

  Alt 14. Dez 2004, 10:54
Hey Guys,

ich will neuerdings so systemnah wie möglich programmieren und schreibe daher zur Übung einige meiner Progs um.
Nun bin ich bei Winsock angelangt. Ich ersetze damit die Indy-Komponenten. Jedoch komme ich mit einigen Umsetzungen nicht klar: Eine von denen wäre:

Zunächst einmal frag ich mich, wieso der Client weiterhin gebunden ist mit dem Port, wenn der Server schon längst die Verbindung geschlossen hat. Logisch wäre es diesen Port wieder freizugeben. Der Client ist also nach einem Connect() und anschließendem Disconnect durch den Server weiterhin an dem Port gebunden. Also muss irgendwie CloseSocket() aufgerufen werden, damit dieser Port fregegeben wird.
Nunja, hier liegt auch das Problem. Wie kann ich dies am leichtesten implementieren? Ich dachte an einen Timer der ständig den Status abfragt, aber der war mir dann schließlich zu umständig und ich kenne keinen Befehl, der den Status abfragt.

Gruß dde
  Mit Zitat antworten Zitat
dde

Registriert seit: 7. Mai 2003
132 Beiträge
 
#2

Re: Server-Disconnect erkennen (WinSock)

  Alt 18. Dez 2004, 13:34
Hab mich jetzt weiter mit diesem Thema beschäftigt und bin auf WSAAsyncSelect() (WinSock-Funktion) gestoßen.

Die MSDN sagt folgendes zu dieser Funktion:

Zitat:
The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.

int WSAAsyncSelect(
SOCKET s,
HWND hWnd,
unsigned int wMsg,
long lEvent
);
Also habe ich das wie folgt implementiert:

Hauptprogramm:

Delphi-Quellcode:
program Project1;

uses
  Windows,
  WinSock,
  messages,
  uServer in 'uServer.pas';
var
  S:TServer;

function WndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
var
begin
  Result := 0;
  case Msg OF
    WM_SOCKET: //Konstante in uServer
      begin
        if Assigned(S) then
         if LParam=FD_CLOSE then S.Disconnect;
         {...}
      end;
  else
    Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  end;
end;

var
  msg:TMsg;
begin
S:=TServer.Create(1223);
with S do
 begin
  Listen;
  if not Listening then
   begin
    Free;
    S:=nil;
   end;
 end;

while true do
 begin
  if not GetMessage(Msg,0,0,0) then Break;
  DispatchMessage(Msg);
 end;
end.
Zunächst einmal gibt es hier das Problem, dass WndProc von DispatchMessage nicht aufgerufen wird, da WndProc keinem Handle zugewiesen ist. Ich will das ohne Handle machen...

So sieht nun uServer.pas aus:
Delphi-Quellcode:
unit uServer;

interface
uses Windows,Winsock,Messages;
const
WM_SOCKET = WM_USER;
FD_SERVER= FD_READ+FD_CONNECT+FD_CLOSE+FD_ACCEPT;

type
 TServer=class(TObject)

  constructor Create(xPort:Word);
  destructor Destroy; override;

  procedure Listen;
  procedure AcceptConnection;
  procedure Disconnect;

// procedure ExecuteMessage(var Msg:TMessage); message WM_SOCKET;

  private
   FSock:TSocket;
   FClientSock:TSocket;
   FPort:Word;
   FConnected:Boolean;
   FListening:Boolean;
  public
   property Sock:TSocket Read FSock;
   property ClientSock:TSocket Read FClientSock;
   property Port:Word Read FPort;
   property Connected:Boolean Read FConnected;
   property Listening:Boolean Read FListening;
 end;

implementation

constructor TServer.Create(xPort:Word);
begin
inherited Create;
FPort:=xPort;
FConnected:=False;
FListening:=False;
end;

destructor TServer.Destroy;
begin
if Connected then Disconnect;
WSACleanUP;
inherited Destroy;
end;

procedure TServer.AcceptConnection;
begin
if Connected then Exit;
FClientSock:=accept(Sock,nil,nil);
FConnected:=true;
end;

procedure TServer.Disconnect;
begin
shutdown(Sock,SD_SEND);
end;

procedure TServer.Listen;
var wsaData: TWSADATA;
    SockAddr: sockaddr_in;
begin
if (WSAStartup(MAKEWORD(2,0),WSAData)) <> 0 then Exit;

FSock:=Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if FSock = INVALID_SOCKET then Exit;

ZeroMemory(@SockAddr, sizeof(SockAddr));
SockAddr.sin_addr.S_addr := INADDR_ANY;
SockAddr.sin_family := AF_INET;
SockAddr.sin_zero := #0#0#0#0#0#0#0;
SockAddr.sin_port := htons(Port);

if (bind(Sock,SockAddr,SizeOf(SockAddr)))=SOCKET_ERROR then Exit;
if (WinSock.listen(Sock, 1)) = SOCKET_ERROR then Exit;
WSAAsyncSelect(Sock,0,WM_SOCKET,FD_SERVER);
FListening:=True;
end;

{procedure TServer.ExecuteMessage(var Msg:TMessage);
begin
end;}
Wie ihr vielleicht erkannt habt, habe ich ExecuteMessage hier ausgeklammert, da ich es zunächst über WndProc bewerkstelligen will.

Also TServer.Listen ruft am Ende die WSAASyncSelect() Funktion auf. Da diese Funktion ein Handle benötigt und ich kein Handle erzeugt habe, übergebe ich ihr die 0. WM_SOCKET ist die Message, die ankommen soll, wenn SOCK FD_SERVER, also entweder FD_READ, FD_CONNECT, FD_CLOSE oder FD_ACCEPT, zurückliefert.
Welches FD_XXX es endgültig ist, erfährt man entweder über lParam oder wParam der Message.

Nun zum Problem:
Es kommt keine Message WM_SOCKET an. Ich vermute es liegt am Handle=0, bin mir aber nicht sicher...
Wie krieg ich es hin, dass WndProc ohne Handle aufgerufen wird von DispatchMessage(Msg)? (eigntl. eine Win-Api Frage)

Habt ihr irgendwelche Ratschläge?
  Mit Zitat antworten Zitat
dde

Registriert seit: 7. Mai 2003
132 Beiträge
 
#3

Problem gelöst!

  Alt 19. Dez 2004, 21:26
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.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:49 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz