!Snap est le dévelopment de ce que j'ai appris après avoir lu un article de Tom Hughes publié dans un article passé de CAUGers expliquant la facilité d'implémenter des applications réseau au sein de tâches taskwindow. Cette méthode décrite était nouvelle pour moi à ce moment là car elle n'était pas décrite dans le quatrième volume du PRM (décrivant les nouveautés liées à RO 3.6). En résumé, elle montre que les modifications à apporter au code source des applications réseau peuvent être minimales pour que ces dernières puissent tourner sous le régime du multi-tâches coopératif du WIMP. Comme la librairie officielle Acorn ne mentionnait même pas l'option à positionner (dans ses fichiers include), j'ai alors utilisé le librairie NetLib qui fournit autrement le même niveau de fonctionnalité dans son API.
!Snap, comme application client/serveur classique, comprend deux composants (vous avez déjà deviné ce qu'ils étaient). Une application serveur itératif lancé par la commande ci-dessous extraite du fichier !Run:
TaskWindow "RMEnsure SnapServSupport 0.00 RMRun <SnapS$Dir>.SnapS" -name SnapServer -display -wimpslot 64K -quit
Le serveur va associer une socket en écoute au port local de numéro 15208.
Dès que le serveur traite une demande de connexion de la part d'un client, il crée un sprite contenant l'écran complet de la station Acorn locale, ensuite il envoie la longueur du sprite (sur un mot) puis les données du sprite elles-même; après quoi il ferme la connexion.
Une application cliente WIMP traitant toutes les interactions utilisateur pour l'établissement de la connexion en plus de l'affichage dans une fenêtre Wimp du sprite transféré du serveur tout en tenant compte des contraintes comme la résolution et le nombre de couleurs courant dans le bureau de la station cliente.
Cette application cliente communique avec des tâches taskwindow qu'elle a juste démarrées à travers le mécanisme d'échange de message (déjà décrit dans un article antérieur de CAUGers nommé !JustAHack). Les tâches taskwindow n'hébergent que du code implémenté au sein d'un module RISC OS en charge de la gestion de toutes les E/S réseau.
*inetstat -a Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address (state) tcp 0 0 *.snap *.* LISTENLa chaine
*.snap
peut être remplacée par la chaine *.15208
dans votre environement.
Maintenant il est temps de démarrer l'application cliente. Double-cliquez sur l'icône !SnapC pour ce faire: L'icône !SnapC est maintenant visible dans la barre d'icônes.
Pour initier une connexion vers le serveur, cliquez sur l'icône: la boîte de dialogue suivante surgit:
Dans notre contexte, le serveur et le client résident sur le même noeud d'où le choix de l'adresse associée à l'interface "loopback ". Pour établir la connexion au serveur, il suffit de cliquer sur le bouton "Connect" et une fenêtre montrant la progression du transfert remplacera la fenêtre de connexion.
Lorsque le transfert est terminé, tla barre de progression disparait et est remplacée par la fenêtre résultat comprenant le sprite (ici, sa dimension est de 640 x 480 x 16 niveaux de gris).
static size_t mget_mode_variable(const int reason) { _kernel_swi_regs r; _kernel_oserror *p; int carry; r.r[0]= -1; r.r[1]= reason; p= _kernel_swi_c(OS_ReadModeVariable, &r, &r, &carry); if(p || carry) exit(EXIT_FAILURE); return (size_t) r.r[2]; } #define KSCRSIZE 7 #define XEIGEN 4 #define YEIGEN 5 #define XWNDLIMIT 11 #define YWNDLIMIT 12 #define KGWLCOL 128 #define KGWBROW 129 #define KGWRCOL 130 #define KGWTROW 131 #define NCOLOUR 3 void refresh(const int mode) { _kernel_swi_regs r; _kernel_oserror *p; const int reasons[] = { KGWLCOL, KGWBROW, KGWRCOL, KGWTROW, -1 }; gmode= mode; screensize= mget_mode_variable(KSCRSIZE) + 1024; xscrlimit= mget_mode_variable(XWNDLIMIT) + 1; yscrlimit= mget_mode_variable(YWNDLIMIT) + 1; xeigen= mget_mode_variable(XEIGEN); yeigen= mget_mode_variable(YEIGEN); ncolour= mget_mode_variable(NCOLOUR); r.r[0]= (int) &reasons; r.r[1]= (int) &vduvars; p= _kernel_swi(OS_ReadVduVariables, &r, &r); if(p) mexit(EXIT_FAILURE, p); reallocated= FALSE; } void spr_init(void) { int mode= _kernel_osbyte(135, 0, 0); if(mode >> 8) & 0xff); atexit(atexit1); }Dès que le mode écran change, le gestionnaire de service call à l'intérieur du module est appelé (sur apparition d'un service call ModeChange).
service-call-handler: sc_handler 0x46Extraitt du module main:
void sc_handler(int service_number, _kernel_swi_regs *r, void *pw) { refresh(r->r[2]); }Dès qu'une connexion avec un client est établie, le programme servira cette dernière avant de prendre en compte la requête suivante (d'où la qualité de "serveur itératif").
void mwrite(const int sd, const void *const pbuf, const size_t ilen) { const size_t mlenmax= 16384; size_t len= ilen; int nwritten; const char *pb= pbuf; do { nwritten= send(sd, pb, (len > mlenmax) ? mlenmax : len, 0); if(nwritten > 0); } ... int main() { ... for(;;) { connfd= accept(listenfd, (SA *) NULL, NULL); if(connfd > 0) { gconnfd= connfd; make_tw_compat(connfd); bOK= process_query(&pbuf, &nbytes); tnbytes= (size_t) htonl(nbytes); mwrite(connfd, &tnbytes, sizeof(size_t)); if(gconnfd!= -1) { if(bOK) mwrite(connfd, pbuf, nbytes); if(gconnfd!= -1) { close(connfd); gconnfd= -1; } } } } return EXIT_SUCCESS; }
Et maintenant voici le coeur du serveur (création d'un sprite avant son envoi). La variable booléenne reallocated
sert de drapeau afin de savoir si une réallocation de la mémoire pour contenir un sprite est nécessaire ou non (positionnée à FALSE
lorsque le mode d'écran change, remis à TRUE
après avoir appelé la fonction de la librairie C realloc
)
static const char spr_name[]= "screen"; static void *pscreen= NULL; static BOOL reallocated= FALSE; static size_t mlen= 0; static size_t ncolour; static size_t screensize; static size_t xscrlimit, yscrlimit; static size_t xeigen, yeigen; static int gmode= -1; static struct { int gwlcol, gwbrow, gwrcol, gwtrow; } vduvars; static void create_sprite(void) { _kernel_swi_regs r; _kernel_oserror *p; r.r[0]= 16 | 256; r.r[1]= (int) pscreen; r.r[2]= (int) spr_name; r.r[3]= ncolour > 256 ? 0 : 1; r.r[4]= vduvars.gwlcol<<xeigen; r.r[5]= vduvars.gwbrow<<yeigen; r.r[6]= vduvars.gwrcol<<xeigen; r.r[7]= vduvars.gwtrow<<yeigen; p= _kernel_swi(OS_SpriteOp, &r, &r); if(p) mexit(EXIT_FAILURE, p); } BOOL process_query(void **const p2pbuf, size_t *const pnbytes) { _kernel_swi_regs r; _kernel_oserror *p; void *pstr; int *pi; size_t llen; assert(pnbytes!= NULL); *pnbytes= 0; if(!reallocated) { pstr= realloc(pscreen, llen= screensize); if(!pstr) return FALSE; pi= (int *) (pscreen= pstr); mlen= llen; *pi= llen; *(pi+1)= 0; *(pi+2)= 16; r.r[0]= 9 | 256; r.r[1]= (int) pscreen; p= _kernel_swi(OS_SpriteOp, &r, &r); if(p) mexit(EXIT_FAILURE, p); reallocated= TRUE; } create_sprite(); *pnbytes= (size_t) *(((int *) pscreen)+3); *p2pbuf= pscreen; return TRUE; }
SnapCGet <key> <serveraddress>
SnapCDone <key>
est exécutée (cette fois ci en utilisant un appel de la kibrairie C system
) afin de libérer les ressources allouées.
Le module c.spriteutil est en charge de toute la gestion du dessin du sprite en fonction du mode courant et des capacités de la station cliente.