Index

Nog een paar programmavoorbeelden

Teller op webpagina
Veronderstel dat je een grote ISP bedrijf bent (geworden) met talrijke gebruikers die allemaal een website hebben, en allemaal willen ze een teller. Maar zoals alle grote internet service providers heb je maar een beperkte technische staff (om precies te zijn: jij alleen) en wens je een systeem dat zo eenvoudig mogelijk is. De vraag is dus maak een teller aan zonder enige argumenten.

Op de webpagina moet de gebruiker enkel deze eevoudige kode opnemen op de plaats waar de teller moet komen:


<script src=http://myserver.com/cgi-bin/counter></script>
Een enkele programma zorgt voor alle tellers op alle pagina's van al je gebruikers:


DEFWORD a-z
FUNCTION PBMAIN()
    $aa = "document.write('": $zz = "');"         ' De browser verwacht javascript, geen HTML,
    homepage$ = ENVIRON$("HTTP_REFERER")          ' We moeten de output "inpakken".
    ipaddr$ = ENVIRON$("REMOTE_ADDR")

    filename$ = $userpath + PARSE$(homepage$, "/", -2) + "_" + PARSE$(homepage$, "/", -1) + ".txt"
                       ' We maken een filenaam aan gebaseerd op de gebruiker (user directory)
                        ' en de pagina zelf (referer)
                        ' We zouden ook kunnen testen dat de domein name wel van onze server afkomstig is zodat
                        ' niet-klanten onze tellers niet gebruiken.
    er = ERRCLEAR
    OPEN filename$ FOR INPUT AS #1
    IF ERRCLEAR THEN
        count = 0
    ELSE
        LINE INPUT #1, count$: count = VAL(count$)
        LINE INPUT #1, oldipaddr$
        CLOSE #1
    END IF
    IF ipaddr$ <> oldipaddr THEN               ' In geval de gebruiker meer dan één teller op zijn pagina zou plaatsen
        INCR count                            ' of de pagina meermalen na elkaar zou inlezen om de tellen kunstmatig op te voeren
        OPEN filename$ FOR OUTPUT AS #1
        PRINT #1, count
        PRINT #1, ipaddr$
        CLOSE #1
    END IF
    OPEN ENVIRON$("CGI_STDOUT") FOR OUTPUT AS #1
    PRINT #1, $aa  count  $zz
END FUNCTION

Een grafische teller is even gemakkelijk: maak 10 gifs aan, allemaal met dezelfde hoogte en met een transparante achtergrond en noem ze 0.gif, 1.gif, enz. Dit is de kode:


    ....
    OPEN ENVIRON$("CGI_STDOUT") FOR OUTPUT AS #1
    count$ = FORMAT$(count, "000000")
    FOR i = 1 TO 6
        PRINT #1, $aa  "<img src=http://myserver.com/counterpix/"  MID$(count$, i, 1)  ".gif align=left>"  $zz
    NEXT i
END FUNCTION

Het kan nog beter: je kan de gebruiker laten kiezen tussen de normale layout (tekst) of een grafische output door een query string toe te voegen:
<script src=http://myserver.com/cgi-bin/counter></script>   default: normale tekst
<script src=http://myserver.com/cgi-bin/counter?A></script> set A
en zo verder.


Het schrijven van gegevens met variabele lengte in records met vaste lengte
We keren even terug tot ons bestelsysteem dat we reeds als voorbeeld hebben gebruikt. We voorzien een <textarea> waar de klanten specifieke gegevens kunnen invullen (bijvoorbeeld: "sleutel vragen bij de geburen"). Een bericht kan tot 1000 tekens lang zijn, maar de meeste klanten zullen van deze functionaliteit geen gebruik maken. Hoe kunnen we ons bestandssysteem aanmaken zodat een klant een facultatief bericht kan opnemen? Reserveren we 1000 bytes voor alle bestellingen, dan eindigen we met een enorm bestand, dat het heel systeen zal vertragen (bijvoorbeeld bij het doorzoeken van het bestand).
Er is echter een beter systeem. Dit is ons orderbestand:


TYPE orderlayout
    ok AS BYTE                  ' Om een order te wissen maken we order.ok = 0
    customer AS DWORD           ' in plaats van het record te wissen.
    date AS STRING * 6          ' Veel gemakkelijker te recupereren...
    trackingnumber AS DWORD
    ...                         ' enz (alle velden van het order komen hier) 
    message AS DWORD
END TYPE 

    DIM order AS orderlayout

Hoe kunnen we een volledig bericht in 4 bytes schrijven? Heel eenvoudig:


    ...
    IF LEN$(msg$) THEN
        order.message = writerecord($path + "order.mes", msg$)
    ELSE
        order.message = 0
    END IF
    OPEN $path + "order.dat" FOR RANDOM ACCESS WRITE AS #1 LEN = LEN(order)
    PUT #1, iorder, order
    CLOSE #1
    ...

En zo lezen we een eventueel bericht:


    ...
    OPEN $path + "order.dat" FOR RANDOM ACCESS READ AS #1 LEN = LEN(order)
    GET #1, iorder, order
    CLOSE #1
    IF order.message THEN
        msg$ = readrecord$($path + "order.mes", order.message)
    ELSE
        msg$ = ""
    END IF
    ...

Bericht wijzigen? Oude wissen en nieuwe aanmaken:


    ...
    OPEN $path + "order.dat" FOR RANDOM AS #1 LEN = LEN(order)
    GET #1, iorder, order
    IF order.message THEN
        killrecord($path + "order.mes", order.message)
    END IF
    order.message = writerecord($path + "order.mes", msg$)
    PUT #1, iorder, order
    ...

Alle handelingen gebeuren in writerecord, readrecord en killrecord. Dit is de source:


FUNCTION writerecord(file$, text$) AS DWORD
    STATIC i AS WORD
    i = FREEFILE
    OPEN file$ FOR APPEND AS #i
    writerecord = LOF(i) + 1
    PRINT #i, text$
    CLOSE #i
END FUNCTION

FUNCTION readrecord$(file$, position)
    STATIC i AS WORD, a AS STRING
    i = FREEFILE
    OPEN file$ FOR INPUT AS #i
    SEEK #i, position
    LINE INPUT #i, a$
    readrecord$ = a$
    CLOSE #i
END FUNCTION

SUB killrecord(file$, position)
    STATIC i AS WORD, a AS STRING
    i = FREEFILE
    OPEN file$ FOR INPUT AS #i
    SEEK #i, position
    LINE INPUT #i, a$
    CLOSE #i
    OPEN file$ FOR BINARY AS #i
    SEEK #i, position
    PUT$ #i, SPACE$(LEN(a$))
    CLOSE #i
END SUB

Een klein minpunt is dat je geen $CRLF in je bericht kan hebben, aangezien deze combinatie als recordseparator wordt gebruikt. Indien je verder toch met HTML zal werken, kan je $CRLF omzetten naar <BR> voor het schrijven.

Het is veel efficienter om records achteraan het bestand bij te voegen dan de gewijzigde records tussen te voegen en de inhoud van de rest van het bestand te verschuiven. Na een tijdje bekom je wel een bestand met ongebruikte delen. Dit is echter geen groot probleem, omdat het bestand met direct access werkt (directe toegang tot iedere record). Maar het is altijd mogelijk de gaten op te vullen:


    OPEN $path + "order.mes" FOR INPUT ACCESS READ SHARED AS #1
    i = 0
    WHILE NOT EOF(1)
        LINE INPUT #1, a$
        IF TRIM$(a$) = "" THEN
            i = i + LEN(a$) + 2
        END IF
    WEND
    j = LOF(1)
    CLOSE #1
    percent = i * 100 / j
    IF percent > 40 THEN
        OPEN $path + "order.dat" FOR RANDOM ACCESS READ WRITE AS #1 LEN = LEN(order)
        FOR iorder = 1 TO norder
            GET #1, iorder, order
            IF order.ok and order.message THEN
                a$ = TRIM$(readrecord$($path + "order.mes", order.message))
                IF LEN(a$) THEN
                    order.message = writerecord($path + "order.tmp", a$)
                ELSE
                    order.message = 0
                PUT #1, iorder, order
            END IF
        NEXT iorder
        CLOSE #1
        KILL $path + "order.mes"
        NAME $path + "order.tmp" AS $path + "order.mes"
    END IF

Deze verwerking doe je best als de server minder belast is. Voor een webtoepassing is het aangeraden de database te locken gedurende de compressie. En natuurlijk maak je gebruik van de gelegenheid om eveneens een back-up te maken.
De volledige compressie is zo gebeurd. Op een oude server (Dell Poweredge 2450, PII 600 MHz, 512MB RAM, SCSI RAID 0-1) duurt de compressie minder dan 10 seconden voor een berichtenbestand van 1 MB (dus duizenden berichten).

Index

Individuele landingspage bezoekers: