3 {io and timer code by plugwash}
\r
5 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r
6 For conditions of distribution and use, see copyright notice in zlib_license.txt
\r
7 which is included in the package
\r
8 ----------------------------------------------------------------------------- }
\r
10 {note: you must use the @ in the last param to tltask.create not doing so will
\r
11 compile without error but will cause an access violation -pg}
\r
13 //note: events after release are normal and are the apps responsibility to deal with safely
\r
33 classes,pgtypes,bfifo;
\r
34 procedure processtasks;
\r
43 receivebufsize=1460;
\r
47 sigset= array[0..31] of longint;
\r
50 ESocketException = class(Exception);
\r
51 TBgExceptionEvent = procedure (Sender : TObject;
\r
53 var CanClose : Boolean) of object;
\r
55 // note : tsocketstate is defined in the same way as it is in François PIETTE's twsocket
\r
56 // however tlsocket currently only uses wsClosed wsConnecting wsconnected and wsListening
\r
57 TSocketState = (wsInvalidState,
\r
59 wsConnecting, wsConnected,
\r
60 wsAccepting, wsListening,
\r
63 TWSocketOption = (wsoNoReceiveLoop, wsoTcpNoDelay);
\r
64 TWSocketOptions = set of TWSocketOption;
\r
66 TSocketevent = procedure(Sender: TObject; Error: word) of object;
\r
67 //Tdataavailevent = procedure(data : string);
\r
68 TSendData = procedure (Sender: TObject; BytesSent: Integer) of object;
\r
70 tlcomponent = class(tcomponent)
\r
73 procedure release; virtual;
\r
74 destructor destroy; override;
\r
77 tlasio = class(tlcomponent)
\r
79 state : tsocketstate ;
\r
80 ComponentOptions : TWSocketOptions;
\r
81 fdhandlein : Longint ; {file discriptor}
\r
82 fdhandleout : Longint ; {file discriptor}
\r
84 onsessionclosed : tsocketevent ;
\r
85 ondataAvailable : tsocketevent ;
\r
86 onsessionAvailable : tsocketevent ;
\r
88 onsessionconnected : tsocketevent ;
\r
89 onsenddata : tsenddata ;
\r
90 ondatasent : tsocketevent ;
\r
91 //connected : boolean ;
\r
96 OnBgException : TBgExceptionEvent ;
\r
97 //connectread : boolean ;
\r
99 closehandles : boolean ;
\r
100 writtenthiscycle : boolean ;
\r
101 onfdwrite : procedure (Sender: TObject; Error: word) of object; //added for bewarehttpd
\r
103 destroying:boolean;
\r
104 function receivestr:string; virtual;
\r
107 procedure internalclose(error:word); virtual;
\r
108 constructor Create(AOwner: TComponent); override;
\r
110 destructor destroy; override;
\r
111 procedure fdcleanup;
\r
112 procedure HandleBackGroundException(E: Exception);
\r
113 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
114 procedure dup(invalue:longint);
\r
116 function sendflush : integer;
\r
117 procedure sendstr(const str : string);virtual;
\r
118 procedure putstringinsendbuffer(const newstring : string);
\r
119 function send(data:pointer;len:integer):integer;virtual;
\r
120 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
121 procedure deletebuffereddata;
\r
123 //procedure messageloop;
\r
124 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
125 procedure flush;virtual;{$ifdef win32} abstract;{$endif}
\r
126 procedure dodatasent(wparam,lparam:longint);
\r
127 procedure doreceiveloop(wparam,lparam:longint);
\r
128 procedure sinkdata(sender:tobject;error:word);
\r
130 procedure release; override; {test -beware}
\r
132 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
134 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
135 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
136 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
138 procedure dupnowatch(invalue:longint);
\r
140 ttimerwrapperinterface=class(tlcomponent)
\r
142 function createwrappedtimer : tobject;virtual;abstract;
\r
143 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
144 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
145 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
146 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
150 timerwrapperinterface : ttimerwrapperinterface;
\r
158 tltimer=class(tlcomponent)
\r
162 wrappedtimer : tobject;
\r
165 // finitialevent : boolean ;
\r
166 fontimer : tnotifyevent ;
\r
167 fenabled : boolean ;
\r
168 finterval : integer ; {miliseconds, default 1000}
\r
170 procedure resettimes;
\r
172 // procedure setinitialevent(newvalue : boolean);
\r
173 procedure setontimer(newvalue:tnotifyevent);
\r
174 procedure setenabled(newvalue : boolean);
\r
175 procedure setinterval(newvalue : integer);
\r
177 //making theese public for now, this code should probablly be restructured later though
\r
178 prevtimer : tltimer ;
\r
179 nexttimer : tltimer ;
\r
180 nextts : ttimeval ;
\r
182 constructor create(aowner:tcomponent);override;
\r
183 destructor destroy;override;
\r
184 // property initialevent : boolean read finitialevent write setinitialevent;
\r
185 property ontimer : tnotifyevent read fontimer write setontimer;
\r
186 property enabled : boolean read fenabled write setenabled;
\r
187 property interval : integer read finterval write setinterval;
\r
191 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
193 tltask=class(tobject)
\r
195 handler : ttaskevent;
\r
200 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
207 procedure processmessages; virtual;abstract;
\r
208 procedure messageloop; virtual;abstract;
\r
209 procedure exitmessageloop; virtual;abstract;
\r
210 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
211 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
212 procedure rmasterclr(fd: integer); virtual;abstract;
\r
213 procedure wmasterset(fd : integer); virtual;abstract;
\r
214 procedure wmasterclr(fd: integer); virtual;abstract;
\r
217 eventcore : teventcore;
\r
219 procedure processmessages;
\r
220 procedure messageloop;
\r
221 procedure exitmessageloop;
\r
224 firstasin : tlasio ;
\r
225 firsttimer : tltimer ;
\r
226 firsttask , lasttask , currenttask : tltask ;
\r
228 numread : integer ;
\r
229 mustrefreshfds : boolean ;
\r
230 { lcoretestcount:integer;}
\r
232 asinreleaseflag:boolean;
\r
235 procedure disconnecttasks(aobj:tobject);
\r
236 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
238 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
240 onaddtask : tonaddtask;
\r
243 procedure sleep(i:integer);
\r
245 procedure prepsigpipe;inline;
\r
251 uses {sockets,}lloopback,lsignal;
\r
257 {$include unixstuff.inc}
\r
259 {$include ltimevalstuff.inc}
\r
262 {!!! added sleep call -beware}
\r
263 procedure sleep(i:integer);
\r
270 tv.tv_sec := i div 1000;
\r
271 tv.tv_usec := (i mod 1000) * 1000;
\r
272 select(0,nil,nil,nil,@tv);
\r
276 destructor tlcomponent.destroy;
\r
278 disconnecttasks(self);
\r
285 procedure tlcomponent.release;
\r
290 procedure tlasio.release;
\r
292 asinreleaseflag := true;
\r
296 procedure tlasio.doreceiveloop;
\r
298 if recvq.size = 0 then exit;
\r
299 if assigned(ondataavailable) then ondataavailable(self,0);
\r
300 if not (wsonoreceiveloop in componentoptions) then
\r
301 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
304 function tlasio.receivestr;
\r
306 setlength(result,recvq.size);
\r
307 receive(@result[1],length(result));
\r
310 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
316 if recvq.size < i then i := recvq.size;
\r
318 while (a < i) do begin
\r
319 b := recvq.get(p,i-a);
\r
321 inc(taddrint(buf),b);
\r
326 if wsonoreceiveloop in componentoptions then begin
\r
327 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
331 constructor tlasio.create;
\r
333 inherited create(AOwner);
\r
334 sendq := tfifo.create;
\r
335 recvq := tfifo.create;
\r
339 nextasin := firstasin;
\r
341 if assigned(nextasin) then nextasin.prevasin := self;
\r
347 destructor tlasio.destroy;
\r
349 destroying := true;
\r
350 if state <> wsclosed then close;
\r
351 if prevasin <> nil then begin
\r
352 prevasin.nextasin := nextasin;
\r
354 firstasin := nextasin;
\r
356 if nextasin <> nil then begin
\r
357 nextasin.prevasin := prevasin;
\r
364 procedure tlasio.close;
\r
369 procedure tlasio.abort;
\r
374 procedure tlasio.fdcleanup;
\r
376 if fdhandlein <> -1 then begin
\r
377 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
379 if fdhandleout <> -1 then begin
\r
380 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
382 if fdhandlein=fdhandleout then begin
\r
383 if fdhandlein <> -1 then begin
\r
384 myfdclose(fdhandlein);
\r
387 if fdhandlein <> -1 then begin
\r
388 myfdclose(fdhandlein);
\r
390 if fdhandleout <> -1 then begin
\r
391 myfdclose(fdhandleout);
\r
398 procedure tlasio.internalclose(error:word);
\r
400 if state<>wsclosed then begin
\r
401 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
402 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
403 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
405 if closehandles then begin
\r
407 //anyone remember why this is here? --plugwash
\r
408 fcntl(fdhandlein,F_SETFL,0);
\r
410 myfdclose(fdhandlein);
\r
411 if fdhandleout <> fdhandlein then begin
\r
413 fcntl(fdhandleout,F_SETFL,0);
\r
415 myfdclose(fdhandleout);
\r
417 eventcore.setfdreverse(fdhandlein,nil);
\r
418 eventcore.setfdreverse(fdhandleout,nil);
\r
425 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
427 sendq.del(maxlongint);
\r
431 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
432 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
433 { application will most likely be shut down ! }
\r
434 procedure tlasio.HandleBackGroundException(E: Exception);
\r
436 CanAbort : Boolean;
\r
439 { First call the error event handler, if any }
\r
440 if Assigned(OnBgException) then begin
\r
442 OnBgException(Self, E, CanAbort);
\r
446 { Then abort the socket }
\r
447 if CanAbort then begin
\r
455 procedure tlasio.sendstr(const str : string);
\r
457 putstringinsendbuffer(str);
\r
461 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
463 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
466 function tlasio.send(data:pointer;len:integer):integer;
\r
468 if state <> wsconnected then begin
\r
472 if len < 0 then len := 0;
\r
474 putdatainsendbuffer(data,len);
\r
479 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
481 sendq.add(data,len);
\r
484 function tlasio.sendflush : integer;
\r
488 // fdstestr : fdset;
\r
489 // fdstestw : fdset;
\r
491 if state <> wsconnected then exit;
\r
493 lensent := sendq.get(data,2920);
\r
494 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
496 if result = -1 then lensent := 0 else lensent := result;
\r
498 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
499 sendq.del(lensent);
\r
501 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
502 // that sends nothing because a previous socket has
\r
503 // slready flushed this socket when the message loop
\r
505 // if sendq.size > 0 then begin
\r
506 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
508 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
510 if result > 0 then begin
\r
511 if assigned(onsenddata) then onsenddata(self,result);
\r
512 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
513 // tltask.create(self.dodatasent,self,0,0);
\r
514 // //begin test code
\r
515 // fd_zero(fdstestr);
\r
516 // fd_zero(fdstestw);
\r
517 // fd_set(fdhandlein,fdstestr);
\r
518 // fd_set(fdhandleout,fdstestw);
\r
519 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
520 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
524 writtenthiscycle := true;
\r
528 procedure tlasio.dupnowatch(invalue:longint);
\r
530 { debugout('invalue='+inttostr(invalue));}
\r
532 if state<> wsclosed then close;
\r
533 fdhandlein := invalue;
\r
534 fdhandleout := invalue;
\r
535 eventcore.setfdreverse(fdhandlein,self);
\r
537 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
539 state := wsconnected;
\r
544 procedure tlasio.dup(invalue:longint);
\r
546 dupnowatch(invalue);
\r
547 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
548 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
552 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
554 sendflushresult : integer;
\r
555 tempbuf:array[0..receivebufsize-1] of byte;
\r
557 if (state=wsconnected) and writetrigger then begin
\r
558 //writeln('write trigger');
\r
560 if (sendq.size >0) then begin
\r
562 sendflushresult := sendflush;
\r
563 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
564 if sendflushresult=0 then begin // linuxerror := 0;
\r
568 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
573 //everything is sent fire off ondatasent event
\r
574 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
575 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
577 if assigned(onfdwrite) then onfdwrite(self,0);
\r
579 writtenthiscycle := false;
\r
580 if (state =wsconnected) and readtrigger then begin
\r
581 if recvq.size=0 then begin
\r
582 numread := myfdread(fdhandlein,tempbuf,sizeof(tempbuf));
\r
583 if (numread=0) and (not mustrefreshfds) then begin
\r
584 {if i remember correctly numread=0 is caused by eof
\r
585 if this isn't dealt with then you get a cpu eating infinite loop
\r
586 however if onsessionconencted has called processmessages that could
\r
587 cause us to drop to here with an empty recvq and nothing left to read
\r
588 and we don't want that to cause the socket to close}
\r
591 end else if (numread=-1) then begin
\r
593 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
594 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
597 if recvq.size > 0 then begin
\r
598 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
599 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
600 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
601 tltask.create(self.doreceiveloop,self,0,0);
\r
603 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
604 { debugout('inner loop complete');}
\r
609 procedure tlasio.flush;
\r
614 fd_set(fdhandleout,fds);
\r
615 while sendq.size>0 do begin
\r
616 select(fdhandleout+1,nil,@fds,nil,nil);
\r
617 if sendflush <= 0 then exit;
\r
622 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
624 if assigned(ondatasent) then ondatasent(self,lparam);
\r
627 procedure tlasio.deletebuffereddata;
\r
629 sendq.del(maxlongint);
\r
632 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
634 tlasio(sender).recvq.del(maxlongint);
\r
638 procedure tltimer.resettimes;
\r
640 gettimeofday(nextts);
\r
641 {if not initialevent then} tv_add(nextts,interval);
\r
645 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
647 if newvalue <> finitialevent then begin
\r
648 finitialevent := newvalue;
\r
649 if assigned(timerwrapperinterface) then begin
\r
650 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
657 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
659 if @newvalue <> @fontimer then begin
\r
660 fontimer := newvalue;
\r
661 if assigned(timerwrapperinterface) then begin
\r
662 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
671 procedure tltimer.setenabled(newvalue : boolean);
\r
673 if newvalue <> fenabled then begin
\r
674 fenabled := newvalue;
\r
675 if assigned(timerwrapperinterface) then begin
\r
676 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
679 raise exception.create('non wrapper timers are not permitted on windows');
\r
687 procedure tltimer.setinterval(newvalue:integer);
\r
689 if newvalue <> finterval then begin
\r
690 finterval := newvalue;
\r
691 if assigned(timerwrapperinterface) then begin
\r
692 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
695 raise exception.create('non wrapper timers are not permitted on windows');
\r
707 constructor tltimer.create;
\r
709 inherited create(AOwner);
\r
710 if assigned(timerwrapperinterface) then begin
\r
711 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
715 nexttimer := firsttimer;
\r
718 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
719 firsttimer := self;
\r
727 destructor tltimer.destroy;
\r
729 if assigned(timerwrapperinterface) then begin
\r
732 if prevtimer <> nil then begin
\r
733 prevtimer.nexttimer := nexttimer;
\r
735 firsttimer := nexttimer;
\r
737 if nexttimer <> nil then begin
\r
738 nexttimer.prevtimer := prevtimer;
\r
745 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
748 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
749 handler := ahandler;
\r
753 {nexttask := firsttask;
\r
754 firsttask := self;}
\r
755 if assigned(lasttask) then begin
\r
756 lasttask.nexttask := self;
\r
761 //ahandler(wparam,lparam);
\r
764 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
767 tltask.create(ahandler,aobj,awparam,alparam);
\r
774 procedure prepsigpipe;inline;
\r
776 starthandlesignal(sigpipe);
\r
777 if not assigned(signalloopback) then begin
\r
778 signalloopback := tlloopback.create(nil);
\r
779 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
786 procedure processtasks;//inline;
\r
788 temptask : tltask ;
\r
792 if not assigned(currenttask) then begin
\r
793 currenttask := firsttask;
\r
797 while assigned(currenttask) do begin
\r
799 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
800 if assigned(currenttask) then begin
\r
801 temptask := currenttask;
\r
802 currenttask := currenttask.nexttask;
\r
805 //writeln('processed a task');
\r
813 procedure disconnecttasks(aobj:tobject);
\r
815 currenttasklocal : tltask ;
\r
818 for counter := 0 to 1 do begin
\r
819 if counter = 0 then begin
\r
820 currenttasklocal := firsttask; //main list of tasks
\r
822 currenttasklocal := currenttask; //needed in case called from a task
\r
824 // note i don't bother to sestroy the links here as that will happen when
\r
825 // the list of tasks is processed anyway
\r
826 while assigned(currenttasklocal) do begin
\r
827 if currenttasklocal.obj = aobj then begin
\r
828 currenttasklocal.obj := nil;
\r
829 currenttasklocal.handler := nil;
\r
831 currenttasklocal := currenttasklocal.nexttask;
\r
837 procedure processmessages;
\r
839 eventcore.processmessages;
\r
841 procedure messageloop;
\r
843 eventcore.messageloop;
\r
846 procedure exitmessageloop;
\r
848 eventcore.exitmessageloop;
\r
851 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
853 result := myfdwrite(fdhandleout,data^,len);
\r
854 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
855 eventcore.wmasterset(fdhandleout);
\r
858 procedure tlasio.myfdclose(fd : integer);
\r
862 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
864 result := fdwrite(fd,buf,size);
\r
867 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
869 result := fdread(fd,buf,size);
\r
882 signalloopback := nil;
\r