• Aucun résultat trouvé

6.2 Une dichotomie bien réelle

7.1.1 Une programmation trop complexe

La programmation de services de Téléphonie sur IP reste aujourd'hui une tâche délicate, même pour un programmeur expérimenté. Elle requiert, en eet, de nombreuses compétences que peu d'utilisateurs possèdent. Plusieurs plates-formes de téléphonie SIP orent une interface de programmation pour le développement de services. Ces API sont basées sur des langages de programmation généralistes comme C, Java ou encore C#.

Cette section donne un aperçu de la complexité de ce type d'interface en implémentant 63

un service via l'interface de programmation JAIN SIP, qui est aujourd'hui l'une des solutions de programmation les plus connues dans le domaine.

Considérons un service simple, que nous nommerons le service Compteur, qui gère le compteur des appels ayant été transférés vers une secrétaire, lorsque l'utilisateur as-socié au service ne peut être joint. Ce compteur est initialisé à zéro lorsque l'utilisateur s'enregistre auprès du serveur d'enregistrement (an de lui signier sa présence), incré-menté lorsqu'un appel est transmis à la secrétaire, et enregistré lorsque l'utilisateur se déconnecte. La gure 7.1 montre les principales étapes de l'implémentation de ce ser-vice Compteur en utilisant l'interface de programmation JAIN SIP. Les blocs de code Bi sont utilisés par la suite pour expliciter le service, mais également dans la section 7.2 pour faciliter la comparaison avec notre langage dédié de programmation (SPL).

Un service JAIN SIP implémente l'interface SipListener, qui est constituée des mé-thodes processRequest pour la gestion des requêtes, processResponse pour la gestion des réponses, et processTimeout pour la gestion des temporisations (timeouts) levées par la plate-forme de téléphonie. La structure de ces méthodes vise à spécier le compor-tement du service en fonction des requêtes SIP à traiter et des réponses associées. Dans notre exemple Compteur, les méthodes processRequest (ligne 11) et processResponse (ligne 47) spécient le traitement des requêtes et des réponses des méthodes REGISTER et INVITE du protocole (respectivement, lignes 15 et 36 pour la méthode processRequest, et lignes 70 et 57 pour la méthode processResponse). La méthode processTimeout (ligne 76) implémente seulement le traitement des timeouts concernant la méthode SIP REGISTER (ligne 78), correspondant au cas où le temps d'enregistrement de l'utilisateur sur la plate-forme de téléphonie a expiré.

La diculté de ce service consiste à maintenir le compteur, qui doit être accessible lors du traitement de l'ensemble des messages SIP (requêtes et réponses) associés à un enregistrement particulier de l'utilisateur. En eet, l'API ne fournit pas de moyen d'asso-cier un état à un enregistrement SIP1. De plus, ce service peut être partagé par plusieurs utilisateurs, empêchant l'utilisation de variables globales partagées. En conséquence, le programmeur doit explicitement implémenter cette association. Ainsi, lorsque le service reçoit une requête REGISTER pour un utilisateur qui n'est pas encore enregistré (bloc B1 de la gure 7.1), il crée un objet de la classe State (bloc B0) qui représente le compteur pour cet enregistrement. Cet objet State est enregistré dans un environnement global env sous un identiant ident qui est unique pour l'enregistrement. Ensuite, lors de l'arrivée d'une requête INVITE pour un utilisateur enregistré (ligne 36-37), la méthode processRequest transfère la requête à l'utilisateur (ligne 38-39). À la réception de la réponse, la méthode processResponse exécute le code comprenant les blocs B3 et B4.

Le bloc B3 gère le cas où une réponse avec un code d'erreur est reçue (ligne 58), indi-quant que l'utilisateur n'a pas pu prendre l'appel. Le compteur associé à l'utilisateur est alors récupéré depuis l'environnement env (ligne 55-56) et incrémenté (ligne 59) avant de transférer l'appel sur la secrétaire. Le bloc B4 traite, quant à lui, le cas normal où l'utilisateur décroche son téléphone. Enn, lorsque l'utilisateur annule son

enregistre-1Un enregistrement SIP correspond à la période pendant laquelle un utilisateur est associé à un terminal SIP, lui permettant de prendre des appels.

1. class State implements Lib.State{ 2. privateint count;

3. public State() {}

4. void setCounter (int x) { count = x; } 5. int getCounter() { return count; }

6. }

7.

