C# Language
Asynchroner Sockel
Suche…
Einführung
Durch die Verwendung von asynchronen Sockets kann ein Server auf eingehende Verbindungen warten und in der Zwischenzeit eine andere Logik ausführen, im Gegensatz zu synchronem Socket, wenn er abhört. Er blockiert den Haupt-Thread und die Anwendung reagiert nicht mehr und das Einfrieren wird erst nach dem Verbindungsaufbau eines Clients eingestellt.
Bemerkungen
Socket und Netzwerk
Zugriff auf einen Server außerhalb meines eigenen Netzwerks Dies ist eine häufige Frage, und wenn sie gefragt wird, wird sie meistens als Thema gekennzeichnet.
Serverseite
Im Netzwerk Ihres Servers müssen Sie den Router portieren, um ihn an Ihren Server weiterzuleiten.
Zum Beispiel PC, auf dem der Server läuft:
lokale IP = 192.168.1.115
Der Server hört auf Port 1234.
Leiten Sie eingehende Verbindungen am Port 1234
Router an 192.168.1.115
Client-Seite
Das einzige, was Sie ändern müssen, ist die IP. Sie möchten sich nicht mit Ihrer Loopback-Adresse verbinden, sondern mit der öffentlichen IP-Adresse des Netzwerks, auf dem Ihr Server läuft. Diese IP erhalten Sie hier .
_connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("10.10.10.10"), 1234));
Nun erstellen Sie eine Anforderung für diesen Endpunkt: 10.10.10.10:1234
Wenn Sie den Router durch den Eigenschafts-Port- 10.10.10.10:1234
wird die Verbindung zwischen Server und Client problemlos hergestellt.
Wenn Sie eine Verbindung zu einer lokalen IP-Adresse herstellen möchten, müssen Sie die Portnummer nicht auf 192.168.1.178
oder ähnliches ändern.
Daten senden:
Daten werden in Byte-Array gesendet. Sie müssen Ihre Daten in ein Byte-Array packen und auf der anderen Seite entpacken.
Wenn Sie mit Socket vertraut sind, können Sie auch versuchen, Ihr Byte-Array vor dem Senden zu verschlüsseln. Dadurch wird verhindert, dass jemand Ihr Paket stiehlt.
Beispiel für asynchrones Socket (Client / Server).
Server Side Beispiel
Listener für Server erstellen
Beginnen Sie mit dem Erstellen eines Servers, der Clients behandelt, die eine Verbindung herstellen, und Anforderungen, die gesendet werden. Erstellen Sie also eine Listener-Klasse, die dies erledigt.
class Listener
{
public Socket ListenerSocket; //This is the socket that will listen to any incoming connections
public short Port = 1234; // on this port we will listen
public Listener()
{
ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
}
Zuerst müssen wir den Listener-Socket initialisieren, um Verbindungen zu überwachen. Wir werden einen TCP-Sockel verwenden, deshalb verwenden wir SocketType.Stream. Außerdem geben wir an, welchen Port der Server abhören soll
Dann hören wir auf eingehende Verbindungen.
Die Baummethoden, die wir hier verwenden, sind:
Diese Methode bindet den Socket an einen IPEndPoint . Diese Klasse enthält die Host- und lokalen oder Remote-Port-Informationen, die eine Anwendung benötigt, um eine Verbindung zu einem Dienst auf einem Host herzustellen.
Der Parameter backlog gibt die Anzahl der eingehenden Verbindungen an, die zur Annahme in die Warteschlange gestellt werden können.
ListenerSocket.BeginAccept ();
Der Server wartet auf eingehende Verbindungen und arbeitet mit einer anderen Logik. Wenn eine Verbindung besteht, wechselt der Server wieder zu dieser Methode und führt die AcceptCallBack-Methode aus
public void StartListening()
{
try
{
MessageBox.Show($"Listening started port:{Port} protocol type: {ProtocolType.Tcp}");
ListenerSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
ListenerSocket.Listen(10);
ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);
}
catch(Exception ex)
{
throw new Exception("listening error" + ex);
}
}
Wenn sich ein Client verbindet, können wir sie mit dieser Methode akzeptieren:
Drei Methoden, die wir hier verwenden, sind:
Wir haben den Rückruf mit
Listener.BeginAccept()
beendet. Jetzt müssen wir den Rückruf beenden.The EndAccept()
-Methode akzeptiert einen IAsyncResult-Parameter. Dies speichert den Status der asynchronen Methode. Aus diesem Status können wir den Socket extrahieren, von dem die eingehende Verbindung stammt.ClientController.AddClient()
Mit dem Socket, den wir von
EndAccept()
haben, erstellen wir einen Client mit einer eigenen Methode (Code ClientController unter dem Server-Beispiel) .Wir müssen wieder zuhören, wenn der Socket mit der neuen Verbindung fertig ist. Übergeben Sie die Methode, die diesen Rückruf abfangen soll. Und übergeben Sie auch den Listener-Socket, damit wir diesen Socket für anstehende Verbindungen wiederverwenden können.
public void AcceptCallback(IAsyncResult ar)
{
try
{
Console.WriteLine($"Accept CallBack port:{Port} protocol type: {ProtocolType.Tcp}");
Socket acceptedSocket = ListenerSocket.EndAccept(ar);
ClientController.AddClient(acceptedSocket);
ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);
}
catch (Exception ex)
{
throw new Exception("Base Accept error"+ ex);
}
}
Jetzt haben wir ein Listening Socket, aber wie erhalten wir vom Client gesendete Daten. Dies ist, was der nächste Code anzeigt.
Erstellen Sie einen Serverempfänger für jeden Client
Erstellen Sie zunächst eine Empfangsklasse mit einem Konstruktor, der einen Socket als Parameter verwendet:
public class ReceivePacket
{
private byte[] _buffer;
private Socket _receiveSocket;
public ReceivePacket(Socket receiveSocket)
{
_receiveSocket = receiveSocket;
}
}
In der nächsten Methode beginnen wir zunächst damit, dem Puffer eine Größe von 4 Bytes (Int32) zu geben, oder das Paket enthält die Teile {Länge, tatsächliche Daten}. Die ersten 4 Bytes reservieren wir also für die Länge der Daten, den Rest für die eigentlichen Daten.
Als nächstes verwenden wir die BeginReceive () -Methode. Diese Methode wird verwendet, um den Empfang von verbundenen Clients zu starten. Wenn Daten empfangen werden, wird die ReceiveCallback
Funktion ausgeführt.
public void StartReceiving()
{
try
{
_buffer = new byte[4];
_receiveSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
}
catch {}
}
private void ReceiveCallback(IAsyncResult AR)
{
try
{
// if bytes are less than 1 takes place when a client disconnect from the server.
// So we run the Disconnect function on the current client
if (_receiveSocket.EndReceive(AR) > 1)
{
// Convert the first 4 bytes (int 32) that we received and convert it to an Int32 (this is the size for the coming data).
_buffer = new byte[BitConverter.ToInt32(_buffer, 0)];
// Next receive this data into the buffer with size that we did receive before
_receiveSocket.Receive(_buffer, _buffer.Length, SocketFlags.None);
// When we received everything its onto you to convert it into the data that you've send.
// For example string, int etc... in this example I only use the implementation for sending and receiving a string.
// Convert the bytes to string and output it in a message box
string data = Encoding.Default.GetString(_buffer);
MessageBox.Show(data);
// Now we have to start all over again with waiting for a data to come from the socket.
StartReceiving();
}
else
{
Disconnect();
}
}
catch
{
// if exeption is throw check if socket is connected because than you can startreive again else Dissconect
if (!_receiveSocket.Connected)
{
Disconnect();
}
else
{
StartReceiving();
}
}
}
private void Disconnect()
{
// Close connection
_receiveSocket.Disconnect(true);
// Next line only apply for the server side receive
ClientController.RemoveClient(_clientId);
// Next line only apply on the Client Side receive
Here you want to run the method TryToConnect()
}
Wir haben also einen Server eingerichtet, der eingehende Verbindungen empfangen und überwachen kann. Wenn ein Client eine Verbindung herstellt, wird er zu einer Liste von Clients hinzugefügt, und jeder Client verfügt über eine eigene Empfangsklasse. Um den Server anzuhören:
Listener listener = new Listener();
listener.StartListening();
Einige Klassen, die ich in diesem Beispiel verwende
class Client
{
public Socket _socket { get; set; }
public ReceivePacket Receive { get; set; }
public int Id { get; set; }
public Client(Socket socket, int id)
{
Receive = new ReceivePacket(socket, id);
Receive.StartReceiving();
_socket = socket;
Id = id;
}
}
static class ClientController
{
public static List<Client> Clients = new List<Client>();
public static void AddClient(Socket socket)
{
Clients.Add(new Client(socket,Clients.Count));
}
public static void RemoveClient(int id)
{
Clients.RemoveAt(Clients.FindIndex(x => x.Id == id));
}
}
Client Side Beispiel
Verbindung zum Server herstellen
Zunächst möchten wir eine Klasse erstellen, die eine Verbindung zu dem Server herstellt, den wir als Namen angeben: Connector:
class Connector
{
private Socket _connectingSocket;
}
Nächste Methode für diese Klasse ist TryToConnect ()
Diese Methode hat einige interessante Dinge:
Erstellen Sie den Sockel.
Als nächstes schleife ich, bis die Steckdose angeschlossen ist
Jede Schleife hält den Thread nur für 1 Sekunde. Wir wollen den Server XD nicht DOS
Mit Connect () wird versucht, eine Verbindung zum Server herzustellen. Wenn dies fehlschlägt, wird eine Ausnahme ausgelöst, das Programm wird jedoch weiterhin mit dem Server verbunden. Sie können dafür eine Connect CallBack- Methode verwenden, aber ich werde einfach eine Methode aufrufen, wenn der Socket angeschlossen ist.
Beachten Sie, dass der Client jetzt versucht, sich an Port 1234 mit Ihrem lokalen PC zu verbinden.
public void TryToConnect() { _connectingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); while (!_connectingSocket.Connected) { Thread.Sleep(1000); try { _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234)); } catch { } } SetupForReceiveing(); } } private void SetupForReceiveing() { // View Client Class bottom of Client Example Client.SetClient(_connectingSocket); Client.StartReceiving(); }
Senden einer Nachricht an den Server
Jetzt haben wir eine fast fertiggestellte oder Socket-Anwendung. Das einzige, was wir nicht haben, ist eine Klasse für das Senden einer Nachricht an den Server.
public class SendPacket
{
private Socket _sendSocked;
public SendPacket(Socket sendSocket)
{
_sendSocked = sendSocket;
}
public void Send(string data)
{
try
{
/* what hapends here:
1. Create a list of bytes
2. Add the length of the string to the list.
So if this message arrives at the server we can easily read the length of the coming message.
3. Add the message(string) bytes
*/
var fullPacket = new List<byte>();
fullPacket.AddRange(BitConverter.GetBytes(data.Length));
fullPacket.AddRange(Encoding.Default.GetBytes(data));
/* Send the message to the server we are currently connected to.
Or package stucture is {length of data 4 bytes (int32), actual data}*/
_sendSocked.Send(fullPacket.ToArray());
}
catch (Exception ex)
{
throw new Exception();
}
}
Zum Schluss können Sie zwei Schaltflächen zum Verbinden und zum Senden einer Nachricht eingeben:
private void ConnectClick(object sender, EventArgs e)
{
Connector tpp = new Connector();
tpp.TryToConnect();
}
private void SendClick(object sender, EventArgs e)
{
Client.SendString("Test data from client");
}
Die Client-Klasse, die ich in diesem Beispiel verwendet habe
public static void SetClient(Socket socket)
{
Id = 1;
Socket = socket;
Receive = new ReceivePacket(socket, Id);
SendPacket = new SendPacket(socket);
}
Beachten
Die Empfangsklasse vom Server entspricht der Empfangsklasse vom Client.
Fazit
Sie haben jetzt einen Server und einen Client. Sie können dieses grundlegende Beispiel ausarbeiten. Stellen Sie beispielsweise sicher, dass der Server auch Dateien oder andere Tings empfangen kann. Oder senden Sie eine Nachricht an den Client. Auf dem Server haben Sie eine Liste von Clients, so dass Sie, wenn Sie etwas erhalten, von Client wissen, von dem es gekommen ist.