À son apogée, ActionScript était le langage de prédilection pour de nombreux développeurs web, dominant le paysage jusqu'en 2018. Son usage intensif et sa popularité ont été d'une certaine manière ébranlés à partir de 2008, en grande partie à cause de la sortie de l'App Store et de la politique d'Apple qui interdisait l'utilisation de plugins dans ses navigateurs. Cette décision a marqué un tournant, entravant la progression d'ActionScript sur le web.

Cependant, loin de s'éteindre complètement, le framework Adobe AIR a suivi son propre chemin, restant à l'abri des aléas du marché web. Ce qui a maintenu AIR en vie et pertinent a été une combinaison d'éléments : une communauté dédiée et passionnée, la flexibilité cross-platform du framework, son caractère universel, et surtout, sa nature complète et efficiente. De plus, avec un langage "human readable" et accessible, il a rendu la programmation plus intuitive et conviviale pour les développeurs de tous niveaux.

La renaissance du framework peut également être attribuée à un événement crucial : son acquisition par la société Harman, une filiale de Samsung. Cette transition a infusé une nouvelle vie à Adobe AIR, stimulant sa popularité et renforçant sa position dans le monde du développement. Aujourd'hui, un nombre impressionnant d'éditeurs adoptent cette technologie, témoignant de sa pertinence persistante, pour concevoir et développer des applications cross-platform de qualité.

De plus, des groupes de programmeurs sont depuis quelque années en train de porter Actionscript de nouveau sur le web via la technologie WASM et nous supportons activement plusieurs groupes.

Fort de notre expérience en tant que développeurs spécialisés dans l'ActionScript et Adobe AIR depuis de nombreuses années, nous avons identifié une niche particulièrement intéressante mais largement inexplorée dans le paysage du développement : l'utilisation d'ActionScript pour faciliter l'échange de données en temps réel sans l'appui d'une infrastructure web, spécifiquement via la mise en place d'un serveur TCP.

Si ActionScript, couplé au framework AIR, est reconnu pour sa puissance et sa capacité à offrir des classes de programmation préfabriquées, il est aussi vrai que notre domaine d'investigation spécifique demeure largement sous-documenté. Cette situation nous a souvent plongés dans des terrains inconnus, parsemés de défis, d'incertitudes et de lacunes à combler.

Notre démarche ne s'est pas simplement arrêtée à la réalisation d'un projet. Elle a débouché sur une exploration approfondie qui a ouvert de nouvelles voies et enrichi les connaissances existantes dans ce secteur spécifique du développement. Conscients de l'importance de partager ces découvertes, nous avons décidé de rendre notre travail accessible en Open Source. De cette manière, nous espérons contribuer à la communauté des développeurs, offrant ainsi des outils et des ressources qui pourraient être la base de projets futurs et d'innovations dans ce domaine.

Objectifs de recherche

Dans notre approche de recherche, afin de développer une compréhension approfondie de la mise en œuvre d'un serveur TCP utilisant ActionScript, un langage traditionnellement utilisé pour le développement de contenus interactifs, nous avons considéré plusieurs sous-objectifs spécifiques :

  1. Évaluer la faisabilité : Déterminer s'il est techniquement possible et pratique d'implémenter un serveur TCP en ActionScript. Cela comprend une évaluation de la capacité d'ActionScript à gérer les complexités et les exigences de la mise en place et de la gestion d'une connexion TCP.
  2. Identifier les avantages potentiels : Identifier les avantages uniques que peut offrir ActionScript pour le développement d'un serveur TCP. Par exemple, le langage pourrait offrir des avantages en termes de rapidité de développement, de facilité d'intégration avec d'autres technologies, de performances et d’intégration cross-plateformes.
  3. Surmonter les défis techniques : Identifier et résoudre les problèmes techniques qui peuvent survenir lors de l'implémentation d'un serveur TCP en ActionScript. Ces défis pourraient inclure des problèmes de performance, de sécurité, de compatibilité avec d'autres technologies, etc.
  4. Comparer avec d'autres approches : Comparer l'approche ActionScript avec d'autres langages de programmation couramment utilisés pour l'implémentation de serveurs TCP. Cette comparaison permettrait de déterminer dans quels cas l'utilisation d'ActionScript pourrait être préférable, et dans quels cas d'autres langages pourraient être plus appropriés.

L'objectif ultime est de contribuer à la base de connaissances existante sur l'implémentation de serveurs TCP, et spécifiquement sur l'utilisation d'ActionScript pour ce genre de tâches. Cette recherche pourrait aider d'autres développeurs à prendre des décisions éclairées lors du choix d'un langage de programmation pour leur propre serveur TCP.

Méthodologie de recherche

Revue de littérature

La mise en place d'un serveur TCP à l'aide d'ActionScript est un sujet peu exploré dans la littérature scientifique. Cependant, l'implémentation de serveurs TCP en général, ainsi que l'utilisation d'ActionScript pour le développement d'applications, sont deux sujets qui ont été étudiés de manière approfondie. Voici un aperçu de la littérature sur ces deux sujets :

  1. Serveurs TCP : Les protocoles TCP (Transmission Control Protocol) sont bien documentés dans la littérature technique et scientifique (Clark, 1988; Postel, 1981). Les travaux ont exploré les nombreux facteurs qui peuvent affecter la performance d'un serveur TCP, tels que la congestion du réseau (Jacobson, 1988), la taille de la fenêtre de transmission (Allman et al., 1999), et la latence de la connexion (Padhye et al., 2000). Des recherches ont également été menées sur les différentes méthodes d'implémentation de serveurs TCP, notamment l'utilisation de différents langages de programmation (Ousterhout, 2018).
  2. ActionScript : ActionScript a été principalement utilisé pour le développement de contenus interactifs, en particulier dans le cadre de la plateforme Flash d'Adobe (Moock, 2007). La littérature sur ActionScript a souvent abordé son utilisation pour le développement de jeux, d'animations et d'applications multimédias interactives (Shupe, 2006). Cependant, l'usage de ce langage pour le développement de serveurs TCP n'a pas été largement exploré.

