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 packetbasesize = 1460;
\r
39 receivebufsize=packetbasesize*8;
\r
42 absoloutemaxs:integer=0;
\r
46 sigset= array[0..31] of longint;
\r
49 ESocketException = class(Exception);
\r
50 TBgExceptionEvent = procedure (Sender : TObject;
\r
52 var CanClose : Boolean) of object;
\r
54 // note : tsocketstate is defined in the same way as it is in François PIETTE's twsocket
\r
55 // however tlsocket currently only uses wsClosed wsConnecting wsconnected and wsListening
\r
56 TSocketState = (wsInvalidState,
\r
58 wsConnecting, wsConnected,
\r
59 wsAccepting, wsListening,
\r
62 TWSocketOption = (wsoNoReceiveLoop, wsoTcpNoDelay);
\r
63 TWSocketOptions = set of TWSocketOption;
\r
65 TSocketevent = procedure(Sender: TObject; Error: word) of object;
\r
66 //Tdataavailevent = procedure(data : string);
\r
67 TSendData = procedure (Sender: TObject; BytesSent: Integer) of object;
\r
69 tlcomponent = class(tcomponent)
\r
72 procedure release; virtual;
\r
73 destructor destroy; override;
\r
76 tlasio = class(tlcomponent)
\r
78 state : tsocketstate ;
\r
79 ComponentOptions : TWSocketOptions;
\r
80 fdhandlein : Longint ; {file discriptor}
\r
81 fdhandleout : Longint ; {file discriptor}
\r
83 onsessionclosed : tsocketevent ;
\r
84 ondataAvailable : tsocketevent ;
\r
85 onsessionAvailable : tsocketevent ;
\r
87 onsessionconnected : tsocketevent ;
\r
88 onsenddata : tsenddata ;
\r
89 ondatasent : tsocketevent ;
\r
90 //connected : boolean ;
\r
95 OnBgException : TBgExceptionEvent ;
\r
96 //connectread : boolean ;
\r
98 closehandles : boolean ;
\r
99 writtenthiscycle : boolean ;
\r
100 onfdwrite : procedure (Sender: TObject; Error: word) of object; //added for bewarehttpd
\r
102 destroying:boolean;
\r
103 recvbufsize:integer;
\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;
\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;{$ifndef ver1_0}inline;{$endif}
\r
251 uses {sockets,}lloopback,lsignal;
\r
254 uses windows,winsock;
\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 if not assigned(eventcore) then raise exception.create('no event core');
\r
335 sendq := tfifo.create;
\r
336 recvq := tfifo.create;
\r
340 nextasin := firstasin;
\r
342 if assigned(nextasin) then nextasin.prevasin := self;
\r
348 destructor tlasio.destroy;
\r
350 destroying := true;
\r
351 if state <> wsclosed then close;
\r
352 if prevasin <> nil then begin
\r
353 prevasin.nextasin := nextasin;
\r
355 firstasin := nextasin;
\r
357 if nextasin <> nil then begin
\r
358 nextasin.prevasin := prevasin;
\r
365 procedure tlasio.close;
\r
370 procedure tlasio.abort;
\r
375 procedure tlasio.fdcleanup;
\r
377 if fdhandlein <> -1 then begin
\r
378 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
380 if fdhandleout <> -1 then begin
\r
381 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
383 if fdhandlein=fdhandleout then begin
\r
384 if fdhandlein <> -1 then begin
\r
385 myfdclose(fdhandlein);
\r
388 if fdhandlein <> -1 then begin
\r
389 myfdclose(fdhandlein);
\r
391 if fdhandleout <> -1 then begin
\r
392 myfdclose(fdhandleout);
\r
399 procedure tlasio.internalclose(error:word);
\r
401 if (state<>wsclosed) and (state<>wsinvalidstate) then begin
\r
402 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
403 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
404 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
406 if closehandles then begin
\r
408 //anyone remember why this is here? --plugwash
\r
409 fcntl(fdhandlein,F_SETFL,0);
\r
411 myfdclose(fdhandlein);
\r
412 if fdhandleout <> fdhandlein then begin
\r
414 fcntl(fdhandleout,F_SETFL,0);
\r
416 myfdclose(fdhandleout);
\r
418 eventcore.setfdreverse(fdhandlein,nil);
\r
419 eventcore.setfdreverse(fdhandleout,nil);
\r
426 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
428 if assigned(sendq) then sendq.del(maxlongint);
\r
432 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
433 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
434 { application will most likely be shut down ! }
\r
435 procedure tlasio.HandleBackGroundException(E: Exception);
\r
437 CanAbort : Boolean;
\r
440 { First call the error event handler, if any }
\r
441 if Assigned(OnBgException) then begin
\r
443 OnBgException(Self, E, CanAbort);
\r
447 { Then abort the socket }
\r
448 if CanAbort then begin
\r
456 procedure tlasio.sendstr(const str : string);
\r
458 putstringinsendbuffer(str);
\r
462 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
464 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
467 function tlasio.send(data:pointer;len:integer):integer;
\r
469 if state <> wsconnected then begin
\r
473 if len < 0 then len := 0;
\r
475 putdatainsendbuffer(data,len);
\r
480 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
482 sendq.add(data,len);
\r
485 function tlasio.sendflush : integer;
\r
489 // fdstestr : fdset;
\r
490 // fdstestw : fdset;
\r
492 if state <> wsconnected then exit;
\r
494 lensent := sendq.get(data,packetbasesize*2);
\r
495 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
497 if result = -1 then lensent := 0 else lensent := result;
\r
499 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
500 sendq.del(lensent);
\r
502 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
503 // that sends nothing because a previous socket has
\r
504 // slready flushed this socket when the message loop
\r
506 // if sendq.size > 0 then begin
\r
507 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
509 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
511 if result > 0 then begin
\r
512 if assigned(onsenddata) then onsenddata(self,result);
\r
513 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
514 // tltask.create(self.dodatasent,self,0,0);
\r
515 // //begin test code
\r
516 // fd_zero(fdstestr);
\r
517 // fd_zero(fdstestw);
\r
518 // fd_set(fdhandlein,fdstestr);
\r
519 // fd_set(fdhandleout,fdstestw);
\r
520 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
521 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
525 writtenthiscycle := true;
\r
529 procedure tlasio.dupnowatch(invalue:longint);
\r
531 { debugout('invalue='+inttostr(invalue));}
\r
533 if state<> wsclosed then close;
\r
534 fdhandlein := invalue;
\r
535 fdhandleout := invalue;
\r
536 eventcore.setfdreverse(fdhandlein,self);
\r
538 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
540 state := wsconnected;
\r
545 procedure tlasio.dup(invalue:longint);
\r
547 dupnowatch(invalue);
\r
548 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
549 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
553 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
555 sendflushresult : integer;
\r
556 tempbuf:array[0..receivebufsize-1] of byte;
\r
559 if (state=wsconnected) and writetrigger then begin
\r
560 //writeln('write trigger');
\r
562 if (sendq.size >0) then begin
\r
564 sendflushresult := sendflush;
\r
565 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
566 if sendflushresult=0 then begin // linuxerror := 0;
\r
570 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
575 //everything is sent fire off ondatasent event
\r
576 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
577 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
579 if assigned(onfdwrite) then onfdwrite(self,0);
\r
581 writtenthiscycle := false;
\r
582 if (state =wsconnected) and readtrigger then begin
\r
583 if recvq.size=0 then begin
\r
585 if (a <= 0) or (a > sizeof(tempbuf)) then a := sizeof(tempbuf);
\r
586 numread := myfdread(fdhandlein,tempbuf,a);
\r
587 if (numread=0) and (not mustrefreshfds) then begin
\r
588 {if i remember correctly numread=0 is caused by eof
\r
589 if this isn't dealt with then you get a cpu eating infinite loop
\r
590 however if onsessionconencted has called processmessages that could
\r
591 cause us to drop to here with an empty recvq and nothing left to read
\r
592 and we don't want that to cause the socket to close}
\r
595 end else if (numread=-1) then begin
\r
597 //sometimes on windows we get stale messages due to the inherent delays
\r
598 //in the windows message queue
\r
599 if WSAGetLastError = wsaewouldblock then begin
\r
605 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
607 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
610 if recvq.size > 0 then begin
\r
611 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
612 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
613 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
614 tltask.create(self.doreceiveloop,self,0,0);
\r
616 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
617 { debugout('inner loop complete');}
\r
621 procedure tlasio.flush;
\r
623 type fdset = tfdset;
\r
629 fd_set(fdhandleout,fds);
\r
630 while sendq.size>0 do begin
\r
631 select(fdhandleout+1,nil,@fds,nil,nil);
\r
632 if sendflush <= 0 then exit;
\r
636 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
638 if assigned(ondatasent) then ondatasent(self,lparam);
\r
641 procedure tlasio.deletebuffereddata;
\r
643 sendq.del(maxlongint);
\r
646 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
648 tlasio(sender).recvq.del(maxlongint);
\r
652 procedure tltimer.resettimes;
\r
654 gettimeofday(nextts);
\r
655 {if not initialevent then} tv_add(nextts,interval);
\r
659 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
661 if newvalue <> finitialevent then begin
\r
662 finitialevent := newvalue;
\r
663 if assigned(timerwrapperinterface) then begin
\r
664 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
671 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
673 if @newvalue <> @fontimer then begin
\r
674 fontimer := newvalue;
\r
675 if assigned(timerwrapperinterface) then begin
\r
676 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
685 procedure tltimer.setenabled(newvalue : boolean);
\r
687 if newvalue <> fenabled then begin
\r
688 fenabled := newvalue;
\r
689 if assigned(timerwrapperinterface) then begin
\r
690 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
693 raise exception.create('non wrapper timers are not permitted on windows');
\r
701 procedure tltimer.setinterval(newvalue:integer);
\r
703 if newvalue <> finterval then begin
\r
704 finterval := newvalue;
\r
705 if assigned(timerwrapperinterface) then begin
\r
706 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
709 raise exception.create('non wrapper timers are not permitted on windows');
\r
721 constructor tltimer.create;
\r
723 inherited create(AOwner);
\r
724 if assigned(timerwrapperinterface) then begin
\r
725 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
729 nexttimer := firsttimer;
\r
732 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
733 firsttimer := self;
\r
741 destructor tltimer.destroy;
\r
743 if assigned(timerwrapperinterface) then begin
\r
746 if prevtimer <> nil then begin
\r
747 prevtimer.nexttimer := nexttimer;
\r
749 firsttimer := nexttimer;
\r
751 if nexttimer <> nil then begin
\r
752 nexttimer.prevtimer := prevtimer;
\r
759 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
762 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
763 handler := ahandler;
\r
767 {nexttask := firsttask;
\r
768 firsttask := self;}
\r
769 if assigned(lasttask) then begin
\r
770 lasttask.nexttask := self;
\r
775 //ahandler(wparam,lparam);
\r
778 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
781 tltask.create(ahandler,aobj,awparam,alparam);
\r
785 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
788 starthandlesignal(sigpipe);
\r
789 if not assigned(signalloopback) then begin
\r
790 signalloopback := tlloopback.create(nil);
\r
791 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
798 procedure processtasks;//inline;
\r
800 temptask : tltask ;
\r
804 if not assigned(currenttask) then begin
\r
805 currenttask := firsttask;
\r
809 while assigned(currenttask) do begin
\r
811 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
812 if assigned(currenttask) then begin
\r
813 temptask := currenttask;
\r
814 currenttask := currenttask.nexttask;
\r
817 //writeln('processed a task');
\r
825 procedure disconnecttasks(aobj:tobject);
\r
827 currenttasklocal : tltask ;
\r
830 for counter := 0 to 1 do begin
\r
831 if counter = 0 then begin
\r
832 currenttasklocal := firsttask; //main list of tasks
\r
834 currenttasklocal := currenttask; //needed in case called from a task
\r
836 // note i don't bother to sestroy the links here as that will happen when
\r
837 // the list of tasks is processed anyway
\r
838 while assigned(currenttasklocal) do begin
\r
839 if currenttasklocal.obj = aobj then begin
\r
840 currenttasklocal.obj := nil;
\r
841 currenttasklocal.handler := nil;
\r
843 currenttasklocal := currenttasklocal.nexttask;
\r
849 procedure processmessages;
\r
851 eventcore.processmessages;
\r
853 procedure messageloop;
\r
855 eventcore.messageloop;
\r
858 procedure exitmessageloop;
\r
860 eventcore.exitmessageloop;
\r
863 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
865 result := myfdwrite(fdhandleout,data^,len);
\r
866 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
867 eventcore.wmasterset(fdhandleout);
\r
870 procedure tlasio.myfdclose(fd : integer);
\r
874 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
876 result := fdwrite(fd,buf,size);
\r
879 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
881 result := fdread(fd,buf,size);
\r
894 signalloopback := nil;
\r