8. public class Counter implements SipListener { 9. Lib lib;

10. [...]

11. public void processRequest (RequestEvent requestEvent) { 12. Request rq_request = requestEvent.getRequest();

13. SipProvider rq_sipProvider = (SipProvider)requestEvent.getSource();

14. String method = rq_request.getMethod();

15. if (method.equals (Request.REGISTER)) {

16. if(!lib.registrar.hasExpiresZero (rq_request)) { 17. if(!lib.registrar.hasRegistration (rq_request)) { 18. State state = new State();

19. int ident = lib.env.getId (rq_request);

20. lib.env.setEnv (ident, state);

21. state.setCounter (0);

22. rq_sipProvider.sendRequest (rq_request);

23. } else {

24. rq_sipProvider.sendRequest (rq_request);

25. }

26. } else if (lib.registrar.hasRegistration(rq_request)){

27. int ident = lib.env.getId (rq_request);

28. State state = (State)lib.env.getEnv (ident);

29. lib.local.log (state.getCounter());

30. lib.env.delEnv (ident);

31. rq_sipProvider.sendRequest (rq_request);

32. } else {

33. rq_sipProvider.sendRequest (rq_request);

34. }

35. }

36. if (method.equals (Request.INVITE)) {

37. if(lib.registrar.hasRegistration (rq_request)) {

38. ClientTransaction ct = rq_sipProvider.getNewClientTransaction(rq_request);

39. ct.sendRequest (rq_request);

40. } else {

41. rq_sipProvider.sendRequest (rq_request);

42. }

43. }

44. [...]

45. }

46.

47. public void processResponse (ResponseEvent responseEvent) { 48. ClientTransaction rs_ct = responseEvent.getClientTransaction();

49. Response rs_response = responseEvent.getResponse();

50. int rs_responseCode = rs_response.getStatusCode();

51. SipProvider rs_sipProvider = (SipProvider) responseEvent.getSource();

52. if(rs_ct != null) {

53. Request rs_request = rs_ct.getRequest();

54. String method = rs_request.getMethod();

55. int ident = lib.env.getId(rs_request);

56. State state = (State)lib.env.getEnv(ident);

57. if(method.equals (Request.INVITE)) { 58. if(rs_responseCode >= 300) {

59. state.setCounter (state.getCounter() + 1);

60. AddressFactory addressFactory = lib.getAddressFactory();

61. SipURI sipURI = addressFactory.createSipURI ("secretary", "company.com");

62. rs_request.setRequestURI (sipURI);

63. rs_sipProvider.sendRequest(rs_request);

64. } else {

65. rs_ct.sendResponse (rs_response);

66. }

67. } else{

68. rs_sipProvider.sendResponse(rs_response);

69. }

70. } else {

71. rs_sipProvider.sendResponse(rs_response);

72. }

73. [...]

74. }

75.

76. public void processTimeout (TimeoutEvent timeOutEvent) { 77. Timeout timeout = timeOutEvent.getTimeout();

78. if (timeout.equals (Timeout.REGISTER)){

79. int ident = timeout.getId();

80. State state = (State)lib.env.getEnv (ident);

81. local.log (state.getCounter());

82. lib.env.delEnv (ident);

83. }

84. }

85. }

B0

B1

B2_1

B3

B4

B2_2

Fig. 7.1 Le service Compteur écrit en JAIN SIP

ment (bloc B2 1) ou que ce dernier expire (bloc B2 2), le compteur est sauvegardé et supprimé de l'environnement env.

Certains traitements peuvent nécessiter de la part de la plate-forme de téléphonie de garder un état bien particulier, an de lui permettre d'associer une réponse à sa requête.

La transaction2 est alors dite à état (stateful), par opposition à sans état (stateless).

Mais cette association a un coût au niveau de la plate-forme. C'est pourquoi, an d'amé-liorer les performances, les transactions du service sont sans état (e.g., ligne 31) quand cela est possible. Les transactions à état (e.g., lignes 38-39) sont cependant nécessaires lorsque, comme dans notre exemple, certaines données dénies dans le traitement de la requête sont utilisées dans le traitement de la réponse. Ce type de transaction permet également la retransmission des messages SIP par la plate-forme de téléphonie, assurant que toutes les requêtes obtiennent une réponse. Dans le cas d'un service partiellement sans état, qui ne garantit donc pas cette dernière propriété, les requêtes auxquelles le service ne répond pas sont automatiquement retransmises par l'agent utilisateur de l'appelant. De telles retransmissions ré-invoquent le service, ce qui peut avoir des réper-cutions sur son comportement. Le bloc B3 de notre exemple incrémente un compteur et transmet la requête à la secrétaire. Dans ce cas, même si aucun traitement n'est eectué sur le compteur par la suite, la transaction doit être à état parce que le traitement de la requête INVITE modie le compteur, et n'est donc pas idempotent.

L'exemple Compteur montre clairement la diculté de développer des services même simples avec les approches existantes du type JAIN SIP. Ces approches étant basées sur un langage généraliste, il n'existe aucun support pour guider le programmeur à tra-vers les méandres du protocole et de l'API. La séparation du service selon des points d'entrée distincts pour le traitement de requêtes et de réponses obscurcit grandement les ots de contrôle et de données à travers les diérentes transactions. Ainsi, l'API ne reète absolument pas les concepts qui existent dans la gestion des communications du protocole SIP (e.g., transaction, dialogue3, ou encore enregistrement). Ce manque de décomposition, notamment au niveau du dialogue et de l'enregistrement, entraîne une gestion complexe de l'état.

Même pour un programmeur averti, l'implémentation de services de Téléphonie sur IP est aujourd'hui une tâche dicile. Elle demande une parfaite maîtrise d'un ensemble de technologies, où le manque de garde-fous fait de chaque erreur logicielle une me-nace pour la plate-forme de téléphonie. Il devient donc indispensable d'élever le niveau d'abstraction des solutions de programmation. Comme dans de nombreux domaines, la téléphonie fait face à une divergence de points de vue pour répondre à ce besoin. Aux experts en téléphonie s'opposent les experts en implémentation.

2Une transaction consiste à associer une réponse à sa requête (e.g., une requête REGISTER et sa réponse).

3Un dialogue est une relation SIP entre deux entités agents utilisateurs qui persiste pour une certaine durée. Un dialogue est initié par une requête INVITE et conrmé par une requête ACK. Le dialogue persiste jusqu'à ce que l'une des parties envoie une requête BYE.