Par conséquent, il existe un vide dans la littérature actuelle concernant l'implémentation de serveurs TCP en ActionScript. Ce projet cherche à combler ce vide en explorant la faisabilité, les avantages potentiels et les défis techniques de cette approche.

Références

  • Allman, M., Paxson, V., & Stevens, W. (1999). TCP Congestion Control. RFC 2581.
  • Clark, D. (1988). The Design Philosophy of the DARPA Internet Protocols. SIGCOMM '88.
  • Jacobson, V. (1988). Congestion Avoidance and Control. SIGCOMM '88.
  • Moock, C. (2007). Essential ActionScript 3.0. O'Reilly Media.
  • Ousterhout, J. (2018). Why Threads Are A Bad Idea (for most purposes). Presentation for USENIX Technical Conference.
  • Padhye, J., Firoiu, V., Towsley, D., & Kurose, J. (2000). Modeling TCP Throughput: A Simple Model and its Empirical Validation. SIGCOMM '00.
  • Postel, J. (1981). Transmission Control Protocol. RFC 793.
  • Shupe, G. (2006). The ActionScript 3.0 Quick Reference Guide. O'Reilly Media.
  • Ressources système : Apple Developer Documentation, Android Developer Dashboard, Android Battery Stats, macOS Activity Monitor, Windows Task Manager.

Design et implémentation

Au cours de cette étape cruciale du projet, notre équipe s'est concentrée initialement sur la définition précise des spécifications fonctionnelles pour le serveur TCP. Ces spécifications ont été soigneusement élaborées pour répondre à l'ensemble des exigences liées au contexte spécifique de notre projet. L'enjeu majeur était de permettre la transmission fluide et la gestion efficace des données entre différents utilisateurs en situation de présentiel. L'originalité de notre approche résidait dans le fait que ces échanges devaient se faire de manière locale, sans avoir recours à une plateforme web.

Spécifications fonctionnelles

  1. Rôle central de l'animateur : Au sein du réseau établi, un utilisateur endossant le rôle d'animateur devient la plaque tournante des communications. C'est lui qui héberge le serveur TCP directement sur sa machine en local, servant ainsi de pont pour connecter l'ensemble des participants.
  2. Gestion des données : Toutes les communications (transmissions de données), sont routées via le serveur TCP de l'animateur. Ce serveur détient la capacité de reconnaître chaque utilisateur participant à travers son adresse IP. Une fois les données reçues, il se charge alors de les redistribuer efficacement vers les destinataires appropriés, garantissant ainsi une fluidité et une cohérence dans les échanges.

Cette approche garantit non seulement une autonomie par rapport aux services en ligne, mais offre également une sécurité accrue, puisque les données demeurent strictement en local et sont gérées de manière centralisée par l'animateur.

Spécifications techniques

Après avoir établi les spécifications fonctionnelles, notre attention s'est portée sur la conception technique du serveur TCP. Ces spécifications techniques sont vitales car elles assurent la stabilité, la rapidité et l'efficacité du serveur dans des conditions réelles d'utilisation. Elles englobent divers aspects allant de la gestion des connexions simultanées à la taille optimale des buffers de données, en passant par une gestion adéquate des données et des utilisateurs.

Nombre de connexions simultanées

Nous avons conçu notre serveur pour être scalable. Au cours de la phase expérimentale (détaillée dans le chapitre suivant), nous avons analysé la performance du serveur à différents niveaux de charge. Cela nous a permis d'évaluer ses capacités, en commençant par gérer 250 connexions simultanées, et augmentant progressivement par paliers de 250, jusqu'à atteindre un potentiel de 1500 connexions simultanées.

Taille des buffers de données

La fluidité de la transmission des données repose sur un choix judicieux de la taille du buffer. Nous avons travaillé à optimiser cette taille tout en respectant les contraintes imposées par les cartes réseau des appareils susceptibles d'être connectés à notre serveur.

Gestion des données

Dans le contexte d'un serveur qui gère de multiples connexions, il est primordial d'assurer un transfert sans faille des données. Notre serveur a été conçu pour segmenter, transférer et reconstituer les informations, garantissant ainsi que chaque paquet de données atteint sa destination sans altération ni perte.

Gestion des utilisateurs du serveur TCP

Chaque utilisateur connecté au serveur a un profil de gestion et de communication qui lui est propre. Par exemple, l'utilisateur-animateur a des prérogatives étendues, lui permettant de communiquer avec l'ensemble des participants ou de cibler un utilisateur spécifique. Cette distinction des profils a nécessité une ingénierie fine pour assurer des échanges clairs et fluides entre tous les acteurs du réseau.

Ces spécifications techniques constituent la colonne vertébrale de notre serveur, garantissant une performance optimale pour une variété d'utilisateurs et de contextes.

Architecture du serveur et gestion des données

Une fois les spécifications définies, nous avons conçu l'architecture du serveur. Cela comprend le détail de chaque composant du serveur, la manière dont chaque composant interagit avec les autres, comment les données sont gérées et transmises, et comment les erreurs et exceptions sont traitées.

Nous nous sommes basés sur la classe Socket de ActionScript pour gérer les connexions.

Implémentation

Comme notre serveur est développé en ActionScript, nous utilisons des outils et des environnements de développement compatibles avec ce langage, comme Adobe Animate ou d'autres IDE compatibles ActionScript.

Tests et expérimentation

Au cours de notre processus de développement, l'importance d'une approche test-driven ne peut être sous-estimée. Elle garantit non seulement la qualité du code, mais aussi sa fiabilité dans des conditions réelles d'utilisation.

Dans cette phase, nous évaluerons les performances et l'efficacité du serveur TCP implémenté en ActionScript. Plusieurs aspects seront évalués, notamment :

Vitesse de transmission

Pour ces tests, nous avons mesuré le débit de transmission des données de différentes tailles à travers le serveur en mode point à point, c’est-à-dire d’un utilisateur à un autre.

Débit de transmission de données point à point

Stabilité

Nous évaluerons combien de temps le serveur peut fonctionner sans interruption et combien de données il peut gérer sans erreurs.

Gestion de la congestion

Nous testerons comment le serveur gère la congestion du réseau, en simulant des conditions de congestion et en mesurant comment cela affecte les performances du serveur.

Gestion des connexions

Nous testerons combien de connexions simultanées le serveur peut gérer et comment cela affecte les performances.

Ces expérimentations permettront d'évaluer si le serveur TCP en ActionScript peut être une alternative viable aux serveurs TCP écrits dans d'autres langages de programmation.

Analyse des résultats

Une fois les expérimentations réalisées, nous analyserons les résultats en comparant les performances de notre serveur TCP en ActionScript avec les attentes initiales et les normes du secteur. Nous utiliserons des statistiques descriptives pour résumer les résultats et des techniques d'inférence pour déterminer si les différences observées sont significatives.

  1. Vitesse de transmission : Nous analyserons la vitesse de transmission des données en fonction de leur taille et comparerons ces résultats aux serveurs TCP standards. L'objectif sera d'évaluer si notre serveur offre une vitesse de transmission compétitive.
  2. Stabilité : Nous examinerons la durée de fonctionnement ininterrompu du serveur et la quantité de données qu'il peut traiter sans erreurs. Ces informations nous aideront à déterminer la fiabilité du serveur dans un environnement de production réel.
  3. Gestion de la congestion : Nous analyserons comment les performances du serveur sont affectées en cas de congestion du réseau. En particulier, nous comparerons la vitesse de transmission des données et le taux d'erreur avant et pendant la congestion.
  4. Gestion des connexions : Nous étudierons le nombre maximum de connexions simultanées que le serveur peut gérer sans dégradation significative des performances. Cela nous aidera à comprendre si le serveur peut gérer une charge élevée.

Pour chaque indicateur de performance, nous utiliserons des graphiques pour visualiser les résultats, ce qui facilitera la compréhension de la performance du serveur. Par exemple, nous pourrions utiliser des histogrammes pour montrer la distribution de la vitesse de transmission ou des diagrammes de dispersion pour montrer la relation entre le nombre de connexions et la vitesse de transmission.

Enfin, nous conclurons notre analyse en résumant nos résultats et en les mettant en contexte. Nous discuterons des avantages et des inconvénients du serveur TCP en ActionScript que nous avons développé, et nous proposerons des directions pour les travaux futurs.

Problèmes rencontrés

Durant ce projet, nous avons été rapidement confrontés à des problématiques bien précises, mais également fluctuantes en raison du caractère cross-plateformes du projet. Ces problématiques ont principalement trait aux différences techniques entre les OS considérés, mais également entre les appareils cible.

Notre recherche a mis en évidence différents problèmes qui ont été une source de tests / implémentations / recherche conséquente. Nous pouvons aujourd’hui affirmer avec le recul que le temps passé sur ces problèmes a représenté entre 65 et 70% du temps de recherche alloué.

Nous pouvons séparer ces problèmes en 2 catégories :

  • Les problèmes « convenus », possédant un caractère somme toute prévisible, en raison de l’essence même du projet cross-plateforme.
  • Les problèmes « inédits », qui ont été les plus retors dans le sens où la logique ne prévalait pas forcément à leur résolution, mais c’est plutôt grâce à une multitude d’idées nouvelles et d’expérimentations qu’ils ont été surmontés.

Les problèmes « convenus »

  1. Différences de gestion des buffers entre les plateformes.
  2. État Wake on/off sur mobile.
  3. Gestion des ressources système CPU.
  4. Gestion des ressources système mémoire.

Les problèmes « inédits »

  1. Hétérogénéité de la persistance des sockets, notamment sous macOS et Android.
  2. Débits limités dans les flux full duplex.

Différences de gestion des buffers entre les plateformes

iOS

iOS utilise des mécanismes spécifiques pour la gestion de la mémoire et des buffers, en particulier dans le contexte des communications réseau. Le framework CocoaTouch propose des classes dédiées à la gestion des buffers, comme NSData et NSMutableData. Le contrôle fin des tampons n'est généralement pas exposé aux développeurs, ce qui peut entraîner une certaine perte d'efficacité dans les communications en temps réel.

Android

Android, basé sur le noyau Linux, permet une gestion plus flexible des buffers. Des classes Java comme ByteBuffer offrent un contrôle plus direct sur la mémoire. Cependant, la machine virtuelle Java ajoute une couche d'abstraction qui peut également affecter les performances.

macOS

Étant basé sur Unix, macOS offre un contrôle plus granulaire des ressources système. Des technologies comme Grand Central Dispatch permettent de gérer efficacement les buffers. Les développeurs peuvent utiliser des bibliothèques de bas niveau pour optimiser le tamponnage et la gestion de la mémoire.

Windows

Windows utilise son propre modèle de gestion des buffers, souvent à travers des API comme WinSock. Il permet également un contrôle assez détaillé des ressources système mais peut nécessiter des ajustements spécifiques pour optimiser les performances, surtout lorsqu'il est question de compatibilité avec d'autres systèmes d'exploitation.

En ActionScript, la taille des buffers de données pour la transmission via une socket n'est généralement pas spécifiée par défaut dans la documentation officielle d'Adobe. Cependant, le comportement par défaut est souvent déterminé par le système d'exploitation sous-jacent ou le runtime Flash/AIR.

Sur les plateformes mobiles, nous avons expérimenter des incohérences de transmission de données et nous avons donc dû utiliser des buffers définis dans le code. Le principe est de gérer cette problématique manuellement dans le code, en accumulant des données dans un buffer personnalisé jusqu'à ce qu'il atteigne la taille souhaitée avant de le traiter ou de l'envoyer.

Plus la taille de buffer est élevée, plus les erreurs sont nombreuses, en raison des paquets de données non transmis intégralement. On voit sur ce graphique que pour un buffer de 512ko, on expérimente quasiment un paquet sur deux erroné.

La taille de buffer a également une forte incidence sur la vitesse de transmission des données. Nos expérimentations montrent que la vitesse de transmission d’un fichier est liée quasi proportionnellement à la taille du buffer, mais que d’un système d’exploitation à l’autre, les résultats sont assez disparates bien que cohérent pour chaque OS. Cela vient de la conjonction de 2 facteurs principaux : la gestion du CPU et la gestion de la mémoire allouée.

Problématiques associées à la communication de données

  1. Latence : Les différences dans la gestion des buffers peuvent entraîner une latence variable dans les communications entre différentes plateformes.
  2. Fiabilité : Un tampon mal géré peut entraîner des problèmes de fiabilité, comme la perte de données en transit.
  3. Interopérabilité : La communication entre différentes plateformes nécessite souvent des ajustements spécifiques pour gérer les différences dans les tailles de buffer et les stratégies de gestion de la mémoire.
  4. Consommation de ressources : Une mauvaise gestion des buffers peut entraîner une utilisation inutile de la CPU et de la mémoire, affectant ainsi la performance globale du système.

État Wake on/off sur mobile

Le comportement d'une socket réseau sur une plateforme mobile lorsqu'un appareil passe en mode veille varie selon l'écosystème du système d'exploitation et les paramètres de gestion de l'énergie de l'appareil. Voici une vue d'ensemble pour quelques plateformes courantes :

  • iOS :

Sur les appareils iOS, une application qui passe en arrière-plan (ou lorsque l'écran se verrouille, mettant l'appareil en mode veille) est généralement suspendue pour économiser les ressources de l'appareil, y compris la batterie. Cela signifie que les sockets ouverts par l'application seront dans un état "gelé" et ne pourront pas recevoir ou envoyer de données jusqu'à ce que l'application revienne au premier plan.

Il existe toutefois des moyens d'exécuter des tâches en arrière-plan en utilisant les API d'exécution en arrière-plan (Background Modes). Mais là encore, la gestion des données est limitée à 1 ou 2% selon nos tests réalisés et ne permet pas une continuité satisfaisante.

  • Android :

Sur Android, le comportement peut être un peu plus flexible, mais il dépend également des versions du système d'exploitation et des personnalisations du fabricant. Dans de nombreux cas, une application en arrière-plan peut continuer à maintenir une connexion de socket ouverte, mais cela peut avoir un impact significatif sur la vie de la batterie. Les versions récentes d'Android ont introduit des restrictions plus strictes sur ce que les applications en arrière-plan sont autorisées à faire pour économiser la batterie.

Selon nos tests, les appareils continuent de recevoir ou d’envoyer  les données, mais :

  • La vitesse de réception et de transmission des données est réduite de 74% en moyenne,
  • La phase de post-traitement est réduite de 92%.

Mais là encore, cette différence de performances ne permet pas une continuité satisfaisante.

Problématiques connexes :

  1. Consommation de la batterie : Maintenir une socket ouverte en veille peut consommer beaucoup de batterie, ce qui est souvent indésirable pour les utilisateurs.
  2. Gestion de la connexion : Le fait de passer en mode veille peut entraîner la fermeture de la socket par le système d'exploitation, nécessitant une logique de reconnexion complexe.
  3. Notifications : Si l'appareil est en mode veille et qu'une donnée cruciale est reçue sur la socket, informer l'utilisateur peut être difficile.
  4. Compatibilité et fragmentation : Les différentes versions des systèmes d'exploitation et les différences entre les fabricants peuvent rendre difficile la création d'une solution unique pour gérer les sockets en mode veille.
  5. Ressources système : Les systèmes d'exploitation mobiles gèrent de manière agressive les ressources pour les applications en arrière-plan, ce qui peut entraîner des comportements imprévus ou indésirables.

Gestion des ressources système CPU

La manière dont chaque système gère ses ressources système peut affecter significativement les performances et l'efficacité des applications qui reposent sur la communication TCP. Lors de notre recherche, nous avons exploré les différences de gestion des ressources système CPU entre ces quatre plateformes et les problématiques qui peuvent en découler.

L'une des distinctions majeures qui émerge aujourd'hui réside dans la disparité croissante en termes de performances entre les processeurs X86 et ARM, deux architectures qui se présentent comme des entités diamétralement opposées. Cette disparité s'est accentuée à mesure que les technologies de traitement évoluent et que les besoins en matière de puissance de calcul deviennent de plus en plus complexes et diversifiés.

L'architecture X86, souvent associée aux ordinateurs de bureau et aux serveurs traditionnels, repose sur une conception robuste orientée vers les performances brutes et la polyvalence. Elle a évolué au fil des années pour répondre aux exigences croissantes des applications gourmandes en ressources et des charges de travail intensives. Les processeurs X86 se distinguent par leur capacité à exécuter des tâches complexes rapidement et efficacement, en exploitant des cycles d'horloge élevés et une architecture hautement optimisée.

D'un autre côté, les processeurs ARM ont gagné en popularité en tant que choix privilégié pour les appareils mobiles, les dispositifs embarqués et les systèmes à faible consommation d'énergie. Cette architecture est axée sur l'efficacité énergétique et la compacité, offrant ainsi des avantages significatifs en termes d'autonomie de batterie et de dissipation thermique réduite. Cependant, les processeurs ARM étaient autrefois considérés comme moins puissants que leurs homologues X86, limitant ainsi leur utilisation dans les charges de travail intensives. Cependant, les choses ont évolué de manière significative. Avec l'essor des appareils mobiles sophistiqués, des technologies embarquées avancées et des besoins en intelligence artificielle et en traitement parallèle, les processeurs ARM ont fait d'énormes progrès en matière de performances. Les conceptions modernes d'architectures ARM ont intégré des innovations telles que le multicœur, la vectorisation et l'accélération matérielle spécialisée, permettant aux processeurs ARM de rivaliser avec leurs homologues X86 dans un éventail de domaines d'application.

Cette divergence dans les performances des processeurs X86 et ARM reflète une diversité croissante dans les besoins technologiques d'aujourd'hui. Les développeurs doivent désormais évaluer attentivement les avantages et les inconvénients de chaque architecture en fonction des charges de travail spécifiques. Alors que l'architecture X86 excelle dans les applications gourmandes en ressources et les charges de travail nécessitant une puissance de calcul substantielle, l'architecture ARM offre une efficacité énergétique précieuse pour les appareils mobiles et les environnements où la dissipation thermique est un facteur critique.

Articles :

ARM vs x86: What's the difference?

https://www.redhat.com/en/topics/linux/ARM-vs-x86

What is an ARM processor?

https://www.redhat.com/en/topics/linux/what-is-arm-processor

iOS

Processeur ARM.

  1. Gestion des Ressources
    • Limitation du CPU: Les applications en arrière-plan sont généralement suspendues ou ont un temps CPU très limité.
  2. Problématiques
    • Gestion des Connexions: En raison des restrictions du temps CPU, maintenir une connexion TCP ouverte peut être difficile.
    • Économie d'énergie: Les politiques strictes de gestion de l'énergie peuvent entraîner une déconnexion.
  3. Statistiques
    • Temps CPU moyen pour les applications en arrière-plan: ~5%.

Android

Processeur ARM.

  1. Gestion des Ressources
    • Doze Mode: Réduit l'activité CPU et réseau pour les applications en arrière-plan.
    • Standby Apps: Les applications inactives peuvent être mises en "standby" pour limiter leur accès aux ressources système.
  2. Problématiques
    • Fragmentation: Différents fabricants ont des implémentations Android personnalisées.
    • Notifications: Le comportement des sockets peut être erratique, surtout pendant le "Doze Mode".
  3. Statistiques
    • Temps CPU moyen pour les applications en arrière-plan: ~10%.

macOS

Processeur X86.

  1. Gestion des Ressources
    • App Nap: Réduit l'activité des applications en arrière-plan.
    • Power Nap: Permet des mises à jour système en mode veille.
  2. Problématiques
    • Compatibilité: Les applications doivent être optimisées pour la dernière version de macOS pour tirer parti des améliorations de gestion des ressources.
  3. Statistiques
    • Temps CPU moyen pour les applications en arrière-plan: ~20%.

Windows

Processeur X86.

  1. Gestion des Ressources
    • Process Priority: Les tâches en arrière-plan peuvent avoir des priorités plus basses.
  2. Problématiques
    • Multiples Versions: Les performances peuvent varier entre Windows 7, 8, et 10.
    • Firewall et Sécurité: Les paramètres de sécurité peuvent interrompre les connexions TCP.
  3. Statistiques
    • Temps CPU moyen pour les applications en arrière-plan: ~15%.

Problématiques Communes

  • Latence : Varie en fonction des ressources système allouées.
  • Déconnexion : Risque accru en raison de la gestion agressive des ressources.
  • Gestion de la Batterie : Doit être équilibrée avec la performance réseau.

Gestion des ressources système mémoire

La gestion de la mémoire RAM est un aspect crucial du développement d'applications, en particulier celles qui nécessitent des communications via un serveur TCP. Les différences de gestion de la mémoire entre iOS, Android, macOS et Windows ont des implications sur la performance et la fiabilité des applications.

iOS

  1. Gestion de la RAM
    • Jetsam: iOS utilise un processus appelé Jetsam pour terminer automatiquement les applications qui consomment trop de mémoire.
    • Priorité: Les tâches en arrière-plan ont une priorité inférieure pour la gestion de la RAM.
  2. Problématiques
    • Performance Réduite: Les limitations de la mémoire peuvent affecter la vitesse de traitement des paquets TCP.
    • Déconnexion: Le risque de déconnexion des serveurs TCP est plus élevé.
  3. Statistiques
    • La mémoire RAM moyenne sur un iPhone en 2023: 8GB
    • Utilisation moyenne de la RAM par des applications en arrière-plan: ~10-15%.

Android

  1. Gestion de la RAM
    • Garbage Collector: Utilisation du "Garbage Collector" pour nettoyer la mémoire inutilisée.
    • Low Memory Killer: Termine les applications selon une série de niveaux de priorité lorsque la mémoire est faible.
  2. Problématiques
    • Fragmentation de la Mémoire: Peut réduire l'efficacité de la gestion de la mémoire, ce qui affecte la communication TCP.
    • Lenteurs: En raison de la gestion de la mémoire, des retards peuvent être introduits dans la communication TCP.
  3. Statistiques
    • Mémoire RAM moyenne sur un smartphone Android en 2023: 12GB
    • Utilisation moyenne de la RAM par des applications en arrière-plan: ~20%.

macOS

  1. Gestion de la RAM
    • Mémoire Compressée: macOS utilise une mémoire compressée pour augmenter virtuellement la RAM disponible.
    • Swap: Utilisation de l'espace disque pour étendre la RAM.
  2. Problématiques
    • Swap et Latence: L'utilisation de la mémoire swap peut entraîner des latences dans la communication TCP.
    • Consommation Élevée: Les applications macOS tendent à utiliser plus de RAM.
  3. Statistiques
    • Mémoire RAM moyenne sur un MacBook en 2023: 16GB
    • Utilisation moyenne de la RAM par des applications en arrière-plan: ~30%.

Windows

  1. Gestion de la RAM
    • Fichier d'Échange: Utilisation d'un fichier d'échange pour gérer la mémoire.
    • Virtual Memory: Possibilité d'étendre la RAM en utilisant la mémoire virtuelle.
  2. Problématiques
    • Latence et Jitter: L'utilisation de la mémoire virtuelle peut introduire des variations dans la latence.
    • Gestion Inefficace: Les anciennes versions de Windows sont moins efficaces dans la gestion de la RAM.
  3. Statistiques
    • Mémoire RAM moyenne sur un PC Windows en 2023: 16GB
    • Utilisation moyenne de la RAM par des applications en arrière-plan: ~25%.

Problématiques Communes

  • Gestion de la Bande Passante: L'efficacité de la gestion de la RAM peut avoir un impact sur la bande passante utilisable pour la communication TCP.
  • Sécurité: Une mauvaise gestion de la RAM peut exposer des failles de sécurité, notamment via des fuites de mémoire.

Hétérogénéité de la persistance des sockets

Dans le cadre de notre projet de gestion de données multi-utilisateurs via un serveur TCP ActionScript, l'architecture est centrée autour d'un élément clé appelé "SocketServer". Cette entité agit comme une plaque tournante, établissant et gérant les connexions Socket des différents utilisateurs participants. Lorsqu'un utilisateur souhaite participer à l'échange de données, il se connecte à ce serveur Socket à travers sa propre connexion Socket. Une fois connecté, l'utilisateur peut commencer à envoyer et recevoir des données, qui sont ensuite traitées et redistribuées par le serveur aux autres utilisateurs connectés.

Lorsqu'un utilisateur se déconnecte, sa connexion Socket est retirée du pool géré par le serveur Socket. Il est donc naturellement exclu de toute communication subséquente orchestrée par le serveur TCP.

Il est important de noter que l'animateur de l'application est responsable de la gestion du serveur TCP, qui à son tour, gère le SocketServer. Si cet utilisateur (l'animateur) se déconnecte, par exemple en fermant son application, cela entraîne la dissolution du serveur Socket. Cependant, même si le serveur Socket n'est plus opérationnel, certaines de ses composantes continuent à exister pendant une certaine période. Cela est particulièrement vrai pour la gestion des sockets au niveau du système d'exploitation et, plus spécifiquement, du port réseau utilisé. Cette persistance post-fermeture a été observée à la fois sur les systèmes d'exploitation macOS et Android.

Cette persistance génère un défi : si l'on tente de créer à nouveau le serveur TCP pendant cette période de latence, une erreur est déclenchée, empêchant ainsi la réinitialisation du serveur Socket.

Le processus devient dès lors caduque et le serveur n’est pas utilisable.

Débits limités dans les flux full duplex (bidirectionnel)

L'un des défis les plus intrigants que nous avons rencontrés pendant la durée de ce projet de recherche concerne les limitations de débit observées au sein du serveur TCP bidirectionnel.

Nos expériences ont mis en évidence un plafonnement des débits de transfert de données qui, bien qu'il varie légèrement selon le système d'exploitation utilisé, se situe dans une plage de performance assez restreinte. Plus précisément, nous avons identifié un "plafond de verre" à environ 16 Mbps, soit environ 2 Mo/s, pour le transfert de données point à point — c'est-à-dire du client émetteur au client récepteur.

Tests de débit de transfert de données point à point avec un serveur TCP bidirectionnel

Contraste avec le Serveur TCP Unidirectionnel

Parallèlement, nous avons conçu et testé un serveur TCP unidirectionnel, spécifiquement conçu pour la gestion du service de données binaires via le protocole HTTP. Dans ce scénario, le plafond de 16 Mbps disparaît, laissant place à des débits limités uniquement par les contraintes matérielles, telles que :

  • La capacité de la carte réseau,
  • La performance du réseau LAN,
  • La vitesse de lecture-écriture sur le disque dur.

Dans des tests effectués sur un MacBook Pro de 2022, par exemple, nous avons atteint des vitesses de transfert de l’ordre de 5800 Mbps, soit un taux environ 362,5 fois plus rapide que ce que nous avons pu réaliser avec notre serveur TCP bidirectionnel.

Tests de débit de transfert de données en local sur un MacBook Pro 2022 avec un serveur TCP unidirectionnel

Tests de débit de transfert de données en local depuis un MacBook Pro 2022 vers un iPhone avec un serveur TCP unidirectionnel

Solutions proposées

Chacune des problématiques que nous avons rencontrées au cours de notre processus de recherche nous a plongés au cœur de nombreuses incertitudes exigeant des solutions ingénieuses et réfléchies. Chaque défi que nous avons abordé était accompagné de questions sans réponses immédiates, nécessitant des analyses approfondies, des essais méthodiques et une dose considérable de créativité pour trouver des solutions.

Ces incertitudes ne représentaient pas simplement des obstacles à franchir, mais plutôt des opportunités de pousser nos limites et de repousser les frontières de notre compréhension technologique. Chaque question en suspens a servi de point de départ pour une exploration minutieuse, avec l'objectif constant de parvenir à des solutions solides et pérennes.

Tout au long de ce processus, nous avons découvert que l'incertitude était une compagne inévitable du développement technologique. Cependant, elle ne nous a jamais dissuadés. Au contraire, elle a nourri notre détermination à apprendre, à expérimenter et à trouver des voies novatrices pour surmonter les défis qui se présentaient à nous. Chaque fois que nous avons identifié une incertitude, nous l'avons considérée comme une occasion de grandir, d'affiner nos compétences et d'évoluer en tant que chercheurs, développeurs et acteurs de la communauté de développeurs Actionscript.

En fin de compte, c'est cette confrontation constante avec l'incertitude qui a enrichi notre parcours de développement, façonnant chaque étape de notre travail. Nous avons appris à la transformer en un moteur de créativité et à la transformer en une force motrice pour atteindre des solutions élégantes et efficaces.

Différences de gestion des buffers entre les plateformes

À la suite de nos expérimentations approfondies, il est devenu clair que nous étions confrontés à un défi crucial : la gestion optimale des buffers pour instaurer un système uniforme et cohérent à travers toutes les plateformes.

Cette quête pour l'uniformité s'est révélée être une étape de grande importance, compte tenu de la diversité des systèmes d'exploitation et des environnements dans lesquels notre application serait déployée. La nécessité de parvenir à une expérience utilisateur homogène et sans faille, quel que soit le dispositif utilisé, a alimenté notre recherche de solutions ingénieuses pour gérer la communication et les flux de données de manière fluide.

Après mûre réflexion et une évaluation minutieuse des options disponibles, nous avons pris des directions spécifiques pour différentes plateformes, en tenant compte de leurs particularités et de leurs besoins. Pour les environnements Windows et macOS, notre choix s'est orienté vers une approche où l'écoute d'événements tels que ProgressEvent.SOCKET_DATA a pris une place centrale. Cette méthode s'est avérée particulièrement adaptée pour ces plateformes, offrant une manière réactive de détecter la disponibilité des données pour la lecture.

Cependant, la route prise pour les plateformes iOS et Android a été guidée par une autre considération. Conscients des défis uniques posés par ces systèmes, nous avons opté pour une approche plus ciblée en déterminant une taille de buffer de 32 ko comme étant le compromis le plus efficace. Cette décision résulte de nos efforts pour équilibrer la performance, la stabilité et la consommation de ressources sur ces dispositifs mobiles, en visant à assurer une transmission de données fluide et une expérience utilisateur agréable.

État Wake on/off sur mobile

Suite à une série exhaustive de tests, marquée par une volonté de garantir un comportement cohérent et uniforme sur toutes les plateformes et systèmes d'exploitation, nous avons abouti à une conclusion sans équivoque : il était impératif d'envisager l'arrêt des transmissions et une réinitialisation des connexions au serveur. Cette décision a été façonnée par une évaluation minutieuse des performances, de la stabilité et de l'expérience utilisateur, et elle vise à préserver une expérience de communication sans faille, quelle que soit la plateforme utilisée. Cette approche repose sur la reconnaissance que, malgré la diversité des systèmes d'exploitation et des environnements, un mécanisme uniforme et synchronisé pour la cessation des transmissions est essentiel pour éviter les incohérences de données et les erreurs potentielles. En réinitialisant les connexions au serveur, nous pouvons garantir une base propre à partir de laquelle les données peuvent être transmises de manière fiable sans que des artefacts indésirables ne s'accumulent et ne compromettent l'intégrité des informations échangées.

Cette approche n'est pas simplement une réponse technique, mais aussi une stratégie réfléchie pour offrir une expérience utilisateur harmonieuse. En faisant en sorte que chaque utilisateur, quel que soit son système d'exploitation ou sa plateforme, puisse s'appuyer sur un processus de transmission de données uniforme et cohérent, nous veillons à ce que le serveur et la gestion des données atteignent leur objectif d'efficacité et de fiabilité dans un environnement numérique de plus en plus hétérogène.

Gestion des ressources système CPU / mémoire

En raison de la multitude de disparités techniques qui caractérisent les différentes plateformes, nous avons été confrontés à la nécessité impérieuse d'optimiser avec minutie la manière dont les données étaient gérées. Cette optimisation a été le fruit d'une démarche réfléchie visant à atténuer la pression exercée sur le CPU et la mémoire, deux ressources essentielles au cœur du fonctionnement harmonieux de tout système informatique. Pour atteindre cet objectif, nous avons élaboré et mis en œuvre des méthodes de traitement des données d'une simplicité remarquable, tout en garantissant leur efficacité optimale grâce à des fonctionnalités récursives.

L'une des clés de cette optimisation réside dans la conception de fonctions récursives de traitement des données. Plutôt que de recourir à des approches complexes et gourmandes en ressources, nous avons privilégié des itérations itératives récursives qui permettaient un traitement de données pas à pas, sans générer de surcharge excessive pour le CPU ou la mémoire. En procédant ainsi, nous avons su exploiter les mécanismes internes de chaque plateforme pour gérer les données de manière progressive et efficace.

De plus, l'accent a été mis sur la simplicité des processus de gestion des données. Plutôt que de surcharger les systèmes avec des opérations complexes et redondantes, nous avons opté pour des procédures simplifiées qui évitaient tout gaspillage de ressources. Cette approche a permis de minimiser les risques d'engorgement du CPU et de la mémoire, tout en assurant un traitement rapide et fluide des données.

Voici l’exemple de fonction récursive utilisée pour la gestion des données :

function socketDataHandler(event:ProgressEvent):void {
var clientSocket:Socket = Socket(event.target);
var ba:uint = clientSocket.bytesAvailable;
var str:String = clientSocket.readUTFBytes(ba);
_str_buffer += str;
extractStrFromSocketData();

function extractStrFromSocketData():void {
var datas:Array = Tools.splitString(_str_buffer, ",");
_str_length = uint(datas[0]);
_data_length = String(_str_length).length + 1 + _str_length;

if (_str_buffer.length >= _data_length) {
var st_full:String = String(datas[1]).substr(0, _str_length);
var instruction:Array = Tools.splitString(st_full, Const.SEPARATOR1);
var message:Array = Tools.splitString(instruction[1], Const.SEPARATOR1);

switch (instruction[0]) {
case "sendAll" :
sendAll(instruction[1]);
break;
case "sendOTO" :
sendOTO(message[0], message[1]);
break;
case "sendToApprenants" :
sendToApprenants(message[0], message[1]);
break;
}

var new_str_buffer:String = _str_buffer.substr(_data_length);
_str_buffer = new_str_buffer;
if (_str_buffer.length > 0) {
extractStrFromSocketData();
}
}
if (_str_buffer.length == 0) {
_data_length = 0;
_str_length = 0;
}
}
}

Hétérogénéité de la persistance des sockets

Pour résoudre ce problème, nous avons élaboré une routine de vérification spécifique. Cette routine sonde à intervalles réguliers la disponibilité du port réseau précédemment utilisé par le serveur Socket. L'objectif est de s'assurer que le port est entièrement libéré et prêt à être réutilisé avant toute nouvelle tentative de création du serveur Socket. Cette mesure préventive élimine efficacement le risque d'erreurs ou de conflits qui pourraient autrement survenir.

Il est important de noter que cette problématique, bien qu'essentielle pour le bon fonctionnement de notre architecture, n'était documentée nulle part. Ainsi, nous avons dû nous engager dans une série d'expérimentations empiriques pour non seulement identifier la cause profonde du problème, mais également pour concevoir et tester notre solution. Après avoir validé son efficacité, nous avons partagé nos découvertes et notre méthode avec la communauté, contribuant ainsi à enrichir le corpus de connaissances collectives sur ce sujet spécifique.

Ce travail sur la persistance des sockets illustre bien la nature complexe et inexplorée de certains aspects de la gestion de données multi-utilisateurs via des sockets TCP, et souligne l'importance de notre démarche de recherche et de développement pour surmonter ces défis.

Débits limités dans les flux full duplex (bidirectionnel)

Confrontés à des limitations inexplicables de débit dans notre serveur TCP bidirectionnel, nous avons été obligés de repenser entièrement notre architecture de transfert de données. Dans cette quête pour une solution optimale, nous avons élaboré une approche unique qui n'avait jamais été appliquée dans aucun autre projet de développement en ActionScript. Cette stratégie se décompose en deux éléments fondamentaux : une "couche de pilotage" (driving layer) et une "couche de données" (data layer).

Couche de Pilotage

 

Dans cette configuration, notre serveur TCP bidirectionnel assume désormais un rôle plus limité mais tout aussi crucial : celui de faire transiter des messages de contrôle de taille réduite, généralement de quelques bits. Ces messages servent essentiellement à coordonner les actions entre les utilisateurs. Par exemple, ils peuvent informer un utilisateur qu'il doit se préparer à recevoir un ensemble de données via la couche de données. Cette méthode minimise le trafic sur le serveur TCP bidirectionnel, évitant ainsi d'atteindre les limitations de débit qui entravent notre système.

Couche de Données

 

C'est ici que réside la véritable innovation de notre approche : au lieu de tenter de faire transiter toutes les données via un serveur TCP bidirectionnel, nous avons créé un serveur TCP unidirectionnel local pour chaque utilisateur. Ce serveur permet aux autres utilisateurs de télécharger des données directement en utilisant le protocole HTTP. Cette division des tâches entre les deux couches permet non seulement de contourner les limitations de débit inhérentes aux serveurs TCP bidirectionnels, mais également de maximiser l'efficacité de chaque couche pour sa tâche désignée.

Implications et Contributions

 

Cette architecture à deux niveaux s'est avérée être une avancée significative pour notre projet. Non seulement elle a résolu les problèmes de limitation de débit que nous avions rencontrés, mais elle a également ouvert la porte à de nouvelles manières de concevoir des systèmes de transfert de données en réseau. C'est une contribution précieuse à la fois pour notre équipe et pour la communauté ActionScript, qui peut maintenant envisager des alternatives plus flexibles et efficaces pour gérer les transferts de données en environnements réseau hétérogènes.

Comparaison avec d’autres approches

Notre première stratégie pour la gestion des communications réseau dans ce projet a été de développer un serveur TCP en utilisant le langage de programmation C. L'idée était de créer un serveur sous forme d'une application démon (daemon), qui fonctionnerait en arrière-plan et permettrait à tous les utilisateurs de se connecter pour communiquer entre eux.

Toutefois, cette approche a rapidement rencontré des obstacles. L'un des problèmes les plus sérieux était une latence perceptible dans les 'ticks' internes du programme. Cette latence semblait résulter d'une limitation intrinsèque au langage de programmation C lui-même, et malgré nos meilleurs efforts pour l'identifier et la corriger, il s'est avéré impossible de résoudre ce problème de manière satisfaisante.

Après une série de tests et d'analyses approfondies, il est devenu clair que cette première approche ne pourrait pas répondre aux exigences de performance et de fiabilité que nous avions fixées pour le projet.

En outre, l'idée de gérer un exécutable externe ou une application tierce en parallèle avec l'application principale, responsable en dernier ressort de la gestion des données, s'est avérée incompatible avec notre objectif. Nous cherchions à développer une solution intégrée, complète et autonome, capable de fonctionner de manière uniforme sur différentes plateformes. Le recours à des composants externes aurait compromis cette vision d'une solution tout-en-un et multiplateforme.

En conséquence, nous avons pris la décision nécessaire d'abandonner cette méthode au profit de solutions alternatives. Le constat d'échec a été d'autant plus frustrant que nous avions investi des ressources significatives dans cette voie, mais il était évident que persister dans cette direction ne serait pas bénéfique pour l'atteinte des objectifs du projet.

Cette expérience, bien que décevante, a été instructive. Elle nous a forcés à repenser notre approche globale et à rechercher des solutions plus innovantes pour surmonter les défis inhérents à la gestion des communications en réseau. En abandonnant cette voie, nous avons pu rediriger nos efforts vers une solutions plus prometteuse, celle de développer ce composant structurant en ActionScript, ce qui nous a permis d’arriver finalement à la mise en place d'une architecture à deux niveaux plus robuste et efficace.