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 {how this number is made up:
\r
39 - ethernet: MTU 1500
\r
40 - be safe for either "ethernet v1" or "PPPoE", both take 8 bytes
\r
41 - IPv6 header: 40 bytes (IPv4 is 20)
\r
42 - TCP/UDP header: 20 bytes
\r
44 packetbasesize = 1432;
\r
45 receivebufsize=packetbasesize*8;
\r
48 absoloutemaxs:integer=0;
\r
52 sigset= array[0..31] of longint;
\r
55 ESocketException = class(Exception);
\r
56 TBgExceptionEvent = procedure (Sender : TObject;
\r
58 var CanClose : Boolean) of object;
\r
60 // note : tsocketstate is defined in the same way as it is in François PIETTE's twsocket
\r
61 // however tlsocket currently only uses wsClosed wsConnecting wsconnected and wsListening
\r
62 TSocketState = (wsInvalidState,
\r
64 wsConnecting, wsConnected,
\r
65 wsAccepting, wsListening,
\r
68 TWSocketOption = (wsoNoReceiveLoop, wsoTcpNoDelay);
\r
69 TWSocketOptions = set of TWSocketOption;
\r
71 TSocketevent = procedure(Sender: TObject; Error: word) of object;
\r
72 //Tdataavailevent = procedure(data : string);
\r
73 TSendData = procedure (Sender: TObject; BytesSent: Integer) of object;
\r
75 tlcomponent = class(tcomponent)
\r
78 procedure release; virtual;
\r
79 destructor destroy; override;
\r
82 tlasio = class(tlcomponent)
\r
84 state : tsocketstate ;
\r
85 ComponentOptions : TWSocketOptions;
\r
86 fdhandlein : Longint ; {file discriptor}
\r
87 fdhandleout : Longint ; {file discriptor}
\r
89 onsessionclosed : tsocketevent ;
\r
90 ondataAvailable : tsocketevent ;
\r
91 onsessionAvailable : tsocketevent ;
\r
93 onsessionconnected : tsocketevent ;
\r
94 onsenddata : tsenddata ;
\r
95 ondatasent : tsocketevent ;
\r
96 //connected : boolean ;
\r
101 OnBgException : TBgExceptionEvent ;
\r
102 //connectread : boolean ;
\r
104 closehandles : boolean ;
\r
105 writtenthiscycle : boolean ;
\r
106 onfdwrite : procedure (Sender: TObject; Error: word) of object; //added for bewarehttpd
\r
108 destroying:boolean;
\r
109 recvbufsize:integer;
\r
110 function receivestr:string; virtual;
\r
113 procedure internalclose(error:word); virtual;
\r
114 constructor Create(AOwner: TComponent); override;
\r
116 destructor destroy; override;
\r
117 procedure fdcleanup;
\r
118 procedure HandleBackGroundException(E: Exception);
\r
119 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
120 procedure dup(invalue:longint);
\r
122 function sendflush : integer;
\r
123 procedure sendstr(const str : string);virtual;
\r
124 procedure putstringinsendbuffer(const newstring : string);
\r
125 function send(data:pointer;len:integer):integer;virtual;
\r
126 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
127 procedure deletebuffereddata;
\r
129 //procedure messageloop;
\r
130 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
131 procedure flush;virtual;
\r
132 procedure dodatasent(wparam,lparam:longint);
\r
133 procedure doreceiveloop(wparam,lparam:longint);
\r
134 procedure sinkdata(sender:tobject;error:word);
\r
136 procedure release; override; {test -beware}
\r
138 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
140 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
141 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
142 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
144 procedure dupnowatch(invalue:longint);
\r
146 ttimerwrapperinterface=class(tlcomponent)
\r
148 function createwrappedtimer : tobject;virtual;abstract;
\r
149 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
150 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
151 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
152 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
156 timerwrapperinterface : ttimerwrapperinterface;
\r
164 tltimer=class(tlcomponent)
\r
168 wrappedtimer : tobject;
\r
171 // finitialevent : boolean ;
\r
172 fontimer : tnotifyevent ;
\r
173 fenabled : boolean ;
\r
174 finterval : integer ; {miliseconds, default 1000}
\r
176 procedure resettimes;
\r
178 // procedure setinitialevent(newvalue : boolean);
\r
179 procedure setontimer(newvalue:tnotifyevent);
\r
180 procedure setenabled(newvalue : boolean);
\r
181 procedure setinterval(newvalue : integer);
\r
183 //making theese public for now, this code should probablly be restructured later though
\r
184 prevtimer : tltimer ;
\r
185 nexttimer : tltimer ;
\r
186 nextts : ttimeval ;
\r
188 constructor create(aowner:tcomponent);override;
\r
189 destructor destroy;override;
\r
190 // property initialevent : boolean read finitialevent write setinitialevent;
\r
191 property ontimer : tnotifyevent read fontimer write setontimer;
\r
192 property enabled : boolean read fenabled write setenabled;
\r
193 property interval : integer read finterval write setinterval;
\r
197 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
199 tltask=class(tobject)
\r
201 handler : ttaskevent;
\r
206 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
213 procedure processmessages; virtual;abstract;
\r
214 procedure messageloop; virtual;abstract;
\r
215 procedure exitmessageloop; virtual;abstract;
\r
216 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
217 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
218 procedure rmasterclr(fd: integer); virtual;abstract;
\r
219 procedure wmasterset(fd : integer); virtual;abstract;
\r
220 procedure wmasterclr(fd: integer); virtual;abstract;
\r
223 eventcore : teventcore;
\r
225 procedure processmessages;
\r
226 procedure messageloop;
\r
227 procedure exitmessageloop;
\r
230 firstasin : tlasio ;
\r
231 firsttimer : tltimer ;
\r
232 firsttask , lasttask , currenttask : tltask ;
\r
234 numread : integer ;
\r
235 mustrefreshfds : boolean ;
\r
236 { lcoretestcount:integer;}
\r
238 asinreleaseflag:boolean;
\r
241 procedure disconnecttasks(aobj:tobject);
\r
242 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
244 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
246 onaddtask : tonaddtask;
\r
249 procedure sleep(i:integer);
\r
251 procedure prepsigpipe;{$ifndef ver1_0}inline;{$endif}
\r
257 uses {sockets,}lloopback,lsignal;
\r
260 uses windows,winsock;
\r
263 {$include unixstuff.inc}
\r
265 {$include ltimevalstuff.inc}
\r
268 {!!! added sleep call -beware}
\r
269 procedure sleep(i:integer);
\r
276 tv.tv_sec := i div 1000;
\r
277 tv.tv_usec := (i mod 1000) * 1000;
\r
278 select(0,nil,nil,nil,@tv);
\r
282 destructor tlcomponent.destroy;
\r
284 disconnecttasks(self);
\r
291 procedure tlcomponent.release;
\r
296 procedure tlasio.release;
\r
298 asinreleaseflag := true;
\r
302 procedure tlasio.doreceiveloop;
\r
304 if recvq.size = 0 then exit;
\r
305 if assigned(ondataavailable) then ondataavailable(self,0);
\r
306 if not (wsonoreceiveloop in componentoptions) then
\r
307 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
310 function tlasio.receivestr;
\r
312 setlength(result,recvq.size);
\r
313 receive(@result[1],length(result));
\r
316 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
322 if recvq.size < i then i := recvq.size;
\r
324 while (a < i) do begin
\r
325 b := recvq.get(p,i-a);
\r
327 inc(taddrint(buf),b);
\r
332 if wsonoreceiveloop in componentoptions then begin
\r
333 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
337 constructor tlasio.create;
\r
339 inherited create(AOwner);
\r
340 if not assigned(eventcore) then raise exception.create('no event core');
\r
341 sendq := tfifo.create;
\r
342 recvq := tfifo.create;
\r
346 nextasin := firstasin;
\r
348 if assigned(nextasin) then nextasin.prevasin := self;
\r
354 destructor tlasio.destroy;
\r
356 destroying := true;
\r
357 if state <> wsclosed then close;
\r
358 if prevasin <> nil then begin
\r
359 prevasin.nextasin := nextasin;
\r
361 firstasin := nextasin;
\r
363 if nextasin <> nil then begin
\r
364 nextasin.prevasin := prevasin;
\r
371 procedure tlasio.close;
\r
376 procedure tlasio.abort;
\r
381 procedure tlasio.fdcleanup;
\r
383 if fdhandlein <> -1 then begin
\r
384 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
386 if fdhandleout <> -1 then begin
\r
387 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
389 if fdhandlein=fdhandleout then begin
\r
390 if fdhandlein <> -1 then begin
\r
391 myfdclose(fdhandlein);
\r
394 if fdhandlein <> -1 then begin
\r
395 myfdclose(fdhandlein);
\r
397 if fdhandleout <> -1 then begin
\r
398 myfdclose(fdhandleout);
\r
405 procedure tlasio.internalclose(error:word);
\r
407 if (state<>wsclosed) and (state<>wsinvalidstate) then begin
\r
408 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
409 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
410 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
412 if closehandles then begin
\r
414 //anyone remember why this is here? --plugwash
\r
415 fcntl(fdhandlein,F_SETFL,0);
\r
417 myfdclose(fdhandlein);
\r
418 if fdhandleout <> fdhandlein then begin
\r
420 fcntl(fdhandleout,F_SETFL,0);
\r
422 myfdclose(fdhandleout);
\r
424 eventcore.setfdreverse(fdhandlein,nil);
\r
425 eventcore.setfdreverse(fdhandleout,nil);
\r
432 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
434 if assigned(sendq) then sendq.del(maxlongint);
\r
438 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
439 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
440 { application will most likely be shut down ! }
\r
441 procedure tlasio.HandleBackGroundException(E: Exception);
\r
443 CanAbort : Boolean;
\r
446 { First call the error event handler, if any }
\r
447 if Assigned(OnBgException) then begin
\r
449 OnBgException(Self, E, CanAbort);
\r
453 { Then abort the socket }
\r
454 if CanAbort then begin
\r
462 procedure tlasio.sendstr(const str : string);
\r
464 putstringinsendbuffer(str);
\r
468 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
470 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
473 function tlasio.send(data:pointer;len:integer):integer;
\r
475 if state <> wsconnected then begin
\r
479 if len < 0 then len := 0;
\r
481 putdatainsendbuffer(data,len);
\r
486 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
488 sendq.add(data,len);
\r
491 function tlasio.sendflush : integer;
\r
495 // fdstestr : fdset;
\r
496 // fdstestw : fdset;
\r
498 if state <> wsconnected then exit;
\r
500 lensent := sendq.get(data,packetbasesize*2);
\r
501 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
503 if result = -1 then lensent := 0 else lensent := result;
\r
505 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
506 sendq.del(lensent);
\r
508 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
509 // that sends nothing because a previous socket has
\r
510 // slready flushed this socket when the message loop
\r
512 // if sendq.size > 0 then begin
\r
513 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
515 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
517 if result > 0 then begin
\r
518 if assigned(onsenddata) then onsenddata(self,result);
\r
519 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
520 // tltask.create(self.dodatasent,self,0,0);
\r
521 // //begin test code
\r
522 // fd_zero(fdstestr);
\r
523 // fd_zero(fdstestw);
\r
524 // fd_set(fdhandlein,fdstestr);
\r
525 // fd_set(fdhandleout,fdstestw);
\r
526 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
527 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
531 writtenthiscycle := true;
\r
535 procedure tlasio.dupnowatch(invalue:longint);
\r
537 { debugout('invalue='+inttostr(invalue));}
\r
539 if state<> wsclosed then close;
\r
540 fdhandlein := invalue;
\r
541 fdhandleout := invalue;
\r
542 eventcore.setfdreverse(fdhandlein,self);
\r
544 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
546 state := wsconnected;
\r
551 procedure tlasio.dup(invalue:longint);
\r
553 dupnowatch(invalue);
\r
554 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
555 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
559 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
561 sendflushresult : integer;
\r
562 tempbuf:array[0..receivebufsize-1] of byte;
\r
565 if (state=wsconnected) and writetrigger then begin
\r
566 //writeln('write trigger');
\r
568 if (sendq.size >0) then begin
\r
570 sendflushresult := sendflush;
\r
571 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
572 if sendflushresult=0 then begin // linuxerror := 0;
\r
576 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
581 //everything is sent fire off ondatasent event
\r
582 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
583 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
585 if assigned(onfdwrite) then onfdwrite(self,0);
\r
587 writtenthiscycle := false;
\r
588 if (state =wsconnected) and readtrigger then begin
\r
589 if recvq.size=0 then begin
\r
591 if (a <= 0) or (a > sizeof(tempbuf)) then a := sizeof(tempbuf);
\r
592 numread := myfdread(fdhandlein,tempbuf,a);
\r
593 if (numread=0) and (not mustrefreshfds) then begin
\r
594 {if i remember correctly numread=0 is caused by eof
\r
595 if this isn't dealt with then you get a cpu eating infinite loop
\r
596 however if onsessionconencted has called processmessages that could
\r
597 cause us to drop to here with an empty recvq and nothing left to read
\r
598 and we don't want that to cause the socket to close}
\r
601 end else if (numread=-1) then begin
\r
603 //sometimes on windows we get stale messages due to the inherent delays
\r
604 //in the windows message queue
\r
605 if WSAGetLastError = wsaewouldblock then begin
\r
611 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
613 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
616 if recvq.size > 0 then begin
\r
617 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
618 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
619 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
620 tltask.create(self.doreceiveloop,self,0,0);
\r
622 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
623 { debugout('inner loop complete');}
\r
627 procedure tlasio.flush;
\r
629 type fdset = tfdset;
\r
635 fd_set(fdhandleout,fds);
\r
636 while sendq.size>0 do begin
\r
637 select(fdhandleout+1,nil,@fds,nil,nil);
\r
638 if sendflush <= 0 then exit;
\r
642 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
644 if assigned(ondatasent) then ondatasent(self,lparam);
\r
647 procedure tlasio.deletebuffereddata;
\r
649 sendq.del(maxlongint);
\r
652 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
654 tlasio(sender).recvq.del(maxlongint);
\r
658 procedure tltimer.resettimes;
\r
660 gettimeofday(nextts);
\r
661 {if not initialevent then} tv_add(nextts,interval);
\r
665 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
667 if newvalue <> finitialevent then begin
\r
668 finitialevent := newvalue;
\r
669 if assigned(timerwrapperinterface) then begin
\r
670 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
677 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
679 if @newvalue <> @fontimer then begin
\r
680 fontimer := newvalue;
\r
681 if assigned(timerwrapperinterface) then begin
\r
682 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
691 procedure tltimer.setenabled(newvalue : boolean);
\r
693 if newvalue <> fenabled then begin
\r
694 fenabled := newvalue;
\r
695 if assigned(timerwrapperinterface) then begin
\r
696 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
699 raise exception.create('non wrapper timers are not permitted on windows');
\r
707 procedure tltimer.setinterval(newvalue:integer);
\r
709 if newvalue <> finterval then begin
\r
710 finterval := newvalue;
\r
711 if assigned(timerwrapperinterface) then begin
\r
712 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
715 raise exception.create('non wrapper timers are not permitted on windows');
\r
727 constructor tltimer.create;
\r
729 inherited create(AOwner);
\r
730 if assigned(timerwrapperinterface) then begin
\r
731 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
735 nexttimer := firsttimer;
\r
738 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
739 firsttimer := self;
\r
747 destructor tltimer.destroy;
\r
749 if assigned(timerwrapperinterface) then begin
\r
752 if prevtimer <> nil then begin
\r
753 prevtimer.nexttimer := nexttimer;
\r
755 firsttimer := nexttimer;
\r
757 if nexttimer <> nil then begin
\r
758 nexttimer.prevtimer := prevtimer;
\r
765 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
768 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
769 handler := ahandler;
\r
773 {nexttask := firsttask;
\r
774 firsttask := self;}
\r
775 if assigned(lasttask) then begin
\r
776 lasttask.nexttask := self;
\r
781 //ahandler(wparam,lparam);
\r
784 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
787 tltask.create(ahandler,aobj,awparam,alparam);
\r
791 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
794 starthandlesignal(sigpipe);
\r
795 if not assigned(signalloopback) then begin
\r
796 signalloopback := tlloopback.create(nil);
\r
797 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
804 procedure processtasks;//inline;
\r
806 temptask : tltask ;
\r
810 if not assigned(currenttask) then begin
\r
811 currenttask := firsttask;
\r
815 while assigned(currenttask) do begin
\r
817 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
818 if assigned(currenttask) then begin
\r
819 temptask := currenttask;
\r
820 currenttask := currenttask.nexttask;
\r
823 //writeln('processed a task');
\r
831 procedure disconnecttasks(aobj:tobject);
\r
833 currenttasklocal : tltask ;
\r
836 for counter := 0 to 1 do begin
\r
837 if counter = 0 then begin
\r
838 currenttasklocal := firsttask; //main list of tasks
\r
840 currenttasklocal := currenttask; //needed in case called from a task
\r
842 // note i don't bother to sestroy the links here as that will happen when
\r
843 // the list of tasks is processed anyway
\r
844 while assigned(currenttasklocal) do begin
\r
845 if currenttasklocal.obj = aobj then begin
\r
846 currenttasklocal.obj := nil;
\r
847 currenttasklocal.handler := nil;
\r
849 currenttasklocal := currenttasklocal.nexttask;
\r
855 procedure processmessages;
\r
857 eventcore.processmessages;
\r
859 procedure messageloop;
\r
861 eventcore.messageloop;
\r
864 procedure exitmessageloop;
\r
866 eventcore.exitmessageloop;
\r
869 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
871 result := myfdwrite(fdhandleout,data^,len);
\r
872 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
873 eventcore.wmasterset(fdhandleout);
\r
876 procedure tlasio.myfdclose(fd : integer);
\r
880 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
882 result := fdwrite(fd,buf,size);
\r
885 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
887 result := fdread(fd,buf,size);
\r
900 signalloopback := nil;
\r