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 {for IPv6 it is 1440: 1500 - IP header (40) - TCP/UDP header (20). for ipv4 it is 1460}
\r
39 packetbasesize = 1440;
\r
40 receivebufsize=packetbasesize*8;
\r
43 absoloutemaxs:integer=0;
\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 recvbufsize:integer;
\r
105 function receivestr:string; virtual;
\r
108 procedure internalclose(error:word); virtual;
\r
109 constructor Create(AOwner: TComponent); override;
\r
111 destructor destroy; override;
\r
112 procedure fdcleanup;
\r
113 procedure HandleBackGroundException(E: Exception);
\r
114 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
115 procedure dup(invalue:longint);
\r
117 function sendflush : integer;
\r
118 procedure sendstr(const str : string);virtual;
\r
119 procedure putstringinsendbuffer(const newstring : string);
\r
120 function send(data:pointer;len:integer):integer;virtual;
\r
121 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
122 procedure deletebuffereddata;
\r
124 //procedure messageloop;
\r
125 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
126 procedure flush;virtual;
\r
127 procedure dodatasent(wparam,lparam:longint);
\r
128 procedure doreceiveloop(wparam,lparam:longint);
\r
129 procedure sinkdata(sender:tobject;error:word);
\r
131 procedure release; override; {test -beware}
\r
133 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
135 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
136 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
137 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
139 procedure dupnowatch(invalue:longint);
\r
141 ttimerwrapperinterface=class(tlcomponent)
\r
143 function createwrappedtimer : tobject;virtual;abstract;
\r
144 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
145 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
146 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
147 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
151 timerwrapperinterface : ttimerwrapperinterface;
\r
159 tltimer=class(tlcomponent)
\r
163 wrappedtimer : tobject;
\r
166 // finitialevent : boolean ;
\r
167 fontimer : tnotifyevent ;
\r
168 fenabled : boolean ;
\r
169 finterval : integer ; {miliseconds, default 1000}
\r
171 procedure resettimes;
\r
173 // procedure setinitialevent(newvalue : boolean);
\r
174 procedure setontimer(newvalue:tnotifyevent);
\r
175 procedure setenabled(newvalue : boolean);
\r
176 procedure setinterval(newvalue : integer);
\r
178 //making theese public for now, this code should probablly be restructured later though
\r
179 prevtimer : tltimer ;
\r
180 nexttimer : tltimer ;
\r
181 nextts : ttimeval ;
\r
183 constructor create(aowner:tcomponent);override;
\r
184 destructor destroy;override;
\r
185 // property initialevent : boolean read finitialevent write setinitialevent;
\r
186 property ontimer : tnotifyevent read fontimer write setontimer;
\r
187 property enabled : boolean read fenabled write setenabled;
\r
188 property interval : integer read finterval write setinterval;
\r
192 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
194 tltask=class(tobject)
\r
196 handler : ttaskevent;
\r
201 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
208 procedure processmessages; virtual;abstract;
\r
209 procedure messageloop; virtual;abstract;
\r
210 procedure exitmessageloop; virtual;abstract;
\r
211 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
212 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
213 procedure rmasterclr(fd: integer); virtual;abstract;
\r
214 procedure wmasterset(fd : integer); virtual;abstract;
\r
215 procedure wmasterclr(fd: integer); virtual;abstract;
\r
218 eventcore : teventcore;
\r
220 procedure processmessages;
\r
221 procedure messageloop;
\r
222 procedure exitmessageloop;
\r
225 firstasin : tlasio ;
\r
226 firsttimer : tltimer ;
\r
227 firsttask , lasttask , currenttask : tltask ;
\r
229 numread : integer ;
\r
230 mustrefreshfds : boolean ;
\r
231 { lcoretestcount:integer;}
\r
233 asinreleaseflag:boolean;
\r
236 procedure disconnecttasks(aobj:tobject);
\r
237 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
239 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
241 onaddtask : tonaddtask;
\r
244 procedure sleep(i:integer);
\r
246 procedure prepsigpipe;{$ifndef ver1_0}inline;{$endif}
\r
252 uses {sockets,}lloopback,lsignal;
\r
255 uses windows,winsock;
\r
258 {$include unixstuff.inc}
\r
260 {$include ltimevalstuff.inc}
\r
263 {!!! added sleep call -beware}
\r
264 procedure sleep(i:integer);
\r
271 tv.tv_sec := i div 1000;
\r
272 tv.tv_usec := (i mod 1000) * 1000;
\r
273 select(0,nil,nil,nil,@tv);
\r
277 destructor tlcomponent.destroy;
\r
279 disconnecttasks(self);
\r
286 procedure tlcomponent.release;
\r
291 procedure tlasio.release;
\r
293 asinreleaseflag := true;
\r
297 procedure tlasio.doreceiveloop;
\r
299 if recvq.size = 0 then exit;
\r
300 if assigned(ondataavailable) then ondataavailable(self,0);
\r
301 if not (wsonoreceiveloop in componentoptions) then
\r
302 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
305 function tlasio.receivestr;
\r
307 setlength(result,recvq.size);
\r
308 receive(@result[1],length(result));
\r
311 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
317 if recvq.size < i then i := recvq.size;
\r
319 while (a < i) do begin
\r
320 b := recvq.get(p,i-a);
\r
322 inc(taddrint(buf),b);
\r
327 if wsonoreceiveloop in componentoptions then begin
\r
328 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
332 constructor tlasio.create;
\r
334 inherited create(AOwner);
\r
335 if not assigned(eventcore) then raise exception.create('no event core');
\r
336 sendq := tfifo.create;
\r
337 recvq := tfifo.create;
\r
341 nextasin := firstasin;
\r
343 if assigned(nextasin) then nextasin.prevasin := self;
\r
349 destructor tlasio.destroy;
\r
351 destroying := true;
\r
352 if state <> wsclosed then close;
\r
353 if prevasin <> nil then begin
\r
354 prevasin.nextasin := nextasin;
\r
356 firstasin := nextasin;
\r
358 if nextasin <> nil then begin
\r
359 nextasin.prevasin := prevasin;
\r
366 procedure tlasio.close;
\r
371 procedure tlasio.abort;
\r
376 procedure tlasio.fdcleanup;
\r
378 if fdhandlein <> -1 then begin
\r
379 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
381 if fdhandleout <> -1 then begin
\r
382 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
384 if fdhandlein=fdhandleout then begin
\r
385 if fdhandlein <> -1 then begin
\r
386 myfdclose(fdhandlein);
\r
389 if fdhandlein <> -1 then begin
\r
390 myfdclose(fdhandlein);
\r
392 if fdhandleout <> -1 then begin
\r
393 myfdclose(fdhandleout);
\r
400 procedure tlasio.internalclose(error:word);
\r
402 if (state<>wsclosed) and (state<>wsinvalidstate) then begin
\r
403 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
404 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
405 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
407 if closehandles then begin
\r
409 //anyone remember why this is here? --plugwash
\r
410 fcntl(fdhandlein,F_SETFL,0);
\r
412 myfdclose(fdhandlein);
\r
413 if fdhandleout <> fdhandlein then begin
\r
415 fcntl(fdhandleout,F_SETFL,0);
\r
417 myfdclose(fdhandleout);
\r
419 eventcore.setfdreverse(fdhandlein,nil);
\r
420 eventcore.setfdreverse(fdhandleout,nil);
\r
427 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
429 if assigned(sendq) then sendq.del(maxlongint);
\r
433 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
434 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
435 { application will most likely be shut down ! }
\r
436 procedure tlasio.HandleBackGroundException(E: Exception);
\r
438 CanAbort : Boolean;
\r
441 { First call the error event handler, if any }
\r
442 if Assigned(OnBgException) then begin
\r
444 OnBgException(Self, E, CanAbort);
\r
448 { Then abort the socket }
\r
449 if CanAbort then begin
\r
457 procedure tlasio.sendstr(const str : string);
\r
459 putstringinsendbuffer(str);
\r
463 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
465 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
468 function tlasio.send(data:pointer;len:integer):integer;
\r
470 if state <> wsconnected then begin
\r
474 if len < 0 then len := 0;
\r
476 putdatainsendbuffer(data,len);
\r
481 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
483 sendq.add(data,len);
\r
486 function tlasio.sendflush : integer;
\r
490 // fdstestr : fdset;
\r
491 // fdstestw : fdset;
\r
493 if state <> wsconnected then exit;
\r
495 lensent := sendq.get(data,packetbasesize*2);
\r
496 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
498 if result = -1 then lensent := 0 else lensent := result;
\r
500 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
501 sendq.del(lensent);
\r
503 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
504 // that sends nothing because a previous socket has
\r
505 // slready flushed this socket when the message loop
\r
507 // if sendq.size > 0 then begin
\r
508 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
510 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
512 if result > 0 then begin
\r
513 if assigned(onsenddata) then onsenddata(self,result);
\r
514 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
515 // tltask.create(self.dodatasent,self,0,0);
\r
516 // //begin test code
\r
517 // fd_zero(fdstestr);
\r
518 // fd_zero(fdstestw);
\r
519 // fd_set(fdhandlein,fdstestr);
\r
520 // fd_set(fdhandleout,fdstestw);
\r
521 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
522 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
526 writtenthiscycle := true;
\r
530 procedure tlasio.dupnowatch(invalue:longint);
\r
532 { debugout('invalue='+inttostr(invalue));}
\r
534 if state<> wsclosed then close;
\r
535 fdhandlein := invalue;
\r
536 fdhandleout := invalue;
\r
537 eventcore.setfdreverse(fdhandlein,self);
\r
539 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
541 state := wsconnected;
\r
546 procedure tlasio.dup(invalue:longint);
\r
548 dupnowatch(invalue);
\r
549 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
550 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
554 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
556 sendflushresult : integer;
\r
557 tempbuf:array[0..receivebufsize-1] of byte;
\r
560 if (state=wsconnected) and writetrigger then begin
\r
561 //writeln('write trigger');
\r
563 if (sendq.size >0) then begin
\r
565 sendflushresult := sendflush;
\r
566 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
567 if sendflushresult=0 then begin // linuxerror := 0;
\r
571 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
576 //everything is sent fire off ondatasent event
\r
577 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
578 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
580 if assigned(onfdwrite) then onfdwrite(self,0);
\r
582 writtenthiscycle := false;
\r
583 if (state =wsconnected) and readtrigger then begin
\r
584 if recvq.size=0 then begin
\r
586 if (a <= 0) or (a > sizeof(tempbuf)) then a := sizeof(tempbuf);
\r
587 numread := myfdread(fdhandlein,tempbuf,a);
\r
588 if (numread=0) and (not mustrefreshfds) then begin
\r
589 {if i remember correctly numread=0 is caused by eof
\r
590 if this isn't dealt with then you get a cpu eating infinite loop
\r
591 however if onsessionconencted has called processmessages that could
\r
592 cause us to drop to here with an empty recvq and nothing left to read
\r
593 and we don't want that to cause the socket to close}
\r
596 end else if (numread=-1) then begin
\r
598 //sometimes on windows we get stale messages due to the inherent delays
\r
599 //in the windows message queue
\r
600 if WSAGetLastError = wsaewouldblock then begin
\r
606 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
608 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
611 if recvq.size > 0 then begin
\r
612 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
613 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
614 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
615 tltask.create(self.doreceiveloop,self,0,0);
\r
617 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
618 { debugout('inner loop complete');}
\r
622 procedure tlasio.flush;
\r
624 type fdset = tfdset;
\r
630 fd_set(fdhandleout,fds);
\r
631 while sendq.size>0 do begin
\r
632 select(fdhandleout+1,nil,@fds,nil,nil);
\r
633 if sendflush <= 0 then exit;
\r
637 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
639 if assigned(ondatasent) then ondatasent(self,lparam);
\r
642 procedure tlasio.deletebuffereddata;
\r
644 sendq.del(maxlongint);
\r
647 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
649 tlasio(sender).recvq.del(maxlongint);
\r
653 procedure tltimer.resettimes;
\r
655 gettimeofday(nextts);
\r
656 {if not initialevent then} tv_add(nextts,interval);
\r
660 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
662 if newvalue <> finitialevent then begin
\r
663 finitialevent := newvalue;
\r
664 if assigned(timerwrapperinterface) then begin
\r
665 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
672 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
674 if @newvalue <> @fontimer then begin
\r
675 fontimer := newvalue;
\r
676 if assigned(timerwrapperinterface) then begin
\r
677 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
686 procedure tltimer.setenabled(newvalue : boolean);
\r
688 if newvalue <> fenabled then begin
\r
689 fenabled := newvalue;
\r
690 if assigned(timerwrapperinterface) then begin
\r
691 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
694 raise exception.create('non wrapper timers are not permitted on windows');
\r
702 procedure tltimer.setinterval(newvalue:integer);
\r
704 if newvalue <> finterval then begin
\r
705 finterval := newvalue;
\r
706 if assigned(timerwrapperinterface) then begin
\r
707 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
710 raise exception.create('non wrapper timers are not permitted on windows');
\r
722 constructor tltimer.create;
\r
724 inherited create(AOwner);
\r
725 if assigned(timerwrapperinterface) then begin
\r
726 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
730 nexttimer := firsttimer;
\r
733 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
734 firsttimer := self;
\r
742 destructor tltimer.destroy;
\r
744 if assigned(timerwrapperinterface) then begin
\r
747 if prevtimer <> nil then begin
\r
748 prevtimer.nexttimer := nexttimer;
\r
750 firsttimer := nexttimer;
\r
752 if nexttimer <> nil then begin
\r
753 nexttimer.prevtimer := prevtimer;
\r
760 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
763 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
764 handler := ahandler;
\r
768 {nexttask := firsttask;
\r
769 firsttask := self;}
\r
770 if assigned(lasttask) then begin
\r
771 lasttask.nexttask := self;
\r
776 //ahandler(wparam,lparam);
\r
779 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
782 tltask.create(ahandler,aobj,awparam,alparam);
\r
786 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
789 starthandlesignal(sigpipe);
\r
790 if not assigned(signalloopback) then begin
\r
791 signalloopback := tlloopback.create(nil);
\r
792 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
799 procedure processtasks;//inline;
\r
801 temptask : tltask ;
\r
805 if not assigned(currenttask) then begin
\r
806 currenttask := firsttask;
\r
810 while assigned(currenttask) do begin
\r
812 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
813 if assigned(currenttask) then begin
\r
814 temptask := currenttask;
\r
815 currenttask := currenttask.nexttask;
\r
818 //writeln('processed a task');
\r
826 procedure disconnecttasks(aobj:tobject);
\r
828 currenttasklocal : tltask ;
\r
831 for counter := 0 to 1 do begin
\r
832 if counter = 0 then begin
\r
833 currenttasklocal := firsttask; //main list of tasks
\r
835 currenttasklocal := currenttask; //needed in case called from a task
\r
837 // note i don't bother to sestroy the links here as that will happen when
\r
838 // the list of tasks is processed anyway
\r
839 while assigned(currenttasklocal) do begin
\r
840 if currenttasklocal.obj = aobj then begin
\r
841 currenttasklocal.obj := nil;
\r
842 currenttasklocal.handler := nil;
\r
844 currenttasklocal := currenttasklocal.nexttask;
\r
850 procedure processmessages;
\r
852 eventcore.processmessages;
\r
854 procedure messageloop;
\r
856 eventcore.messageloop;
\r
859 procedure exitmessageloop;
\r
861 eventcore.exitmessageloop;
\r
864 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
866 result := myfdwrite(fdhandleout,data^,len);
\r
867 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
868 eventcore.wmasterset(fdhandleout);
\r
871 procedure tlasio.myfdclose(fd : integer);
\r
875 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
877 result := fdwrite(fd,buf,size);
\r
880 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
882 result := fdread(fd,buf,size);
\r
895 signalloopback := nil;
\r