Yo !
Introduction :
Après réflexion, voyant que j'améliore constamment mon serveur socket, et que ça aide quelques personnes, j'ai décidé de faire un post fixe, et je mettrais à jour mon code en fonction de l'évolution de mon petit serveur, j'essayerais de commenter mon code au maximum notez bien que presque toutes les explication sont sur la partie serveur, car il y à plus de notions que sur le client.
Fonctionnalités :
* Choix de l'addresse IP et Port & nombre de connexion (bêta)
* Algorithme de Nagle intégré
* Envoie / Réception de données Client >> Serveur, Server >> Client
* Utilisation rapide (double clique sur les textBox du serveur pour copier double clique sur textBox du client pour coller)
Le code :
Server :
using System;
using System.Data;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SocketServer
{
public partial class ServerForm : Form
{
private bool serverIsStarted;
private Socket socket; private Socket socketAccept; private IPEndPoint iep;
public ServerForm()
{
InitializeComponent();
}
/* *\
* -----EVENEMENTS----- *
\* */
private void ServerForm_Load(object sender, EventArgs e)
{
DefaultConnexionInfos();
}
private void btnStart_Click(object sender, EventArgs e)
{
ServerStart();
ServerRead();
serverIsStarted = true;
tbServerIp.Clear(); tbServerPort.Clear();
serverClose();
}
private void btnSend_Click(object sender, EventArgs e)
{
ServerSend(tbMsg.Text);
tbMsg.Clear();
}
private void tbServerIp_MouseDoubleClick(object sender, MouseEventArgs e)
{
tbServerIp.Copy();
}
private void tbServerPort_DoubleClick(object sender, EventArgs e)
{
tbServerPort.Copy();
}
private void tbSend_KeyPress(object sender, KeyPressEventArgs e)
{
// Quand une touche est préssée si cette touche == "ENTER(13)" alors envoie du msg et gestion de
// l'event (pour retirer le bruit chiant de windows).
if (e.KeyChar == (char)13) { ServerSend(tbMsg.Text); tbMsg.Clear(); e.Handled = true; }
}
/* *\
* -----SOCKET----- *
\* */
public void DefaultConnexionInfos()
{
// Obtention de l'addresse IP de la machine où s'execute le programme.
IPHostEntry ihe = Dns.Resolve(Dns.GetHostName());
IPAddress serverIp = ihe.AddressList[0];
tbServerIp.Text = serverIp.ToString(); tbServerPort.Text = "8000";
}
// Prépare le serveur à aceuillir un client
public void ServerStart()
{
string serverIp = tbServerIp.Text;
string serverPort = tbServerPort.Text;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Un IEP est comme un adresse ou client et serveur se rencontre pour établir une connexion.
iep = new IPEndPoint(IPAddress.Parse(serverIp), Int32.Parse(serverPort)); // "Parse" == Convertion.
// On branche notre socket sur cette adresse et on le prépare à recevoir la demande de co. d'un
// client.
socket.Bind(iep);
socket.Listen(Int32.Parse(nudMaxConnections.Text));
// SocketAccept == un socket unique qui permet de communiquer entre le serv/client grâce aux
// infos préalablements données.
socketAccept = socket.Accept();
}
public void ServerSend(string msg)
{
byte[] msgInOctect = Encoding.Default.GetBytes(msg);
// On envoie en premier la taille (on octets) du message principal à envoyer (en octets).
socketAccept.Send(BitConverter.GetBytes(msgInOctect.Length), 0, 4, 0);
// Puis le message principal (toujours en octets ^^).
socketAccept.Send(msgInOctect, 0, msgInOctect.Length, 0);
}
/* ---------- EXPLICATION DE LA FONCTION READ ----------
Cette fonction n'est vraiment pas simple à comprendre du premier coup d'oeil, vous devez garder en tête
que pour afficher n'importe quelle information avec cette fonction vous aurez besoin de deux sockets,
un PRINCIPAL qui contient l'information et un SECONDAIRE qui contient la taille du PRINCIPAL.
Pourquoi se compliquer la vie à faire ça alors que l'on pourrait juste envoyer le socket PRINCIPAL seul
et le Resize() ? Bonne question, la taille maximum d'envoie est de 255 octets, et si vous shouaitez
envoyer plus d'informations ? (vous avez pas le choix ^^), vous devez découper votre information en
plusieurs bouts de 255 octets, et pour savoir combien de bouts vous avez besoin, il vous faut le socket
SECONDAIRE qui contient la taille !
Pour que ce soit plus clair, voyez La taille totale de l'information que vous voulez envoyer comme une
barquette de glace, l'information que vous voulez envoyer comme de la glace, et la taille maxime d'envoie
comme un cuillère. Vous avez besoin de prendre de la glace plusieurs foix (infos totale) avec votre
cuillère (taille max d'envoie), donc il faudras s'y prendre plusieurs foix !
Je sais que ça peut être difficil à comprendre donc si vous avez besoin d'aide / des questions, MP moi.
---------- EXPLICATION DE LA FONCTION READ ---------- */
public void ServerRead()
{
new Thread(() => // Le thread permet l'éxecution d'un bloc de code en continue.
{
while (true)
{
byte[] bufferSizeInBytes = new byte[4];
socketAccept.Receive(bufferSizeInBytes, 0, bufferSizeInBytes.Length, 0); // == taille du prochain socket en octets.
int bufferSize = BitConverter.ToInt32(bufferSizeInBytes, 0); // == taille du prochain socket en nombre entier.
MemoryStream ms = new MemoryStream(); // Permet d'écrire des infos dans un petit coin.
while (bufferSize > 0)
{
byte[] buffer;
if (bufferSize < socketAccept.ReceiveBufferSize) // == Tant que la taille du socket est plus petit que la taille max autorisé.
{
buffer = new byte[bufferSize]; // Si oui, on crée un tableau d'octect (de la taille du socket), pour stocker le socket
}
else { buffer = new byte[socketAccept.ReceiveBufferSize]; } // Sinon, on crée un tableau d'octect de la taille maximum autorisé.
int receivedSocket = socketAccept.Receive(buffer, 0, buffer.Length, 0); // On reçois enfin notre socket principal (ou une partie), dans un tableau de sa taille.
bufferSize -= receivedSocket; // Et biensur, on soustrait la taille des infos du socket principal qu'on à reçus à la taille du socket principal.
ms.Write(buffer, 0, buffer.Length);
}
ms.Close();
byte[] data = ms.ToArray(); // == On copie toute l'info accumulé durant la boucle dans "data".
ms.Dispose(); // On supprime notre MS.
Invoke((MethodInvoker)(delegate
{
rtbChat.AppendText(Encoding.Default.GetString(data) + "\n"); // Conversion & affichage.
}));
}
}).Start();
}
public void serverClose()
{
// Comme on est polis, on ferme bien tout ça.
socket.Close();
}
}
}
Client :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Client
{
public partial class ClientForm : Form
{
private Socket socket;
public ClientForm()
{
InitializeComponent();
}
/* *\
* -----EVENEMENTS----- *
\* */
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnConnection_Click(object sender, EventArgs e)
{
Connection();
Read();
}
private void btnSend_Click(object sender, EventArgs e)
{
Send(tbMsg.Text);
}
private void tbMsg_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)13) { Send(tbMsg.Text); e.Handled = true; }
}
private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
{
Send("Client déconnecté. \n");
}
private void tbServerIp_DoubleClick(object sender, EventArgs e)
{
tbServerIp.Paste();
}
private void tbServerPort_DoubleClick(object sender, EventArgs e)
{
tbServerPort.Paste();
}
/* *\
* -----SOCKET----- *
\* */
// Pour l'explication des fonction "Connection / ServerStart", "Send", et "Read", voir 'ServerForm.cs'.
public void Connection()
{
string ServerIp = tbServerIp.Text;
string serverPort = tbServerPort.Text;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ServerIp), Int32.Parse(serverPort));
socket.Connect(iep);
}
public void Send(string msg)
{
tbMsg.Clear();
byte[] buffer;
buffer = Encoding.Default.GetBytes(msg);
socket.Send(BitConverter.GetBytes(buffer.Length), 0, 4, 0);
socket.Send(buffer, 0, buffer.Length, 0);
}
public void Read()
{
new Thread(() =>
{
while (true)
{
byte[] bufferSize = new byte[4];
socket.Receive(bufferSize, 0, bufferSize.Length, 0);
int size = BitConverter.ToInt32(bufferSize, 0);
MemoryStream ms = new MemoryStream();
while (size > 0)
{
byte[] buffer;
if (size < socket.ReceiveBufferSize)
{
buffer = new byte;
}
else { buffer = new byte[socket.ReceiveBufferSize]; }
int rec = socket.Receive(buffer, 0, buffer.Length, 0);
size -= rec;
ms.Write(buffer, 0, buffer.Length);
}
ms.Close();
byte[] data = ms.ToArray();
ms.Dispose();
Invoke((MethodInvoker)(delegate
{
rtbChat.AppendText(Encoding.Default.GetString(data) + "\n");
}));
}
}).Start();
}
}
}
Téléchargement :
Client V.1 : http://www.megafileupload.com/sjvU/Client.exe
Serveur V.1 : http://www.megafileupload.com/sjvV/SocketServer.exe