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*8;
\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 recvbufsize:integer;
\r
103 function receivestr:string; virtual;
\r
106 procedure internalclose(error:word); virtual;
\r
107 constructor Create(AOwner: TComponent); override;
\r
109 destructor destroy; override;
\r
110 procedure fdcleanup;
\r
111 procedure HandleBackGroundException(E: Exception);
\r
112 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
113 procedure dup(invalue:longint);
\r
115 function sendflush : integer;
\r
116 procedure sendstr(const str : string);virtual;
\r
117 procedure putstringinsendbuffer(const newstring : string);
\r
118 function send(data:pointer;len:integer):integer;virtual;
\r
119 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
120 procedure deletebuffereddata;
\r
122 //procedure messageloop;
\r
123 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
124 procedure flush;virtual;{$ifdef win32} abstract;{$endif}
\r
125 procedure dodatasent(wparam,lparam:longint);
\r
126 procedure doreceiveloop(wparam,lparam:longint);
\r
127 procedure sinkdata(sender:tobject;error:word);
\r
129 procedure release; override; {test -beware}
\r
131 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
133 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
134 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
135 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
137 procedure dupnowatch(invalue:longint);
\r
139 ttimerwrapperinterface=class(tlcomponent)
\r
141 function createwrappedtimer : tobject;virtual;abstract;
\r
142 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
143 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
144 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
145 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
149 timerwrapperinterface : ttimerwrapperinterface;
\r
157 tltimer=class(tlcomponent)
\r
161 wrappedtimer : tobject;
\r
164 // finitialevent : boolean ;
\r
165 fontimer : tnotifyevent ;
\r
166 fenabled : boolean ;
\r
167 finterval : integer ; {miliseconds, default 1000}
\r
169 procedure resettimes;
\r
171 // procedure setinitialevent(newvalue : boolean);
\r
172 procedure setontimer(newvalue:tnotifyevent);
\r
173 procedure setenabled(newvalue : boolean);
\r
174 procedure setinterval(newvalue : integer);
\r
176 //making theese public for now, this code should probablly be restructured later though
\r
177 prevtimer : tltimer ;
\r
178 nexttimer : tltimer ;
\r
179 nextts : ttimeval ;
\r
181 constructor create(aowner:tcomponent);override;
\r
182 destructor destroy;override;
\r
183 // property initialevent : boolean read finitialevent write setinitialevent;
\r
184 property ontimer : tnotifyevent read fontimer write setontimer;
\r
185 property enabled : boolean read fenabled write setenabled;
\r
186 property interval : integer read finterval write setinterval;
\r
190 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
192 tltask=class(tobject)
\r
194 handler : ttaskevent;
\r
199 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
206 procedure processmessages; virtual;abstract;
\r
207 procedure messageloop; virtual;abstract;
\r
208 procedure exitmessageloop; virtual;abstract;
\r
209 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
210 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
211 procedure rmasterclr(fd: integer); virtual;abstract;
\r
212 procedure wmasterset(fd : integer); virtual;abstract;
\r
213 procedure wmasterclr(fd: integer); virtual;abstract;
\r
216 eventcore : teventcore;
\r
218 procedure processmessages;
\r
219 procedure messageloop;
\r
220 procedure exitmessageloop;
\r
223 firstasin : tlasio ;
\r
224 firsttimer : tltimer ;
\r
225 firsttask , lasttask , currenttask : tltask ;
\r
227 numread : integer ;
\r
228 mustrefreshfds : boolean ;
\r
229 { lcoretestcount:integer;}
\r
231 asinreleaseflag:boolean;
\r
234 procedure disconnecttasks(aobj:tobject);
\r
235 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
237 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
239 onaddtask : tonaddtask;
\r
242 procedure sleep(i:integer);
\r
244 procedure prepsigpipe;{$ifndef ver1_0}inline;{$endif}
\r
250 uses {sockets,}lloopback,lsignal;
\r
253 uses windows,winsock;
\r
256 {$include unixstuff.inc}
\r
258 {$include ltimevalstuff.inc}
\r
261 {!!! added sleep call -beware}
\r
262 procedure sleep(i:integer);
\r
269 tv.tv_sec := i div 1000;
\r
270 tv.tv_usec := (i mod 1000) * 1000;
\r
271 select(0,nil,nil,nil,@tv);
\r
275 destructor tlcomponent.destroy;
\r
277 disconnecttasks(self);
\r
284 procedure tlcomponent.release;
\r
289 procedure tlasio.release;
\r
291 asinreleaseflag := true;
\r
295 procedure tlasio.doreceiveloop;
\r
297 if recvq.size = 0 then exit;
\r
298 if assigned(ondataavailable) then ondataavailable(self,0);
\r
299 if not (wsonoreceiveloop in componentoptions) then
\r
300 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
303 function tlasio.receivestr;
\r
305 setlength(result,recvq.size);
\r
306 receive(@result[1],length(result));
\r
309 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
315 if recvq.size < i then i := recvq.size;
\r
317 while (a < i) do begin
\r
318 b := recvq.get(p,i-a);
\r
320 inc(taddrint(buf),b);
\r
325 if wsonoreceiveloop in componentoptions then begin
\r
326 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
330 constructor tlasio.create;
\r
332 inherited create(AOwner);
\r
333 if not assigned(eventcore) then raise exception.create('no event core');
\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) and (state<>wsinvalidstate) 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 if assigned(sendq) then 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
558 if (state=wsconnected) and writetrigger then begin
\r
559 //writeln('write trigger');
\r
561 if (sendq.size >0) then begin
\r
563 sendflushresult := sendflush;
\r
564 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
565 if sendflushresult=0 then begin // linuxerror := 0;
\r
569 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
574 //everything is sent fire off ondatasent event
\r
575 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
576 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
578 if assigned(onfdwrite) then onfdwrite(self,0);
\r
580 writtenthiscycle := false;
\r
581 if (state =wsconnected) and readtrigger then begin
\r
582 if recvq.size=0 then begin
\r
584 if (a <= 0) or (a > sizeof(tempbuf)) then a := sizeof(tempbuf);
\r
585 numread := myfdread(fdhandlein,tempbuf,a);
\r
586 if (numread=0) and (not mustrefreshfds) then begin
\r
587 {if i remember correctly numread=0 is caused by eof
\r
588 if this isn't dealt with then you get a cpu eating infinite loop
\r
589 however if onsessionconencted has called processmessages that could
\r
590 cause us to drop to here with an empty recvq and nothing left to read
\r
591 and we don't want that to cause the socket to close}
\r
594 end else if (numread=-1) then begin
\r
596 //sometimes on windows we get stale messages due to the inherent delays
\r
597 //in the windows message queue
\r
598 if WSAGetLastError = wsaewouldblock then begin
\r
604 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
606 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
609 if recvq.size > 0 then begin
\r
610 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
611 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
612 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
613 tltask.create(self.doreceiveloop,self,0,0);
\r
615 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
616 { debugout('inner loop complete');}
\r
621 procedure tlasio.flush;
\r
626 fd_set(fdhandleout,fds);
\r
627 while sendq.size>0 do begin
\r
628 select(fdhandleout+1,nil,@fds,nil,nil);
\r
629 if sendflush <= 0 then exit;
\r
634 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
636 if assigned(ondatasent) then ondatasent(self,lparam);
\r
639 procedure tlasio.deletebuffereddata;
\r
641 sendq.del(maxlongint);
\r
644 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
646 tlasio(sender).recvq.del(maxlongint);
\r
650 procedure tltimer.resettimes;
\r
652 gettimeofday(nextts);
\r
653 {if not initialevent then} tv_add(nextts,interval);
\r
657 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
659 if newvalue <> finitialevent then begin
\r
660 finitialevent := newvalue;
\r
661 if assigned(timerwrapperinterface) then begin
\r
662 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
669 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
671 if @newvalue <> @fontimer then begin
\r
672 fontimer := newvalue;
\r
673 if assigned(timerwrapperinterface) then begin
\r
674 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
683 procedure tltimer.setenabled(newvalue : boolean);
\r
685 if newvalue <> fenabled then begin
\r
686 fenabled := newvalue;
\r
687 if assigned(timerwrapperinterface) then begin
\r
688 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
691 raise exception.create('non wrapper timers are not permitted on windows');
\r
699 procedure tltimer.setinterval(newvalue:integer);
\r
701 if newvalue <> finterval then begin
\r
702 finterval := newvalue;
\r
703 if assigned(timerwrapperinterface) then begin
\r
704 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
707 raise exception.create('non wrapper timers are not permitted on windows');
\r
719 constructor tltimer.create;
\r
721 inherited create(AOwner);
\r
722 if assigned(timerwrapperinterface) then begin
\r
723 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
727 nexttimer := firsttimer;
\r
730 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
731 firsttimer := self;
\r
739 destructor tltimer.destroy;
\r
741 if assigned(timerwrapperinterface) then begin
\r
744 if prevtimer <> nil then begin
\r
745 prevtimer.nexttimer := nexttimer;
\r
747 firsttimer := nexttimer;
\r
749 if nexttimer <> nil then begin
\r
750 nexttimer.prevtimer := prevtimer;
\r
757 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
760 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
761 handler := ahandler;
\r
765 {nexttask := firsttask;
\r
766 firsttask := self;}
\r
767 if assigned(lasttask) then begin
\r
768 lasttask.nexttask := self;
\r
773 //ahandler(wparam,lparam);
\r
776 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
779 tltask.create(ahandler,aobj,awparam,alparam);
\r
783 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
786 starthandlesignal(sigpipe);
\r
787 if not assigned(signalloopback) then begin
\r
788 signalloopback := tlloopback.create(nil);
\r
789 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
796 procedure processtasks;//inline;
\r
798 temptask : tltask ;
\r
802 if not assigned(currenttask) then begin
\r
803 currenttask := firsttask;
\r
807 while assigned(currenttask) do begin
\r
809 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
810 if assigned(currenttask) then begin
\r
811 temptask := currenttask;
\r
812 currenttask := currenttask.nexttask;
\r
815 //writeln('processed a task');
\r
823 procedure disconnecttasks(aobj:tobject);
\r
825 currenttasklocal : tltask ;
\r
828 for counter := 0 to 1 do begin
\r
829 if counter = 0 then begin
\r
830 currenttasklocal := firsttask; //main list of tasks
\r
832 currenttasklocal := currenttask; //needed in case called from a task
\r
834 // note i don't bother to sestroy the links here as that will happen when
\r
835 // the list of tasks is processed anyway
\r
836 while assigned(currenttasklocal) do begin
\r
837 if currenttasklocal.obj = aobj then begin
\r
838 currenttasklocal.obj := nil;
\r
839 currenttasklocal.handler := nil;
\r
841 currenttasklocal := currenttasklocal.nexttask;
\r
847 procedure processmessages;
\r
849 eventcore.processmessages;
\r
851 procedure messageloop;
\r
853 eventcore.messageloop;
\r
856 procedure exitmessageloop;
\r
858 eventcore.exitmessageloop;
\r
861 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
863 result := myfdwrite(fdhandleout,data^,len);
\r
864 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
865 eventcore.wmasterset(fdhandleout);
\r
868 procedure tlasio.myfdclose(fd : integer);
\r
872 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
874 result := fdwrite(fd,buf,size);
\r
877 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
879 result := fdread(fd,buf,size);
\r
892 signalloopback := nil;
\r