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
29 baseunix,unix,unixutil,
\r
33 classes,pgtypes,bfifo;
\r
34 procedure processtasks;
\r
38 receivebufsize=1460;
\r
41 absoloutemaxs:integer=0;
\r
45 sigset= array[0..31] of longint;
\r
48 ESocketException = class(Exception);
\r
49 TBgExceptionEvent = procedure (Sender : TObject;
\r
51 var CanClose : Boolean) of object;
\r
53 // note : tsocketstate is defined in the same way as it is in François PIETTE's twsocket
\r
54 // however tlsocket currently only uses wsClosed wsConnecting wsconnected and wsListening
\r
55 TSocketState = (wsInvalidState,
\r
57 wsConnecting, wsConnected,
\r
58 wsAccepting, wsListening,
\r
61 TWSocketOption = (wsoNoReceiveLoop, wsoTcpNoDelay);
\r
62 TWSocketOptions = set of TWSocketOption;
\r
64 TSocketevent = procedure(Sender: TObject; Error: word) of object;
\r
65 //Tdataavailevent = procedure(data : string);
\r
66 TSendData = procedure (Sender: TObject; BytesSent: Integer) of object;
\r
68 tlcomponent = class(tcomponent)
\r
71 procedure release; virtual;
\r
72 destructor destroy; override;
\r
75 tlasio = class(tlcomponent)
\r
77 state : tsocketstate ;
\r
78 ComponentOptions : TWSocketOptions;
\r
79 fdhandlein : Longint ; {file discriptor}
\r
80 fdhandleout : Longint ; {file discriptor}
\r
82 onsessionclosed : tsocketevent ;
\r
83 ondataAvailable : tsocketevent ;
\r
84 onsessionAvailable : tsocketevent ;
\r
86 onsessionconnected : tsocketevent ;
\r
87 onsenddata : tsenddata ;
\r
88 ondatasent : tsocketevent ;
\r
89 //connected : boolean ;
\r
94 OnBgException : TBgExceptionEvent ;
\r
95 //connectread : boolean ;
\r
97 closehandles : boolean ;
\r
98 writtenthiscycle : boolean ;
\r
99 onfdwrite : procedure (Sender: TObject; Error: word) of object; //added for bewarehttpd
\r
101 destroying:boolean;
\r
102 function receivestr:string; virtual;
\r
105 procedure internalclose(error:word); virtual;
\r
106 constructor Create(AOwner: TComponent); override;
\r
108 destructor destroy; override;
\r
109 procedure fdcleanup;
\r
110 procedure HandleBackGroundException(E: Exception);
\r
111 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
112 procedure dup(invalue:longint);
\r
114 function sendflush : integer;
\r
115 procedure sendstr(const str : string);virtual;
\r
116 procedure putstringinsendbuffer(const newstring : string);
\r
117 function send(data:pointer;len:integer):integer;virtual;
\r
118 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
119 procedure deletebuffereddata;
\r
121 //procedure messageloop;
\r
122 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
123 procedure flush;virtual;{$ifdef win32} abstract;{$endif}
\r
124 procedure dodatasent(wparam,lparam:longint);
\r
125 procedure doreceiveloop(wparam,lparam:longint);
\r
126 procedure sinkdata(sender:tobject;error:word);
\r
128 procedure release; override; {test -beware}
\r
130 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
132 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
133 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
134 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
136 procedure dupnowatch(invalue:longint);
\r
138 ttimerwrapperinterface=class(tlcomponent)
\r
140 function createwrappedtimer : tobject;virtual;abstract;
\r
141 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
142 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
143 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
144 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
148 timerwrapperinterface : ttimerwrapperinterface;
\r
156 tltimer=class(tlcomponent)
\r
160 wrappedtimer : tobject;
\r
163 // finitialevent : boolean ;
\r
164 fontimer : tnotifyevent ;
\r
165 fenabled : boolean ;
\r
166 finterval : integer ; {miliseconds, default 1000}
\r
168 procedure resettimes;
\r
170 // procedure setinitialevent(newvalue : boolean);
\r
171 procedure setontimer(newvalue:tnotifyevent);
\r
172 procedure setenabled(newvalue : boolean);
\r
173 procedure setinterval(newvalue : integer);
\r
175 //making theese public for now, this code should probablly be restructured later though
\r
176 prevtimer : tltimer ;
\r
177 nexttimer : tltimer ;
\r
178 nextts : ttimeval ;
\r
180 constructor create(aowner:tcomponent);override;
\r
181 destructor destroy;override;
\r
182 // property initialevent : boolean read finitialevent write setinitialevent;
\r
183 property ontimer : tnotifyevent read fontimer write setontimer;
\r
184 property enabled : boolean read fenabled write setenabled;
\r
185 property interval : integer read finterval write setinterval;
\r
189 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
191 tltask=class(tobject)
\r
193 handler : ttaskevent;
\r
198 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
205 procedure processmessages; virtual;abstract;
\r
206 procedure messageloop; virtual;abstract;
\r
207 procedure exitmessageloop; virtual;abstract;
\r
208 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
209 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
210 procedure rmasterclr(fd: integer); virtual;abstract;
\r
211 procedure wmasterset(fd : integer); virtual;abstract;
\r
212 procedure wmasterclr(fd: integer); virtual;abstract;
\r
215 eventcore : teventcore;
\r
217 procedure processmessages;
\r
218 procedure messageloop;
\r
219 procedure exitmessageloop;
\r
222 firstasin : tlasio ;
\r
223 firsttimer : tltimer ;
\r
224 firsttask , lasttask , currenttask : tltask ;
\r
226 numread : integer ;
\r
227 mustrefreshfds : boolean ;
\r
228 { lcoretestcount:integer;}
\r
230 asinreleaseflag:boolean;
\r
233 procedure disconnecttasks(aobj:tobject);
\r
234 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
236 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
238 onaddtask : tonaddtask;
\r
241 procedure sleep(i:integer);
\r
243 procedure prepsigpipe;{$ifndef ver1_0}inline;{$endif}
\r
249 uses {sockets,}lloopback,lsignal;
\r
252 uses windows,winsock;
\r
255 {$include unixstuff.inc}
\r
257 {$include ltimevalstuff.inc}
\r
260 {!!! added sleep call -beware}
\r
261 procedure sleep(i:integer);
\r
268 tv.tv_sec := i div 1000;
\r
269 tv.tv_usec := (i mod 1000) * 1000;
\r
270 select(0,nil,nil,nil,@tv);
\r
274 destructor tlcomponent.destroy;
\r
276 disconnecttasks(self);
\r
283 procedure tlcomponent.release;
\r
288 procedure tlasio.release;
\r
290 asinreleaseflag := true;
\r
294 procedure tlasio.doreceiveloop;
\r
296 if recvq.size = 0 then exit;
\r
297 if assigned(ondataavailable) then ondataavailable(self,0);
\r
298 if not (wsonoreceiveloop in componentoptions) then
\r
299 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
302 function tlasio.receivestr;
\r
304 setlength(result,recvq.size);
\r
305 receive(@result[1],length(result));
\r
308 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
314 if recvq.size < i then i := recvq.size;
\r
316 while (a < i) do begin
\r
317 b := recvq.get(p,i-a);
\r
319 inc(taddrint(buf),b);
\r
324 if wsonoreceiveloop in componentoptions then begin
\r
325 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
329 constructor tlasio.create;
\r
331 inherited create(AOwner);
\r
332 if not assigned(eventcore) then raise exception.create('no event core');
\r
333 sendq := tfifo.create;
\r
334 recvq := tfifo.create;
\r
338 nextasin := firstasin;
\r
340 if assigned(nextasin) then nextasin.prevasin := self;
\r
346 destructor tlasio.destroy;
\r
348 destroying := true;
\r
349 if state <> wsclosed then close;
\r
350 if prevasin <> nil then begin
\r
351 prevasin.nextasin := nextasin;
\r
353 firstasin := nextasin;
\r
355 if nextasin <> nil then begin
\r
356 nextasin.prevasin := prevasin;
\r
363 procedure tlasio.close;
\r
368 procedure tlasio.abort;
\r
373 procedure tlasio.fdcleanup;
\r
375 if fdhandlein <> -1 then begin
\r
376 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
378 if fdhandleout <> -1 then begin
\r
379 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
381 if fdhandlein=fdhandleout then begin
\r
382 if fdhandlein <> -1 then begin
\r
383 myfdclose(fdhandlein);
\r
386 if fdhandlein <> -1 then begin
\r
387 myfdclose(fdhandlein);
\r
389 if fdhandleout <> -1 then begin
\r
390 myfdclose(fdhandleout);
\r
397 procedure tlasio.internalclose(error:word);
\r
399 if (state<>wsclosed) and (state<>wsinvalidstate) then begin
\r
400 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
401 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
402 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
404 if closehandles then begin
\r
406 //anyone remember why this is here? --plugwash
\r
407 fcntl(fdhandlein,F_SETFL,0);
\r
409 myfdclose(fdhandlein);
\r
410 if fdhandleout <> fdhandlein then begin
\r
412 fcntl(fdhandleout,F_SETFL,0);
\r
414 myfdclose(fdhandleout);
\r
416 eventcore.setfdreverse(fdhandlein,nil);
\r
417 eventcore.setfdreverse(fdhandleout,nil);
\r
424 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
426 if assigned(sendq) then sendq.del(maxlongint);
\r
430 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
431 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
432 { application will most likely be shut down ! }
\r
433 procedure tlasio.HandleBackGroundException(E: Exception);
\r
435 CanAbort : Boolean;
\r
438 { First call the error event handler, if any }
\r
439 if Assigned(OnBgException) then begin
\r
441 OnBgException(Self, E, CanAbort);
\r
445 { Then abort the socket }
\r
446 if CanAbort then begin
\r
454 procedure tlasio.sendstr(const str : string);
\r
456 putstringinsendbuffer(str);
\r
460 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
462 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
465 function tlasio.send(data:pointer;len:integer):integer;
\r
467 if state <> wsconnected then begin
\r
471 if len < 0 then len := 0;
\r
473 putdatainsendbuffer(data,len);
\r
478 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
480 sendq.add(data,len);
\r
483 function tlasio.sendflush : integer;
\r
487 // fdstestr : fdset;
\r
488 // fdstestw : fdset;
\r
490 if state <> wsconnected then exit;
\r
492 lensent := sendq.get(data,2920);
\r
493 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
495 if result = -1 then lensent := 0 else lensent := result;
\r
497 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
498 sendq.del(lensent);
\r
500 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
501 // that sends nothing because a previous socket has
\r
502 // slready flushed this socket when the message loop
\r
504 // if sendq.size > 0 then begin
\r
505 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
507 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
509 if result > 0 then begin
\r
510 if assigned(onsenddata) then onsenddata(self,result);
\r
511 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
512 // tltask.create(self.dodatasent,self,0,0);
\r
513 // //begin test code
\r
514 // fd_zero(fdstestr);
\r
515 // fd_zero(fdstestw);
\r
516 // fd_set(fdhandlein,fdstestr);
\r
517 // fd_set(fdhandleout,fdstestw);
\r
518 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
519 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
523 writtenthiscycle := true;
\r
527 procedure tlasio.dupnowatch(invalue:longint);
\r
529 { debugout('invalue='+inttostr(invalue));}
\r
531 if state<> wsclosed then close;
\r
532 fdhandlein := invalue;
\r
533 fdhandleout := invalue;
\r
534 eventcore.setfdreverse(fdhandlein,self);
\r
536 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
538 state := wsconnected;
\r
543 procedure tlasio.dup(invalue:longint);
\r
545 dupnowatch(invalue);
\r
546 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
547 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
551 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
553 sendflushresult : integer;
\r
554 tempbuf:array[0..receivebufsize-1] of byte;
\r
556 if (state=wsconnected) and writetrigger then begin
\r
557 //writeln('write trigger');
\r
559 if (sendq.size >0) then begin
\r
561 sendflushresult := sendflush;
\r
562 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
563 if sendflushresult=0 then begin // linuxerror := 0;
\r
567 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
572 //everything is sent fire off ondatasent event
\r
573 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
574 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
576 if assigned(onfdwrite) then onfdwrite(self,0);
\r
578 writtenthiscycle := false;
\r
579 if (state =wsconnected) and readtrigger then begin
\r
580 if recvq.size=0 then begin
\r
581 numread := myfdread(fdhandlein,tempbuf,sizeof(tempbuf));
\r
582 if (numread=0) and (not mustrefreshfds) then begin
\r
583 {if i remember correctly numread=0 is caused by eof
\r
584 if this isn't dealt with then you get a cpu eating infinite loop
\r
585 however if onsessionconencted has called processmessages that could
\r
586 cause us to drop to here with an empty recvq and nothing left to read
\r
587 and we don't want that to cause the socket to close}
\r
590 end else if (numread=-1) then begin
\r
592 //sometimes on windows we get stale messages due to the inherent delays
\r
593 //in the windows message queue
\r
594 if WSAGetLastError = wsaewouldblock then begin
\r
600 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
602 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
605 if recvq.size > 0 then begin
\r
606 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
607 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
608 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
609 tltask.create(self.doreceiveloop,self,0,0);
\r
611 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
612 { debugout('inner loop complete');}
\r
617 procedure tlasio.flush;
\r
622 fd_set(fdhandleout,fds);
\r
623 while sendq.size>0 do begin
\r
624 select(fdhandleout+1,nil,@fds,nil,nil);
\r
625 if sendflush <= 0 then exit;
\r
630 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
632 if assigned(ondatasent) then ondatasent(self,lparam);
\r
635 procedure tlasio.deletebuffereddata;
\r
637 sendq.del(maxlongint);
\r
640 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
642 tlasio(sender).recvq.del(maxlongint);
\r
646 procedure tltimer.resettimes;
\r
648 gettimeofday(nextts);
\r
649 {if not initialevent then} tv_add(nextts,interval);
\r
653 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
655 if newvalue <> finitialevent then begin
\r
656 finitialevent := newvalue;
\r
657 if assigned(timerwrapperinterface) then begin
\r
658 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
665 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
667 if @newvalue <> @fontimer then begin
\r
668 fontimer := newvalue;
\r
669 if assigned(timerwrapperinterface) then begin
\r
670 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
679 procedure tltimer.setenabled(newvalue : boolean);
\r
681 if newvalue <> fenabled then begin
\r
682 fenabled := newvalue;
\r
683 if assigned(timerwrapperinterface) then begin
\r
684 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
687 raise exception.create('non wrapper timers are not permitted on windows');
\r
695 procedure tltimer.setinterval(newvalue:integer);
\r
697 if newvalue <> finterval then begin
\r
698 finterval := newvalue;
\r
699 if assigned(timerwrapperinterface) then begin
\r
700 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
703 raise exception.create('non wrapper timers are not permitted on windows');
\r
715 constructor tltimer.create;
\r
717 inherited create(AOwner);
\r
718 if assigned(timerwrapperinterface) then begin
\r
719 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
723 nexttimer := firsttimer;
\r
726 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
727 firsttimer := self;
\r
735 destructor tltimer.destroy;
\r
737 if assigned(timerwrapperinterface) then begin
\r
740 if prevtimer <> nil then begin
\r
741 prevtimer.nexttimer := nexttimer;
\r
743 firsttimer := nexttimer;
\r
745 if nexttimer <> nil then begin
\r
746 nexttimer.prevtimer := prevtimer;
\r
753 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
756 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
757 handler := ahandler;
\r
761 {nexttask := firsttask;
\r
762 firsttask := self;}
\r
763 if assigned(lasttask) then begin
\r
764 lasttask.nexttask := self;
\r
769 //ahandler(wparam,lparam);
\r
772 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
775 tltask.create(ahandler,aobj,awparam,alparam);
\r
779 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
782 starthandlesignal(sigpipe);
\r
783 if not assigned(signalloopback) then begin
\r
784 signalloopback := tlloopback.create(nil);
\r
785 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
792 procedure processtasks;//inline;
\r
794 temptask : tltask ;
\r
798 if not assigned(currenttask) then begin
\r
799 currenttask := firsttask;
\r
803 while assigned(currenttask) do begin
\r
805 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
806 if assigned(currenttask) then begin
\r
807 temptask := currenttask;
\r
808 currenttask := currenttask.nexttask;
\r
811 //writeln('processed a task');
\r
819 procedure disconnecttasks(aobj:tobject);
\r
821 currenttasklocal : tltask ;
\r
824 for counter := 0 to 1 do begin
\r
825 if counter = 0 then begin
\r
826 currenttasklocal := firsttask; //main list of tasks
\r
828 currenttasklocal := currenttask; //needed in case called from a task
\r
830 // note i don't bother to sestroy the links here as that will happen when
\r
831 // the list of tasks is processed anyway
\r
832 while assigned(currenttasklocal) do begin
\r
833 if currenttasklocal.obj = aobj then begin
\r
834 currenttasklocal.obj := nil;
\r
835 currenttasklocal.handler := nil;
\r
837 currenttasklocal := currenttasklocal.nexttask;
\r
843 procedure processmessages;
\r
845 eventcore.processmessages;
\r
847 procedure messageloop;
\r
849 eventcore.messageloop;
\r
852 procedure exitmessageloop;
\r
854 eventcore.exitmessageloop;
\r
857 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
859 result := myfdwrite(fdhandleout,data^,len);
\r
860 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
861 eventcore.wmasterset(fdhandleout);
\r
864 procedure tlasio.myfdclose(fd : integer);
\r
868 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
870 result := fdwrite(fd,buf,size);
\r
873 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
875 result := fdread(fd,buf,size);
\r
888 signalloopback := nil;
\r