En TCP, on établit une connexion entre deux machines. En général, le client fait la demande de connexion que le serveur accepte. Une fois la connexion établie, le client et le serveur peuvent lire et écrire des données.
La connexion a deux canaux :
On peut fermer l'un de ces canaux sans fermer la connexion.
Fiabilité : Le protocole TCP garantit qu'aucune donnée n'est perdue.
Intégrité : Le protocole TCP garantit que les données arrivent dans l'ordre où elles ont été envoyées et qu'elles ne sont pas modifiées.
Non préservation des limites : Le protocole TCP ne garantit pas que les données arrivent en une seule fois.
Si on écrit 100 octets sur une connexion TCP, on pourra recevoir 40 octets puis 60 octets. On pourra aussi recevoir 100 octets. Si on écrit 50 octets puis 50 octets, le serveur pourra recevoir 100 octets en une seule fois.
En général, plusieurs clients se connectent à un unique serveur.
Comme en UDP, le point d'attachement côté client comme côté serveur se fait par des sockets, liées à une adresse IP et un numéro de port.
Pour l'instant, nous ne considérons que le cas des clients.
La
SocketChannel
.
Une SocketChannel sc
est créée grâce à la méthode factory SocketChannel.open()
. Elle n'est pas connectée.
La connexion à un serveur se fait par l'appel à la méthode sc.connect(SocketAddress)
.
// creation of the socket (not connected) SocketChannel sc = SocketChannel.open(); var serverAddress = new InetSocketAddress("www.google.com", 80); // connection to server sc.connect(serverAddress);
SocketChannel
Une fois la SocketChannel sc
connectée, il est possible d'envoyer ou de recevoir des octets vers ou depuis le serveur.
sc.read(ByteBuffer buffer)
lit des données du serveur et les stocke dans buffer
.
sc.write(ByteBuffer buffer)
écrit les octets de buffer
vers le serveur.
Les méthodes sont les mêmes que pour la classe FileChannel.
Par défaut, ces méthodes (read
et write
) sont bloquantes.
La méthode read
termine quand au moins 1 octet a été lu ou que la fermeture de la connexion est détectée. La méthode write
termine quand toute la zone de travail a été écrite.
SocketChannel
(1/2)
La lecture se fait avec la méthode read(ByteBuffer buffer)
.
Les données lues sont écrites dans la zone de travail de
buffer
(au plus buffer.remaining()
).
Attention : la zone de travail de buffer
ne vas pas forcément être remplie intégralement !
S'il y a plus de données que la taille de buffer
,
elles ne sont pas perdues et seront lues au prochain read
.
La méthode read
renvoie le
nombre d'octets lus ou -1 si la connexion a été fermée en écriture
par l'interlocuteur.
On peut encore lui écrire mais on ne
recevra plus jamais rien.
SocketChannel
(2/2)
var buffer = ByteBuffer.allocate(BUFFER_SIZE); var read = sc.read(buffer); if (read == -1){ System.out.println("Connection closed for reading"); } else { System.out.println("Read "+ read +" bytes"); }
SocketChannel
L'écriture se fait avec la méthode write(ByteBuffer buffer)
.
Les données de la zone de travail de buffer
sont intégralement écrites. Par défaut, la méthode bloque jusqu'à
la fin de l'écriture.
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); buffer.putLong(1l); buffer.putLong(2l); buffer.flip(); sc.write(buffer);
On peut fermer...
shutdownOutput()
. L'interlocuteur est
notifié (son read
renvoie -1).
shutdownInput()
. Opération purement locale :
l'interlocuteur n'est pas notifié.
close()
.Quand on reçoit des données, a priori, on ne peut pas :
C'est le rôle du protocole ! Il y a trois solutions usuelles :
Si on a un seul bloc de données à envoyer, on peut l'écrire puis fermer la connexion en écriture. L'interlocuteur saura qu'il n'y a plus de données quand read renverra -1 (c'est ce que fait HTTP 1.0).
Problèmes :
Si on a plusieurs données à envoyer, comme par exemple plusieurs chaînes encodées en ASCII, on peut convenir d'une suite d'octets marquant la fin de chaque chaîne (par exemple \r\n dans les headers HTTP).
Problèmes :
On peut écrire la taille (par exemple avec un long
) des
données avant de les envoyer.
Problème :
(c'est le principe des chunks en HTTP/1.1)