Styles

mercredi 4 janvier 2017

EmacsLisp et les processus réseaux

Emacs cache en son sein une machine virtuelle complète, qui, si elle accuse effectivement le poids des ans, n'en reste pas moins fonctionnelle, à défaut d'être performante.

EmacsLisp, le Lisp qu'elle supporte, ressemble plus à du Common Lisp qu'à du Scheme. Le fait que l'éditeur soit intimement lié avec la machine virtuelle facilite l'apprentissage de ce monde étrange et sinueux qu'est la programmation en EmacsLisp.

J'ai voulu tester les capacités réseaux offertes par EmacsLisp pour apprendre notamment à ouvrir des connexions et développer de véritables serveurs. Emacs propose déjà des applications réseaux intéressantes comme Elnode (serveur Web asynchrone) ou tout simplement l'outil TRAMP qui permet d'accéder à n'importe quel fichier sur le réseau et de l'éditer comme tout autre fichier dans Emacs sans différence aucune.

Le programme  suivant réalise un test automatisé (avec ERT) de la création d'un serveur et d'un client, ce dernier envoyant un "Hello World" au serveur:

;;; -*- coding: utf-8; lexical-binding: t -*-

;;; Test of network functions.

(require 'ert)

(defvar net-server-port 10000
  "port of the net-test server")

(defun net-test-server-log (string &optional client)
  "If a *net-test-server* buffer exists, write STRING to it for
logging purposes."
  (if (get-buffer "*net-test-server*")
      (with-current-buffer "*net-test-server*"
 (goto-char (point-max))
 (insert (current-time-string)
  (if client (format " %s:" client) " ")
  string)
 (or (bolp) (newline)))))

(defun net-test-server-sentinel (proc msg)
  "Server sentinel"
  (if (string= msg "connection broken by remote peer\n")
      (net-test-server-log (format "client %s has quit" proc))
    (if (string= msg "deleted\n")
 (net-test-server-log (format "server %s has been deleted" proc))
      (net-test-server-log (format "unknown message for %s : %s" proc msg)))))

(defun net-test-server-filter (proc string)
  "Server filter function"
  (net-test-server-log string proc))

(ert-deftest net-test ()
  "Test the opening of a TCP network connection to a server."
  (let ((server (make-network-process
   :name "net-test-server"
   :buffer "*net-test-server*"
   :family 'ipv4
   :service net-server-port
   :sentinel 'net-test-server-sentinel
   :filter 'net-test-server-filter
   :server 't
   )))
    (should (processp server))
    (should (equal (process-contact server :service) net-server-port))
    (let ((client
    (open-network-stream "net-test-client"
    "*net-test-client*"
    "localhost"
    net-server-port)))
      (sit-for 1)
      (should (processp client))
      (should (equal (process-contact client :service) net-server-port))
      (process-send-string client "hello world\n")
      (sit-for 1)
      (delete-process client)
      (sit-for 1))
    (delete-process server)))


Les connexions réseaux en EmacsLisp sont gérées comme des "processus" d'un type particulier.

La programmation réseau en EmacsLisp tourne autour des points suivants:
- une fonction "sentinel" qui est appelée quand le processus réseau change d'état,
- une fonction "filtre" qui est appelée à chaque fois qu'un message est reçu par le processus réseau.

A chaque processus réseau peut être associé ou non un tampon ("buffer"), la fonction "filtre" par défaut réalisant une écriture du message reçu dans le tampon.

Comme l'indique la documentation officielle EmacsLisp, il faut laisser à Emacs des moments de respiration pour lui permettre d'appeler de façon asynchrone les méthodes "filtre" et "sentinel". C'est le rôle des appels à la fonction "sit-for" qui fait une pause d'1 seconde, pour éviter que le test unitaire ne ferme le client ou le serveur avant même qu'ils aient pu entrer en communication et s'envoyer des messages.

Aucun commentaire: