|
Antwort |
Registriert seit: 7. Mai 2003 132 Beiträge |
#1
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 |
Zitat |
Registriert seit: 7. Mai 2003 132 Beiträge |
#2
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 ); Hauptprogramm:
Delphi-Quellcode:
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...
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. So sieht nun uServer.pas aus:
Delphi-Quellcode:
Wie ihr vielleicht erkannt habt, habe ich ExecuteMessage hier ausgeklammert, da ich es zunächst über WndProc bewerkstelligen will.
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;} 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? |
Zitat |
Registriert seit: 7. Mai 2003 132 Beiträge |
#3
Ich habe nun das Problem gelöst. Also:
Zunächst habe ich (doch noch) ein Handle kriiert:
Delphi-Quellcode:
Erläuterung:
(...)
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; 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:
Erläuterung:
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; 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:
Analog gilt die Prozedur für das Lesen von eintreffenden Nachrichten, die der Client geschickt hat.
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; 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. Dies könnte man z.B. wie folgt machen:
Delphi-Quellcode:
lParam enthält nach Aufruf dieser Befehle die FD_CLOSE-Message. Diese wird schließlich im Weiteren in der WndProc-Prozedur verarbeitet.
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; Analog gilt die ganze Sache beim Absturz des Servers. |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |