Image créée par rawpixel.comDans cet article, nous allons descendre dans la couche TCP et découvrir les sockets et les outils de Core Foundation en développant une application de chat.
Temps de lecture estimé: 25 minutes.
Pourquoi des sockets?
Vous vous demandez peut-ĂȘtre: "Pourquoi devrais-je aller un niveau plus bas que
URLSession ?" Si vous ĂȘtes assez intelligent et ne posez pas cette question, passez directement Ă la section suivante.
La réponse pour pas si intelligentGrande question! Le fait est que l'utilisation de URLSession est basée sur le
protocole HTTP , c'est-Ă -dire que la communication se produit dans le style de
demande-réponse , approximativement comme suit:
- demander au serveur des données au format JSON
- obtenir ces données, traiter, afficher, etc.
Mais que se passe-t-il si nous avons besoin d'un serveur de sa propre initiative pour transférer des données vers votre application? Ici, HTTP est sans travail.
Bien sûr, nous pouvons tirer en permanence le serveur et voir s'il y a des données pour nous (aka
polling ). Ou nous pouvons ĂȘtre plus sophistiquĂ©s et utiliser
des sondages longs . Mais toutes ces béquilles sont légÚrement inappropriées dans ce cas.
AprÚs tout, pourquoi vous limiter au paradigme demande-réponse s'il correspond un peu moins à rien à notre tùche?
Dans ce guide, vous apprendrez à plonger à un niveau d'abstraction inférieur et à utiliser directement
SOCKETS dans l'application de chat.
Au lieu de vérifier le serveur pour les nouveaux messages, notre application utilisera des flux qui restent ouverts pendant la session de chat.
Pour commencer
Téléchargez le
matériel source . Il existe une application client fictive et un serveur simple écrit en
Go .
Vous n'avez pas besoin d'écrire dans Go, mais vous devrez exécuter l'application serveur pour que les applications clientes puissent s'y connecter.
Lancer l'application serveur
Les matĂ©riaux source ont Ă la fois une application compilĂ©e et une source. Si vous avez une paranoĂŻa saine et que vous ne faites pas confiance au code compilĂ© par quelqu'un d'autre, vous pouvez compiler le code source vous-mĂȘme.
Si vous ĂȘtes courageux, ouvrez
Terminal , allez dans le répertoire avec les documents téléchargés et exécutez la commande:
sudo ./server
Lorsque vous y ĂȘtes invitĂ©, entrez votre mot de passe. AprĂšs cela, vous devriez voir un message
Ăcoute sur 127.0.0.1:80.Remarque: l'application serveur dĂ©marre en mode privilĂ©giĂ© (la commande «sudo») car elle Ă©coute sur le port 80. Tous les ports avec des nombres infĂ©rieurs Ă 1024 nĂ©cessitent un accĂšs spĂ©cial.
Votre serveur de chat est prĂȘt! Vous pouvez passer Ă la section suivante.
Si vous souhaitez compiler vous-mĂȘme le code source du serveur,dans ce cas, vous devez installer
Go Ă l' aide de
Homebrew .
Si vous n'avez pas Homebrew, vous devez d'abord l'installer. Ouvrez Terminal et collez-y la ligne suivante:
/usr/bin/ruby -e \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Utilisez ensuite cette commande pour installer Go:
brew install go
à la fin, allez dans le répertoire avec les matériaux source téléchargés et compilez le code source de l'application serveur:
go build server.go
Enfin, vous pouvez démarrer le serveur avec la commande
au début de cette section .
Nous regardons ce que nous avons chez le client
Ouvrez maintenant le projet
DogeChat , compilez-le et voyez ce qu'il y a.

Comme vous pouvez le voir, DogeChat vous permet dĂ©sormais de saisir un nom d'utilisateur et d'accĂ©der Ă la section de chat elle-mĂȘme.
Il semble que le développeur de ce projet ne savait pas comment faire un chat. Donc, tout ce que nous avons est une interface utilisateur et une navigation de base. Nous allons écrire une couche réseau. Hourra!
Créer une salle de chat
Pour accĂ©der directement au dĂ©veloppement, accĂ©dez Ă
ChatRoomViewController.swift . Il s'agit d'un contrÎleur de vue qui peut recevoir du texte saisi par l'utilisateur et afficher les messages reçus dans une vue de table.
Comme nous avons un
ChatRoomViewController , il est logique de développer une classe
ChatRoom qui fera tout le travail.
Réfléchissons à ce que la nouvelle classe offrira:
- ouvrir une connexion Ă l'application serveur;
- connecter un utilisateur avec le nom spécifié par lui au chat;
- envoyer et recevoir des messages;
- fermeture de la connexion Ă la fin.
Maintenant que nous savons ce que nous voulons de cette classe, appuyez sur
Commande-N , sélectionnez
Fichier Swift et appelez-le
ChatRoom .
Création de flux d'E / S
Remplacez le contenu de
ChatRoom.swift par ceci:
import UIKit class ChatRoom: NSObject {
Ici, nous définissons la classe
ChatRoom et déclarons les propriétés dont nous avons besoin.
- Nous définissons d'abord les flux d'entrée / sortie. Les utiliser en paire nous permettra de créer une connexion socket entre l'application et le serveur de chat. Bien sûr, nous enverrons des messages en utilisant le flux de sortie et recevrons en utilisant le flux d'entrée.
- Ensuite, nous définissons le nom d'utilisateur.
- Et enfin, nous définissons la variable maxReadLength, qui limite la longueur maximale d'un seul message.
Accédez maintenant au fichier
ChatRoomViewController.swift et ajoutez cette ligne à la liste de ses propriétés:
let chatRoom = ChatRoom()
Maintenant que nous avons créé la structure de base de la classe, il est temps de faire la premiÚre des tùches prévues: ouvrir la connexion entre l'application et le serveur.
Connexion ouverte
Nous revenons à ChatRoom.swift et ajoutons cette méthode pour les définitions de propriétés:
func setupNetworkCommunication() {
Voici ce que nous faisons ici:
- nous définissons d'abord deux variables pour les flux de socket sans utiliser la gestion automatique de la mémoire
- puis nous, en utilisant ces mĂȘmes variables, crĂ©ons directement des flux qui sont liĂ©s Ă l'hĂŽte et au numĂ©ro de port.
La fonction a quatre arguments. Le premier est le type d'allocateur de mémoire que nous utiliserons lors de l'initialisation des threads. Vous devez utiliser
kCFAllocatorDefault , bien qu'il existe d'autres options possibles dans le cas oĂč vous souhaitez modifier le comportement des threads.
Note du traducteurLa documentation de la fonction CFStreamCreatePairWithSocketToHost indique: utilisez NULL ou kCFAllocatorDefault . Et la description de kCFAllocatorDefault indique qu'il s'agit d'un synonyme de NULL . Le cercle est fermé!
Ensuite, nous définissons le nom d'hÎte. Dans notre cas, nous nous connectons au serveur local. Si votre serveur est situé à un autre endroit, vous pouvez définir son adresse IP.
Ensuite, le numéro de port que le serveur écoute.
Enfin, nous transmettons des pointeurs à nos flux d'E / S afin que la fonction puisse les initialiser et les connecter aux flux qu'elle crée.
Maintenant que nous avons les flux initialisés, nous pouvons enregistrer des liens vers eux en ajoutant ces lignes à la fin de la méthode
setupNetworkCommunication () :
inputStream = readStream!.takeRetainedValue() outputStream = writeStream!.takeRetainedValue()
L'utilisation de
takeRetainedValue () appliquĂ©e Ă un objet non gĂ©rĂ© nous permet de conserver une rĂ©fĂ©rence Ă celui-ci et, en mĂȘme temps, d'Ă©viter de futures fuites de mĂ©moire. Maintenant, nous pouvons utiliser nos fils oĂč nous voulons.
Nous devons maintenant ajouter ces threads Ă la
boucle d'exécution afin que notre application traite correctement les événements réseau. Pour ce faire, ajoutez ces deux lignes à la fin de
setupNetworkCommunication () :
inputStream.schedule(in: .current, forMode: .common) outputStream.schedule(in: .current, forMode: .common)
Il est enfin temps de naviguer! Pour commencer, ajoutez ceci à la toute fin de la méthode
setupNetworkCommunication () :
inputStream.open() outputStream.open()
Nous avons maintenant une connexion ouverte entre notre application client et serveur.
Nous pouvons compiler et exĂ©cuter notre application, mais vous ne verrez pas encore de changements, car mĂȘme si nous ne faisons rien avec notre connexion client-serveur.
Connectez-vous au chat
Maintenant que nous avons une connexion établie avec le serveur, il est temps de commencer à faire quelque chose! Dans le cas du chat, vous devez d'abord vous présenter, puis envoyer des messages aux interlocuteurs.
Cela nous amĂšne Ă une conclusion importante: comme nous avons deux types de messages, nous devons en quelque sorte les distinguer.
Protocole de chat
L'un des avantages de l'utilisation de la couche TCP est que nous pouvons définir notre propre «protocole» de communication.
Si nous utilisions HTTP, nous aurions besoin d'utiliser ces différents mots
GET ,
PUT ,
PATCH . Nous aurions besoin de former des URL et d'utiliser les bons en-tĂȘtes et tout cela.
Nous n'avons que deux types de messages. Nous enverrons
iam:Luke
pour entrer dans le chat et vous présenter.
Et nous enverrons
msg:Hey, how goes it, man?
pour envoyer un message de discussion à tous les répondants.
C'est trÚs simple, mais absolument sans principes, alors n'utilisez pas cette méthode dans les projets critiques.
Nous savons maintenant ce que notre serveur attend et nous pouvons écrire une méthode dans la classe
ChatRoom qui permettra Ă l'utilisateur de se connecter au chat. Le seul argument est le surnom de l'utilisateur.
Ajoutez cette méthode dans
ChatRoom.swift :
func joinChat(username: String) {
- D'abord, nous formons notre message en utilisant notre propre «protocole»
- Enregistrez le nom pour référence future.
- withUnsafeBytes (_ :) fournit un moyen pratique de travailler avec un pointeur non sécurisé à l'intérieur d'une fermeture.
- Enfin, nous envoyons notre message au flux de sortie. Cela peut sembler plus compliqué que ce à quoi vous pourriez vous attendre, mais écrire (_: maxLength :) utilise le pointeur non sécurisé créé à l'étape précédente.
Maintenant, notre mĂ©thode est prĂȘte, ouvrez
ChatRoomViewController.swift et ajoutez un appel à cette méthode à la fin de
viewWillAppear (_ :) .
chatRoom.joinChat(username: username)
Maintenant, compilez et exécutez l'application. Entrez votre surnom et appuyez sur Retour pour voir ...

...
que rien n'a encore changĂ©!Attendez, ça va! AccĂ©dez Ă la fenĂȘtre du terminal. LĂ , vous verrez le message que
Vasya a rejoint ou quelque chose comme ça si votre nom n'est pas Vasya.
C'est trÚs bien, mais ce serait bien d'avoir une indication d'une connexion réussie sur l'écran de votre téléphone.
Répondre aux messages entrants
Le serveur envoie des messages de jonction client à tous ceux qui sont dans le chat, y compris vous. Heureusement, notre application a déjà tout pour afficher les messages entrants sous forme de cellules dans le tableau des messages dans
ChatRoomViewController .
Tout ce que vous avez Ă faire est d'utiliser
inputStream pour «intercepter» ces messages, les convertir en instances de la classe
Message et les transmettre Ă la table pour affichage.
Pour pouvoir répondre aux messages entrants, vous avez besoin de
ChatRoom pour se
conformer au protocole
StreamDelegate .
Pour ce faire, ajoutez cette extension au bas du fichier
ChatRoom.swift :
extension ChatRoom: StreamDelegate { }
Déclarez maintenant qui deviendra délégué à inputStream.
Ajoutez cette ligne à la méthode setupNetworkCommunication () juste avant les appels à planifier (dans: forMode :):
inputStream.delegate = self
Ajoutez maintenant l'implémentation de la méthode stream (_: handle :) à l'extension:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) { switch eventCode { case .hasBytesAvailable: print("new message received") case .endEncountered: print("The end of the stream has been reached.") case .errorOccurred: print("error occurred") case .hasSpaceAvailable: print("has space available") default: print("some other event...") } }
Nous traitons les messages entrants
Nous sommes donc prĂȘts Ă commencer Ă traiter les messages entrants. L'Ă©vĂ©nement qui nous intĂ©resse est
.hasBytesAvailable , qui indique qu'un message entrant est arrivé.
Nous allons écrire une méthode qui traite ces messages. Sous la méthode nouvellement ajoutée, nous écrivons ce qui suit:
private func readAvailableBytes(stream: InputStream) {
- Nous définissons le tampon dans lequel nous lirons les octets entrants.
- Nous tournons en boucle, tandis que dans le flux d'entrée, il y a quelque chose à lire.
- Nous appelons read (_: maxLength :), qui lit les octets du flux et les place dans le tampon.
- Si l'appel a renvoyé une valeur négative, nous renvoyons une erreur et quittons la boucle.
Nous devons appeler cette méthode dÚs que nous avons des données dans le flux entrant, alors allez à l'
instruction switch dans la méthode
stream (_: handle :) , trouvez le commutateur
.hasBytesAvailable et appelez cette méthode immédiatement aprÚs l'instruction print:
readAvailableBytes(stream: aStream as! InputStream)
à cet endroit, nous avons un tampon préparé des données reçues!
Mais nous devons encore transformer ce tampon en contenu de la table des messages.
Placez cette méthode sur
readAvailableBytes (stream :) .
private func processedMessageString(buffer: UnsafeMutablePointer<UInt8>, length: Int) -> Message? {
Tout d'abord, nous initialisons String en utilisant le tampon et la taille que nous transmettons à cette méthode.
Le texte sera en UTF-8, Ă la fin, nous libĂ©rerons le tampon et diviserons le message par le symbole ':' pour sĂ©parer le nom de l'expĂ©diteur et le message lui-mĂȘme.
Nous analysons maintenant si ce message provient d'un autre participant. Sur le produit, vous pouvez créer quelque chose comme un jeton unique, cela suffit pour la démo.
Enfin, de toute cette économie, nous formons une instance de Message et la renvoyons.
Pour utiliser cette méthode, ajoutez le
if-let suivant Ă la fin de la
boucle while dans la
méthode readAvailableBytes (stream :) , immédiatement aprÚs le dernier commentaire:
if let message = processedMessageString(buffer: buffer, length: numberOfBytesRead) {
Maintenant, tout est prĂȘt Ă passer Ă quelqu'un
Message ... Mais Ă qui?
Créer le protocole ChatRoomDelegate
Nous devons donc informer
ChatRoomViewController.swift du nouveau message, mais nous n'avons pas de lien vers celui-ci. Puisqu'il contient un lien
ChatRoom fort, nous pouvons tomber dans le piĂšge d'un cycle de liens fort.
C'est l'endroit idéal pour créer un protocole de délégué. ChatRoom ne se soucie pas de savoir qui a besoin de connaßtre les nouveaux messages.
En haut de
ChatRoom.swift, ajoutez une nouvelle définition de protocole:
protocol ChatRoomDelegate: class { func received(message: Message) }
Maintenant à l'intérieur de la classe
ChatRoom, ajoutez un lien faible pour stocker qui deviendra le délégué:
weak var delegate: ChatRoomDelegate?
Ajoutons maintenant la
méthode readAvailableBytes (stream :) , en ajoutant la ligne suivante à l'intérieur de la construction if-let, sous le dernier commentaire de la méthode:
delegate?.received(message: message)
Revenez Ă
ChatRoomViewController.swift et ajoutez l'extension de classe suivante, qui garantit la conformité avec le protocole
ChatRoomDelegate , immédiatement aprÚs MessageInputDelegate:
extension ChatRoomViewController: ChatRoomDelegate { func received(message: Message) { insertNewMessageCell(message) } }
Le projet d'origine contient déjà le nécessaire, donc
insertNewMessageCell (_ :) acceptera votre message et affichera la cellule correcte dans la vue de table.
Affectez maintenant le contrĂŽleur de vue en tant que dĂ©lĂ©guĂ© en l'ajoutant Ă
viewWillAppear (_ :) immédiatement aprÚs avoir appelé super.viewWillAppear ()
chatRoom.delegate = self
Maintenant, compilez et exécutez l'application. Saisissez un nom et appuyez sur Retour.

Vous verrez une cellule sur votre connexion au chat. Hourra, vous avez réussi à envoyer un message au serveur et à en recevoir une réponse!
Publier des messages
Maintenant que ChatRoom peut envoyer et recevoir des messages, il est temps de donner à l'utilisateur la possibilité d'envoyer ses propres phrases.
Dans
ChatRoom.swift, ajoutez la méthode suivante à la fin de la définition de classe:
func send(message: String) { let data = "msg:\(message)".data(using: .utf8)! _ = data.withUnsafeBytes { guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) else { print("Error joining chat") return } outputStream.write(pointer, maxLength: data.count) } }
Cette mĂ©thode est similaire Ă
joinChat (username :) , que nous avons écrit plus tÎt, sauf qu'elle a le préfixe
msg devant le texte (pour indiquer qu'il s'agit d'un vrai message de discussion).
Puisque nous voulons envoyer des messages par le bouton
Envoyer , nous revenons Ă
ChatRoomViewController.swift et y trouvons
MessageInputDelegate .
Ici, nous voyons la
méthode sendWasTapped (message :) vide. Pour envoyer un message, envoyez-le à chatRoom:
chatRoom.send(message: message)
En fait, câest tout! Ătant donnĂ© que le serveur recevra le message et le transmettra Ă tout le monde, ChatRoom sera informĂ© du nouveau message de la mĂȘme maniĂšre que lors de la participation au chat.
Compilez et exécutez l'application.

Si vous n'avez personne avec qui discuter, lancez une nouvelle fenĂȘtre de terminal et entrez:
nc localhost 80
Cela vous connectera au serveur. Vous pouvez maintenant vous connecter au chat en utilisant le mĂȘme "protocole":
iam:gregg
Et donc - envoyez un message:
msg:Ay mang, wut's good?

Félicitations, vous avez écrit un client pour le chat!
Nous nous nettoyons
Si vous avez déjà développé des applications qui lisent / écrivent activement des fichiers, sachez que les bons développeurs ferment les fichiers lorsqu'ils ont fini de travailler avec eux. Le fait est que la connexion via le socket est fournie par le descripteur de fichier. Cela signifie qu'à la fin du travail, vous devez le fermer, comme tout autre fichier.
Pour ce faire, ajoutez la mĂ©thode suivante Ă
ChatRoom.swift aprÚs avoir défini
send (message :) :
func stopChatSession() { inputStream.close() outputStream.close() }
Comme vous l'avez probablement deviné, cette méthode ferme les threads afin que vous ne puissiez plus recevoir et envoyer de messages. De plus, les threads sont supprimés de la
boucle d'exécution dans laquelle nous les avons précédemment placés.
Ajoutez un appel à cette méthode dans la section
.endEncountered de l'
instruction switch à l' intérieur de
stream (_: handle :) :
stopChatSession()
Revenez ensuite Ă
ChatRoomViewController.swift et faites de mĂȘme dans
viewWillDisappear (_ :) :
chatRoom.stopChatSession()
Voilà ! Maintenant c'est sûr!
Conclusion
Maintenant que vous maßtrisez les bases de la mise en réseau avec des sockets, vous pouvez approfondir vos connaissances.
Prises UDP
Cette application est un exemple de communication réseau utilisant TCP, qui garantit la livraison des paquets à destination.
Cependant, vous pouvez utiliser des sockets UDP. Ce type de connexion ne garantit pas la livraison des colis Ă leur destination, mais il est beaucoup plus rapide.
Ceci est particuliÚrement utile dans les jeux. Avez-vous déjà connu un décalage? Cela signifiait que votre connexion était mauvaise et que de nombreux paquets UDP étaient perdus.
Websockets
Une autre alternative au HTTP dans les applications est une technologie appelée sockets Web.
Contrairement aux sockets TCP classiques, les sockets Web utilisent HTTP pour Ă©tablir la communication. Avec leur aide, vous pouvez obtenir la mĂȘme chose qu'avec des prises ordinaires, mais avec confort et sĂ©curitĂ©, comme dans un navigateur.