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
42 sigset= array[0..31] of longint;
\r
45 ESocketException = class(Exception);
\r
46 TBgExceptionEvent = procedure (Sender : TObject;
\r
48 var CanClose : Boolean) of object;
\r
50 // note : tsocketstate is defined in the same way as it is in François PIETTE's twsocket
\r
51 // however tlsocket currently only uses wsClosed wsConnecting wsconnected and wsListening
\r
52 TSocketState = (wsInvalidState,
\r
54 wsConnecting, wsConnected,
\r
55 wsAccepting, wsListening,
\r
58 TWSocketOption = (wsoNoReceiveLoop, wsoTcpNoDelay);
\r
59 TWSocketOptions = set of TWSocketOption;
\r
61 TSocketevent = procedure(Sender: TObject; Error: word) of object;
\r
62 //Tdataavailevent = procedure(data : string);
\r
63 TSendData = procedure (Sender: TObject; BytesSent: Integer) of object;
\r
65 tlcomponent = class(tcomponent)
\r
68 procedure release; virtual;
\r
69 destructor destroy; override;
\r
72 tlasio = class(tlcomponent)
\r
74 state : tsocketstate ;
\r
75 ComponentOptions : TWSocketOptions;
\r
76 fdhandlein : Longint ; {file discriptor}
\r
77 fdhandleout : Longint ; {file discriptor}
\r
79 onsessionclosed : tsocketevent ;
\r
80 ondataAvailable : tsocketevent ;
\r
81 onsessionAvailable : tsocketevent ;
\r
83 onsessionconnected : tsocketevent ;
\r
84 onsenddata : tsenddata ;
\r
85 ondatasent : tsocketevent ;
\r
86 //connected : boolean ;
\r
91 OnBgException : TBgExceptionEvent ;
\r
92 //connectread : boolean ;
\r
94 closehandles : boolean ;
\r
95 writtenthiscycle : boolean ;
\r
96 onfdwrite : procedure (Sender: TObject; Error: word) of object; //added for bewarehttpd
\r
99 function receivestr:string; virtual;
\r
102 procedure internalclose(error:word); virtual;
\r
103 constructor Create(AOwner: TComponent); override;
\r
105 destructor destroy; override;
\r
106 procedure fdcleanup;
\r
107 procedure HandleBackGroundException(E: Exception);
\r
108 procedure handlefdtrigger(readtrigger,writetrigger:boolean); virtual;
\r
109 procedure dup(invalue:longint);
\r
111 function sendflush : integer;
\r
112 procedure sendstr(const str : string);virtual;
\r
113 procedure putstringinsendbuffer(const newstring : string);
\r
114 function send(data:pointer;len:integer):integer;virtual;
\r
115 procedure putdatainsendbuffer(data:pointer;len:integer); virtual;
\r
116 procedure deletebuffereddata;
\r
118 //procedure messageloop;
\r
119 function Receive(Buf:Pointer;BufSize:integer):integer; virtual;
\r
120 procedure flush;virtual;{$ifdef win32} abstract;{$endif}
\r
121 procedure dodatasent(wparam,lparam:longint);
\r
122 procedure doreceiveloop(wparam,lparam:longint);
\r
123 procedure sinkdata(sender:tobject;error:word);
\r
125 procedure release; override; {test -beware}
\r
127 function RealSend(Data : Pointer; Len : Integer) : Integer; //added for bewarehttpd
\r
129 procedure myfdclose(fd : integer); virtual;{$ifdef win32}abstract;{$endif}
\r
130 function myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
131 function myfdread(fd: LongInt;var buf;size: LongInt):LongInt; virtual;{$ifdef win32}abstract;{$endif}
\r
133 procedure dupnowatch(invalue:longint);
\r
135 ttimerwrapperinterface=class(tlcomponent)
\r
137 function createwrappedtimer : tobject;virtual;abstract;
\r
138 // procedure setinitialevent(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
139 procedure setontimer(wrappedtimer : tobject;newvalue:tnotifyevent);virtual;abstract;
\r
140 procedure setenabled(wrappedtimer : tobject;newvalue : boolean);virtual;abstract;
\r
141 procedure setinterval(wrappedtimer : tobject;newvalue : integer);virtual;abstract;
\r
145 timerwrapperinterface : ttimerwrapperinterface;
\r
153 tltimer=class(tlcomponent)
\r
157 wrappedtimer : tobject;
\r
160 // finitialevent : boolean ;
\r
161 fontimer : tnotifyevent ;
\r
162 fenabled : boolean ;
\r
163 finterval : integer ; {miliseconds, default 1000}
\r
165 procedure resettimes;
\r
167 // procedure setinitialevent(newvalue : boolean);
\r
168 procedure setontimer(newvalue:tnotifyevent);
\r
169 procedure setenabled(newvalue : boolean);
\r
170 procedure setinterval(newvalue : integer);
\r
172 //making theese public for now, this code should probablly be restructured later though
\r
173 prevtimer : tltimer ;
\r
174 nexttimer : tltimer ;
\r
175 nextts : ttimeval ;
\r
177 constructor create(aowner:tcomponent);override;
\r
178 destructor destroy;override;
\r
179 // property initialevent : boolean read finitialevent write setinitialevent;
\r
180 property ontimer : tnotifyevent read fontimer write setontimer;
\r
181 property enabled : boolean read fenabled write setenabled;
\r
182 property interval : integer read finterval write setinterval;
\r
186 ttaskevent=procedure(wparam,lparam:longint) of object;
\r
188 tltask=class(tobject)
\r
190 handler : ttaskevent;
\r
195 constructor create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
202 procedure processmessages; virtual;abstract;
\r
203 procedure messageloop; virtual;abstract;
\r
204 procedure exitmessageloop; virtual;abstract;
\r
205 procedure setfdreverse(fd : integer;reverseto : tlasio);virtual;abstract;
\r
206 procedure rmasterset(fd : integer;islistensocket : boolean); virtual;abstract;
\r
207 procedure rmasterclr(fd: integer); virtual;abstract;
\r
208 procedure wmasterset(fd : integer); virtual;abstract;
\r
209 procedure wmasterclr(fd: integer); virtual;abstract;
\r
212 eventcore : teventcore;
\r
214 procedure processmessages;
\r
215 procedure messageloop;
\r
216 procedure exitmessageloop;
\r
219 firstasin : tlasio ;
\r
220 firsttimer : tltimer ;
\r
221 firsttask , lasttask , currenttask : tltask ;
\r
223 numread : integer ;
\r
224 mustrefreshfds : boolean ;
\r
225 { lcoretestcount:integer;}
\r
227 asinreleaseflag:boolean;
\r
230 procedure disconnecttasks(aobj:tobject);
\r
231 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
233 tonaddtask = procedure(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
235 onaddtask : tonaddtask;
\r
238 procedure sleep(i:integer);
\r
240 procedure prepsigpipe;{$ifndef ver1_0}inline;{$endif}
\r
246 uses {sockets,}lloopback,lsignal;
\r
249 uses windows,winsock;
\r
252 {$include unixstuff.inc}
\r
254 {$include ltimevalstuff.inc}
\r
257 {!!! added sleep call -beware}
\r
258 procedure sleep(i:integer);
\r
265 tv.tv_sec := i div 1000;
\r
266 tv.tv_usec := (i mod 1000) * 1000;
\r
267 select(0,nil,nil,nil,@tv);
\r
271 destructor tlcomponent.destroy;
\r
273 disconnecttasks(self);
\r
280 procedure tlcomponent.release;
\r
285 procedure tlasio.release;
\r
287 asinreleaseflag := true;
\r
291 procedure tlasio.doreceiveloop;
\r
293 if recvq.size = 0 then exit;
\r
294 if assigned(ondataavailable) then ondataavailable(self,0);
\r
295 if not (wsonoreceiveloop in componentoptions) then
\r
296 if recvq.size > 0 then tltask.create(self.doreceiveloop,self,0,0);
\r
299 function tlasio.receivestr;
\r
301 setlength(result,recvq.size);
\r
302 receive(@result[1],length(result));
\r
305 function tlasio.receive(Buf:Pointer;BufSize:integer):integer;
\r
311 if recvq.size < i then i := recvq.size;
\r
313 while (a < i) do begin
\r
314 b := recvq.get(p,i-a);
\r
316 inc(taddrint(buf),b);
\r
321 if wsonoreceiveloop in componentoptions then begin
\r
322 if recvq.size = 0 then eventcore.rmasterset(fdhandlein,false);
\r
326 constructor tlasio.create;
\r
328 inherited create(AOwner);
\r
329 sendq := tfifo.create;
\r
330 recvq := tfifo.create;
\r
334 nextasin := firstasin;
\r
336 if assigned(nextasin) then nextasin.prevasin := self;
\r
342 destructor tlasio.destroy;
\r
344 destroying := true;
\r
345 if state <> wsclosed then close;
\r
346 if prevasin <> nil then begin
\r
347 prevasin.nextasin := nextasin;
\r
349 firstasin := nextasin;
\r
351 if nextasin <> nil then begin
\r
352 nextasin.prevasin := prevasin;
\r
359 procedure tlasio.close;
\r
364 procedure tlasio.abort;
\r
369 procedure tlasio.fdcleanup;
\r
371 if fdhandlein <> -1 then begin
\r
372 eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster)
\r
374 if fdhandleout <> -1 then begin
\r
375 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster)
\r
377 if fdhandlein=fdhandleout then begin
\r
378 if fdhandlein <> -1 then begin
\r
379 myfdclose(fdhandlein);
\r
382 if fdhandlein <> -1 then begin
\r
383 myfdclose(fdhandlein);
\r
385 if fdhandleout <> -1 then begin
\r
386 myfdclose(fdhandleout);
\r
393 procedure tlasio.internalclose(error:word);
\r
395 if state<>wsclosed then begin
\r
396 if (fdhandlein < 0) or (fdhandleout < 0) then raise exception.create('internalclose called with invalid fd handles');
\r
397 eventcore.rmasterclr(fdhandlein);//fd_clr(fdhandlein,fdsrmaster);
\r
398 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
400 if closehandles then begin
\r
402 //anyone remember why this is here? --plugwash
\r
403 fcntl(fdhandlein,F_SETFL,0);
\r
405 myfdclose(fdhandlein);
\r
406 if fdhandleout <> fdhandlein then begin
\r
408 fcntl(fdhandleout,F_SETFL,0);
\r
410 myfdclose(fdhandleout);
\r
412 eventcore.setfdreverse(fdhandlein,nil);
\r
413 eventcore.setfdreverse(fdhandleout,nil);
\r
420 if assigned(onsessionclosed) then if not destroying then onsessionclosed(self,error);
\r
422 sendq.del(maxlongint);
\r
426 {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
\r
427 { All exceptions *MUST* be handled. If an exception is not handled, the }
\r
428 { application will most likely be shut down ! }
\r
429 procedure tlasio.HandleBackGroundException(E: Exception);
\r
431 CanAbort : Boolean;
\r
434 { First call the error event handler, if any }
\r
435 if Assigned(OnBgException) then begin
\r
437 OnBgException(Self, E, CanAbort);
\r
441 { Then abort the socket }
\r
442 if CanAbort then begin
\r
450 procedure tlasio.sendstr(const str : string);
\r
452 putstringinsendbuffer(str);
\r
456 procedure tlasio.putstringinsendbuffer(const newstring : string);
\r
458 if newstring <> '' then putdatainsendbuffer(@newstring[1],length(newstring));
\r
461 function tlasio.send(data:pointer;len:integer):integer;
\r
463 if state <> wsconnected then begin
\r
467 if len < 0 then len := 0;
\r
469 putdatainsendbuffer(data,len);
\r
474 procedure tlasio.putdatainsendbuffer(data:pointer;len:integer);
\r
476 sendq.add(data,len);
\r
479 function tlasio.sendflush : integer;
\r
483 // fdstestr : fdset;
\r
484 // fdstestw : fdset;
\r
486 if state <> wsconnected then exit;
\r
488 lensent := sendq.get(data,2920);
\r
489 if assigned(data) then result := myfdwrite(fdhandleout,data^,lensent) else result := 0;
\r
491 if result = -1 then lensent := 0 else lensent := result;
\r
493 //sendq := copy(sendq,lensent+1,length(sendq)-lensent);
\r
494 sendq.del(lensent);
\r
496 //fd_clr(fdhandleout,fdsw); // this prevents the socket being closed by a write
\r
497 // that sends nothing because a previous socket has
\r
498 // slready flushed this socket when the message loop
\r
500 // if sendq.size > 0 then begin
\r
501 eventcore.wmasterset(fdhandleout);//fd_set(fdhandleout,fdswmaster);
\r
503 // wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
505 if result > 0 then begin
\r
506 if assigned(onsenddata) then onsenddata(self,result);
\r
507 // if sendq.size=0 then if assigned(ondatasent) then begin
\r
508 // tltask.create(self.dodatasent,self,0,0);
\r
509 // //begin test code
\r
510 // fd_zero(fdstestr);
\r
511 // fd_zero(fdstestw);
\r
512 // fd_set(fdhandlein,fdstestr);
\r
513 // fd_set(fdhandleout,fdstestw);
\r
514 // select(maxs,@fdstestr,@fdstestw,nil,0);
\r
515 // writeln(fd_isset(fdhandlein,fdstestr),' ',fd_isset(fdhandleout,fdstestw));
\r
519 writtenthiscycle := true;
\r
523 procedure tlasio.dupnowatch(invalue:longint);
\r
525 { debugout('invalue='+inttostr(invalue));}
\r
527 if state<> wsclosed then close;
\r
528 fdhandlein := invalue;
\r
529 fdhandleout := invalue;
\r
530 eventcore.setfdreverse(fdhandlein,self);
\r
532 fcntl(fdhandlein,F_SETFL,OPEN_NONBLOCK);
\r
534 state := wsconnected;
\r
539 procedure tlasio.dup(invalue:longint);
\r
541 dupnowatch(invalue);
\r
542 eventcore.rmasterset(fdhandlein,false);//fd_set(fdhandlein,fdsrmaster);
\r
543 eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
547 procedure tlasio.handlefdtrigger(readtrigger,writetrigger:boolean);
\r
549 sendflushresult : integer;
\r
550 tempbuf:array[0..receivebufsize-1] of byte;
\r
552 if (state=wsconnected) and writetrigger then begin
\r
553 //writeln('write trigger');
\r
555 if (sendq.size >0) then begin
\r
557 sendflushresult := sendflush;
\r
558 if (sendflushresult <= 0) and (not writtenthiscycle) then begin
\r
559 if sendflushresult=0 then begin // linuxerror := 0;
\r
563 internalclose({$ifdef win32}getlasterror{$else}linuxerror{$endif});
\r
568 //everything is sent fire off ondatasent event
\r
569 if fdhandleout >= 0 then eventcore.wmasterclr(fdhandleout);//fd_clr(fdhandleout,fdswmaster);
\r
570 if assigned(ondatasent) then tltask.create(self.dodatasent,self,0,0);
\r
572 if assigned(onfdwrite) then onfdwrite(self,0);
\r
574 writtenthiscycle := false;
\r
575 if (state =wsconnected) and readtrigger then begin
\r
576 if recvq.size=0 then begin
\r
577 numread := myfdread(fdhandlein,tempbuf,sizeof(tempbuf));
\r
578 if (numread=0) and (not mustrefreshfds) then begin
\r
579 {if i remember correctly numread=0 is caused by eof
\r
580 if this isn't dealt with then you get a cpu eating infinite loop
\r
581 however if onsessionconencted has called processmessages that could
\r
582 cause us to drop to here with an empty recvq and nothing left to read
\r
583 and we don't want that to cause the socket to close}
\r
586 end else if (numread=-1) then begin
\r
588 //sometimes on windows we get stale messages due to the inherent delays
\r
589 //in the windows message queue
\r
590 if WSAGetLastError = wsaewouldblock then begin
\r
596 internalclose({$ifdef win32}wsagetlasterror{$else}linuxerror{$endif});
\r
598 end else if numread > 0 then recvq.add(@tempbuf,numread);
\r
601 if recvq.size > 0 then begin
\r
602 if wsonoreceiveloop in componentoptions then eventcore.rmasterclr(fdhandlein); //fd_clr(fdhandlein,fdsrmaster);
\r
603 if assigned(ondataavailable) then ondataAvailable(self,0);
\r
604 if not (wsonoreceiveloop in componentoptions) then if recvq.size > 0 then
\r
605 tltask.create(self.doreceiveloop,self,0,0);
\r
607 //until (numread = 0) or (currentsocket.state<>wsconnected);
\r
608 { debugout('inner loop complete');}
\r
613 procedure tlasio.flush;
\r
618 fd_set(fdhandleout,fds);
\r
619 while sendq.size>0 do begin
\r
620 select(fdhandleout+1,nil,@fds,nil,nil);
\r
621 if sendflush <= 0 then exit;
\r
626 procedure tlasio.dodatasent(wparam,lparam:longint);
\r
628 if assigned(ondatasent) then ondatasent(self,lparam);
\r
631 procedure tlasio.deletebuffereddata;
\r
633 sendq.del(maxlongint);
\r
636 procedure tlasio.sinkdata(sender:tobject;error:word);
\r
638 tlasio(sender).recvq.del(maxlongint);
\r
642 procedure tltimer.resettimes;
\r
644 gettimeofday(nextts);
\r
645 {if not initialevent then} tv_add(nextts,interval);
\r
649 {procedure tltimer.setinitialevent(newvalue : boolean);
\r
651 if newvalue <> finitialevent then begin
\r
652 finitialevent := newvalue;
\r
653 if assigned(timerwrapperinterface) then begin
\r
654 timerwrapperinterface.setinitialevent(wrappedtimer,newvalue);
\r
661 procedure tltimer.setontimer(newvalue:tnotifyevent);
\r
663 if @newvalue <> @fontimer then begin
\r
664 fontimer := newvalue;
\r
665 if assigned(timerwrapperinterface) then begin
\r
666 timerwrapperinterface.setontimer(wrappedtimer,newvalue);
\r
675 procedure tltimer.setenabled(newvalue : boolean);
\r
677 if newvalue <> fenabled then begin
\r
678 fenabled := newvalue;
\r
679 if assigned(timerwrapperinterface) then begin
\r
680 timerwrapperinterface.setenabled(wrappedtimer,newvalue);
\r
683 raise exception.create('non wrapper timers are not permitted on windows');
\r
691 procedure tltimer.setinterval(newvalue:integer);
\r
693 if newvalue <> finterval then begin
\r
694 finterval := newvalue;
\r
695 if assigned(timerwrapperinterface) then begin
\r
696 timerwrapperinterface.setinterval(wrappedtimer,newvalue);
\r
699 raise exception.create('non wrapper timers are not permitted on windows');
\r
711 constructor tltimer.create;
\r
713 inherited create(AOwner);
\r
714 if assigned(timerwrapperinterface) then begin
\r
715 wrappedtimer := timerwrapperinterface.createwrappedtimer;
\r
719 nexttimer := firsttimer;
\r
722 if assigned(nexttimer) then nexttimer.prevtimer := self;
\r
723 firsttimer := self;
\r
731 destructor tltimer.destroy;
\r
733 if assigned(timerwrapperinterface) then begin
\r
736 if prevtimer <> nil then begin
\r
737 prevtimer.nexttimer := nexttimer;
\r
739 firsttimer := nexttimer;
\r
741 if nexttimer <> nil then begin
\r
742 nexttimer.prevtimer := prevtimer;
\r
749 constructor tltask.create(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
752 if assigned(onaddtask) then onaddtask(ahandler,aobj,awparam,alparam);
\r
753 handler := ahandler;
\r
757 {nexttask := firsttask;
\r
758 firsttask := self;}
\r
759 if assigned(lasttask) then begin
\r
760 lasttask.nexttask := self;
\r
765 //ahandler(wparam,lparam);
\r
768 procedure addtask(ahandler:ttaskevent;aobj:tobject;awparam,alparam:longint);
\r
771 tltask.create(ahandler,aobj,awparam,alparam);
\r
775 procedure prepsigpipe;{$ifndef ver1_0}inline;
\r
778 starthandlesignal(sigpipe);
\r
779 if not assigned(signalloopback) then begin
\r
780 signalloopback := tlloopback.create(nil);
\r
781 signalloopback.ondataAvailable := signalloopback.sinkdata;
\r
788 procedure processtasks;//inline;
\r
790 temptask : tltask ;
\r
794 if not assigned(currenttask) then begin
\r
795 currenttask := firsttask;
\r
799 while assigned(currenttask) do begin
\r
801 if assigned(currenttask.handler) then currenttask.handler(currenttask.wparam,currenttask.lparam);
\r
802 if assigned(currenttask) then begin
\r
803 temptask := currenttask;
\r
804 currenttask := currenttask.nexttask;
\r
807 //writeln('processed a task');
\r
815 procedure disconnecttasks(aobj:tobject);
\r
817 currenttasklocal : tltask ;
\r
820 for counter := 0 to 1 do begin
\r
821 if counter = 0 then begin
\r
822 currenttasklocal := firsttask; //main list of tasks
\r
824 currenttasklocal := currenttask; //needed in case called from a task
\r
826 // note i don't bother to sestroy the links here as that will happen when
\r
827 // the list of tasks is processed anyway
\r
828 while assigned(currenttasklocal) do begin
\r
829 if currenttasklocal.obj = aobj then begin
\r
830 currenttasklocal.obj := nil;
\r
831 currenttasklocal.handler := nil;
\r
833 currenttasklocal := currenttasklocal.nexttask;
\r
839 procedure processmessages;
\r
841 eventcore.processmessages;
\r
843 procedure messageloop;
\r
845 eventcore.messageloop;
\r
848 procedure exitmessageloop;
\r
850 eventcore.exitmessageloop;
\r
853 function tlasio.RealSend(Data : Pointer; Len : Integer) : Integer;
\r
855 result := myfdwrite(fdhandleout,data^,len);
\r
856 if (result > 0) and assigned(onsenddata) then onsenddata(self,result);
\r
857 eventcore.wmasterset(fdhandleout);
\r
860 procedure tlasio.myfdclose(fd : integer);
\r
864 function tlasio.myfdwrite(fd: LongInt;const buf;size: LongInt):LongInt;
\r
866 result := fdwrite(fd,buf,size);
\r
869 function tlasio.myfdread(fd: LongInt;var buf;size: LongInt):LongInt;
\r
871 result := fdread(fd,buf,size);
\r
884 signalloopback := nil;
\r